From 7c24e0181a0147992df74ea1f5f94ab1fb766345 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Mon, 17 Jul 2023 15:09:15 +0530 Subject: [PATCH 01/21] add windmill variables to integration --- backend/src/variables/integration.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index 18eaeb049..bd077a019 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -26,6 +26,7 @@ export const INTEGRATION_SUPABASE = "supabase"; export const INTEGRATION_CHECKLY = "checkly"; export const INTEGRATION_HASHICORP_VAULT = "hashicorp-vault"; export const INTEGRATION_CLOUDFLARE_PAGES = "cloudflare-pages"; +export const INTEGRATION_WINDMILL = "windmill"; export const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -41,7 +42,8 @@ export const INTEGRATION_SET = new Set([ INTEGRATION_SUPABASE, INTEGRATION_CHECKLY, INTEGRATION_HASHICORP_VAULT, - INTEGRATION_CLOUDFLARE_PAGES + INTEGRATION_CLOUDFLARE_PAGES, + INTEGRATION_WINDMILL ]); // integration types @@ -71,6 +73,7 @@ export const INTEGRATION_SUPABASE_API_URL = "https://api.supabase.com"; export const INTEGRATION_LARAVELFORGE_API_URL = "https://forge.laravel.com"; export const INTEGRATION_CHECKLY_API_URL = "https://api.checklyhq.com"; export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = "https://api.cloudflare.com"; +export const INTEGRATION_WINDMILL_API_URL = "https://app.windmill.dev/api"; export const getIntegrationOptions = async () => { const INTEGRATION_OPTIONS = [ @@ -245,7 +248,17 @@ export const getIntegrationOptions = async () => { type: "pat", clientId: "", docsLink: "" - } + }, + { + name: "Windmill", + slug: "windmill", + image: "Cloudflare.png", + isAvailable: true, + type: "pat", + clientId: "", + docsLink: "" + }, + ] return INTEGRATION_OPTIONS; From 06bd98bf56ad4772a707c6b4c262f11a8faa1e69 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Mon, 17 Jul 2023 15:12:12 +0530 Subject: [PATCH 02/21] add windmill variables to model schema --- backend/src/models/integration.ts | 5 ++++- backend/src/models/integrationAuth.ts | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index c4021977d..7c487cbfe 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -18,6 +18,7 @@ import { INTEGRATION_SUPABASE, INTEGRATION_TRAVISCI, INTEGRATION_VERCEL, + INTEGRATION_WINDMILL, } from "../variables"; export interface IIntegration { @@ -54,7 +55,8 @@ export interface IIntegration { | "supabase" | "checkly" | "hashicorp-vault" - | "cloudflare-pages"; + | "cloudflare-pages" + | "windmill"; integrationAuth: Types.ObjectId; } @@ -144,6 +146,7 @@ const integrationSchema = new Schema( INTEGRATION_CHECKLY, INTEGRATION_HASHICORP_VAULT, INTEGRATION_CLOUDFLARE_PAGES, + INTEGRATION_WINDMILL, ], required: true, }, diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index ed3dabadf..3d18166f8 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -19,13 +19,14 @@ import { INTEGRATION_RENDER, INTEGRATION_SUPABASE, INTEGRATION_TRAVISCI, - INTEGRATION_VERCEL + INTEGRATION_VERCEL, + INTEGRATION_WINDMILL } from "../variables"; export interface IIntegrationAuth extends Document { _id: Types.ObjectId; workspace: Types.ObjectId; - integration: "heroku" | "vercel" | "netlify" | "github" | "gitlab" | "render" | "railway" | "flyio" | "azure-key-vault" | "laravel-forge" | "circleci" | "travisci" | "supabase" | "aws-parameter-store" | "aws-secret-manager" | "checkly" | "cloudflare-pages"; + integration: "heroku" | "vercel" | "netlify" | "github" | "gitlab" | "render" | "railway" | "flyio" | "azure-key-vault" | "laravel-forge" | "circleci" | "travisci" | "supabase" | "aws-parameter-store" | "aws-secret-manager" | "checkly" | "cloudflare-pages" | "windmill"; teamId: string; accountId: string; url: string; @@ -71,6 +72,7 @@ const integrationAuthSchema = new Schema( INTEGRATION_SUPABASE, INTEGRATION_HASHICORP_VAULT, INTEGRATION_CLOUDFLARE_PAGES, + INTEGRATION_WINDMILL, ], required: true, }, From 52e26fc6fac948c05032e25a7f116f655f498f4f Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Mon, 17 Jul 2023 16:34:39 +0530 Subject: [PATCH 03/21] create integration pages for windmill --- .../pages/integrations/windmill/authorize.tsx | 65 ++++++++ .../pages/integrations/windmill/create.tsx | 156 ++++++++++++++++++ 2 files changed, 221 insertions(+) create mode 100644 frontend/src/pages/integrations/windmill/authorize.tsx create mode 100644 frontend/src/pages/integrations/windmill/create.tsx diff --git a/frontend/src/pages/integrations/windmill/authorize.tsx b/frontend/src/pages/integrations/windmill/authorize.tsx new file mode 100644 index 000000000..fcf7048d1 --- /dev/null +++ b/frontend/src/pages/integrations/windmill/authorize.tsx @@ -0,0 +1,65 @@ +import { useState } from "react"; +import { useRouter } from "next/router"; + +import { Button, Card, CardTitle, FormControl, Input } from "../../../components/v2"; +import saveIntegrationAccessToken from "../../api/integrations/saveIntegrationAccessToken"; + + +export default function WindmillCreateIntegrationPage() { + const router = useRouter(); + const [apiKey, setApiKey] = useState(""); + const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [isLoading, setIsLoading] = useState(false); + + const handleButtonClick = async () => { + try { + setApiKeyErrorText(""); + if (apiKey.length === 0) { + setApiKeyErrorText("API Key cannot be blank"); + return; + } + + setIsLoading(true); + + const integrationAuth = await saveIntegrationAccessToken({ + workspaceId: localStorage.getItem("projectData.id"), + integration: "windmill", + accessToken: apiKey, + accessId: null, + url: null, + namespace: null + }); + + setIsLoading(false); + + router.push(`/integrations/windmill/create?integrationAuthId=${integrationAuth._id}`); + } catch (err) { + console.error(err); + } + }; + + return ( +
+ + Windmill Integration + + setApiKey(e.target.value)} /> + + + +
+ ); +} + +WindmillCreateIntegrationPage.requireAuth = true; \ No newline at end of file diff --git a/frontend/src/pages/integrations/windmill/create.tsx b/frontend/src/pages/integrations/windmill/create.tsx new file mode 100644 index 000000000..519355f65 --- /dev/null +++ b/frontend/src/pages/integrations/windmill/create.tsx @@ -0,0 +1,156 @@ +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import queryString from "query-string"; + +import { + Button, + Card, + CardTitle, + FormControl, + Input, + Select, + SelectItem +} from "../../../components/v2"; +import { + useGetIntegrationAuthApps, + useGetIntegrationAuthById +} from "../../../hooks/api/integrationAuth"; +import { useGetWorkspaceById } from "../../../hooks/api/workspace"; +import createIntegration from "../../api/integrations/createIntegration"; + +export default function WindmillCreateIntegrationPage() { + const router = useRouter(); + + const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); + + const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ""); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? "" + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); + const [secretPath, setSecretPath] = useState("/"); + const [targetApp, setTargetApp] = useState(""); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + } else { + setTargetApp("none"); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?._id) return; + + setIsLoading(true); + + await createIntegration({ + integrationAuthId: integrationAuth?._id, + isActive: true, + app: targetApp, + appId: + integrationAuthApps?.find((integrationAuthApp) => integrationAuthApp.name === targetApp) + ?.appId ?? null, + sourceEnvironment: selectedSourceEnvironment, + targetEnvironment: null, + targetEnvironmentId: null, + targetService: null, + targetServiceId: null, + owner: null, + path: null, + region: null, + secretPath + }); + + setIsLoading(false); + + router.push(`/integrations/${localStorage.getItem("projectData.id")}`); + } catch (err) { + console.error(err); + } + }; + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetApp ? ( +
+ + Windmill Integration + + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + + +
+ ) : ( +
+ ); +} + +WindmillCreateIntegrationPage.requireAuth = true; From 6125246794a6b279c435bc5054fdf773230c9912 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Mon, 17 Jul 2023 16:35:11 +0530 Subject: [PATCH 04/21] add integration authorize redirect url --- frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx index fd82d1fa7..fa6633df0 100644 --- a/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx +++ b/frontend/src/views/IntegrationsPage/IntegrationPage.utils.tsx @@ -92,6 +92,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) => case "cloudflare-pages": link = `${window.location.origin}/integrations/cloudflare-pages/authorize`; break; + case "windmill": + link = `${window.location.origin}/integrations/windmill/authorize`; + break; default: break; } From 04611d980b55fd58ad355481a19973c0415c112e Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Mon, 17 Jul 2023 16:50:27 +0530 Subject: [PATCH 05/21] create windmill get all workspaces list function --- backend/src/integrations/apps.ts | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index b2fec9292..0d5a8239f 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -32,6 +32,8 @@ import { INTEGRATION_TRAVISCI_API_URL, INTEGRATION_VERCEL, INTEGRATION_VERCEL_API_URL, + INTEGRATION_WINDMILL, + INTEGRATION_WINDMILL_API_URL, } from "../variables"; interface App { @@ -145,6 +147,11 @@ const getApps = async ({ accountId: accessId }) break; + case INTEGRATION_WINDMILL: + apps = await getAppsWindmill({ + accessToken, + }); + break; } return apps; @@ -721,4 +728,33 @@ const getAppsCloudflarePages = async ({ return apps; } +/** + * Return list of projects for Windmill integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for Windmill API + * @returns {Object[]} apps - names of Windmill workspaces + * @returns {String} apps.name - name of Windmill workspace + */ +const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { + const { data } = await standardRequest.get( + `${INTEGRATION_WINDMILL_API_URL}/workspaces/list`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ); + + const apps = data.map((a: any) => { + return { + name: a.name, + appId: a.id, + }; + }); + + return apps; +}; + + export { getApps }; From 4db7b0c05e3b65b9368c9f32209d536d028a203d Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Wed, 19 Jul 2023 13:13:14 +0530 Subject: [PATCH 06/21] add function for windmill secret sync --- backend/src/integrations/sync.ts | 115 +++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 5d8efa101..a085dd110 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -42,6 +42,8 @@ import { INTEGRATION_TRAVISCI_API_URL, INTEGRATION_VERCEL, INTEGRATION_VERCEL_API_URL, + INTEGRATION_WINDMILL, + INTEGRATION_WINDMILL_API_URL, } from "../variables"; import { standardRequest} from "../config/request"; @@ -202,6 +204,13 @@ const syncSecrets = async ({ accessToken }); break; + case INTEGRATION_WINDMILL: + await syncSecretsWindmill({ + integration, + secrets, + accessToken, + }); + break; } }; @@ -1937,4 +1946,110 @@ const syncSecretsCloudflarePages = async ({ ); } +/** + * Sync/push [secrets] to Windmil 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 windmill integration + */ +const syncSecretsWindmill = async ({ + integration, + secrets, + accessToken, +}: { + integration: IIntegration; + secrets: any; + accessToken: string; +}) => { + const { data: getSecretsRes } = await standardRequest.get( + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/list`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ); + + // convert secret results to [key] format + const secretsResList = getSecretsRes.map((secretObj: any) => (secretObj.path)); + + // convert the secrets to [{}] format + const modifiedFormatForSecretInjection: any[] = []; + const modifiedFormatForCreateSecretInjection: any[] = []; + const modifiedFormatForUpdateSecretInjection: any[] = []; + Object.keys(secrets).forEach( + (key) => { + if(key.startsWith("u/") || key.startsWith("f/")) { + if(secretsResList.includes(key)) { + modifiedFormatForUpdateSecretInjection.push({ + path: key, + value: secrets[key], + is_secret: true + }); + } else { + modifiedFormatForCreateSecretInjection.push({ + path: key, + value: secrets[key], + is_secret: true, + description: "" + }); + } + }; + } + ); + + // create new secrets in windmill workspace + modifiedFormatForCreateSecretInjection.forEach(async (secretObj: any) => { + await standardRequest.post( + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/create`, + secretObj, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ); + }) + + // update old secrets already present in windmill workspace + modifiedFormatForUpdateSecretInjection.forEach(async (secretObj: any) => { + await standardRequest.post( + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/update/${secretObj.path}`, + secretObj, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ) + }) + + // create list of secrets to delete + const secretsToDelete: any = []; + secretsResList.forEach((secret: string) => { + if(!(secret in secrets)) { + secretsToDelete.push(secret); + } + }) + + // delete all secrets from secretsToDelete List + secretsToDelete.forEach(async (secret: string) => { + await standardRequest.delete( + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/delete/${secret}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + "Accept-Encoding": "application/json", + } + } + ); + }); +}; + export { syncSecrets }; From a52c2f03bfe79534b5d35b000e19522397be6bc4 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Wed, 19 Jul 2023 14:12:05 +0530 Subject: [PATCH 07/21] add integration slug name mapping for windmill --- frontend/public/data/frequentConstants.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index c3c74091d..a6a21c966 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -20,7 +20,8 @@ const integrationSlugNameMapping: Mapping = { 'supabase': 'Supabase', 'checkly': 'Checkly', 'hashicorp-vault': 'Vault', - 'cloudflare-pages': 'Cloudflare Pages' + 'cloudflare-pages': 'Cloudflare Pages', + 'windmill': 'windmill' } const envMapping: Mapping = { From d2d23a7abaafc97d7d411a64dc4c178ba561647d Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Wed, 19 Jul 2023 14:47:15 +0530 Subject: [PATCH 08/21] add windmill logo --- .../public/images/integrations/Windmill.png | Bin 0 -> 10188 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 frontend/public/images/integrations/Windmill.png diff --git a/frontend/public/images/integrations/Windmill.png b/frontend/public/images/integrations/Windmill.png new file mode 100644 index 0000000000000000000000000000000000000000..c4297077fc1c6dc9524ae71f0c033a672c239430 GIT binary patch literal 10188 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 Date: Wed, 19 Jul 2023 14:47:37 +0530 Subject: [PATCH 09/21] add windmill logo to integration variable --- backend/src/variables/integration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index bd077a019..12ff525bc 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -252,7 +252,7 @@ export const getIntegrationOptions = async () => { { name: "Windmill", slug: "windmill", - image: "Cloudflare.png", + image: "Windmill.png", isAvailable: true, type: "pat", clientId: "", From 83d52919988518de53b7541b4b54bba05e228d16 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Wed, 19 Jul 2023 15:00:42 +0530 Subject: [PATCH 10/21] add interface for windmill request body --- backend/src/integrations/sync.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index a085dd110..f633cb88e 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1963,6 +1963,17 @@ const syncSecretsWindmill = async ({ secrets: any; accessToken: string; }) => { + interface WindmilSecretUpdate { + path: string; + value: string; + is_secret: boolean; + } + + interface WindmillSecretCreate extends WindmilSecretUpdate { + description: string; + } + + // get secrets stored in windmill workspace const { data: getSecretsRes } = await standardRequest.get( `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/list`, { @@ -1977,9 +1988,9 @@ const syncSecretsWindmill = async ({ const secretsResList = getSecretsRes.map((secretObj: any) => (secretObj.path)); // convert the secrets to [{}] format - const modifiedFormatForSecretInjection: any[] = []; - const modifiedFormatForCreateSecretInjection: any[] = []; - const modifiedFormatForUpdateSecretInjection: any[] = []; + const modifiedFormatForCreateSecretInjection: WindmillSecretCreate[] = []; + const modifiedFormatForUpdateSecretInjection: WindmilSecretUpdate[] = []; + Object.keys(secrets).forEach( (key) => { if(key.startsWith("u/") || key.startsWith("f/")) { @@ -2030,7 +2041,7 @@ const syncSecretsWindmill = async ({ }) // create list of secrets to delete - const secretsToDelete: any = []; + const secretsToDelete: string[] = []; secretsResList.forEach((secret: string) => { if(!(secret in secrets)) { secretsToDelete.push(secret); From ec1e8422026511780b63268875f2ff5ead359b8c Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Wed, 19 Jul 2023 19:04:59 +0530 Subject: [PATCH 11/21] change windmill workspace label --- frontend/src/pages/integrations/windmill/create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/integrations/windmill/create.tsx b/frontend/src/pages/integrations/windmill/create.tsx index 519355f65..a6cb6f8eb 100644 --- a/frontend/src/pages/integrations/windmill/create.tsx +++ b/frontend/src/pages/integrations/windmill/create.tsx @@ -114,7 +114,7 @@ export default function WindmillCreateIntegrationPage() { placeholder="Provide a path, default is /" /> - + setTargetApp(val)} From aa019e15016d254d76f258c6ea0499ebc2a4c5d1 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Thu, 20 Jul 2023 02:12:36 +0530 Subject: [PATCH 14/21] add pattern match for windmill stored secrets --- backend/src/integrations/sync.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index f633cb88e..14ab37ea5 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1993,7 +1993,8 @@ const syncSecretsWindmill = async ({ Object.keys(secrets).forEach( (key) => { - if(key.startsWith("u/") || key.startsWith("f/")) { + const pattern = new RegExp('^([a-zA-Z0-9])\/([a-zA-Z-0-9])+\/([a-zA-Z0-9])+') + if((key.startsWith("u/") || key.startsWith("f/")) && pattern.test(key)) { if(secretsResList.includes(key)) { modifiedFormatForUpdateSecretInjection.push({ path: key, From b3baaac5c8c73b1d9b49cee2bffb310a4e93beec Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Thu, 20 Jul 2023 12:57:16 +0530 Subject: [PATCH 15/21] map secret comments to windmill api description --- backend/src/helpers/bot.ts | 68 ++++++++++++++++++++++++++++++ backend/src/helpers/integration.ts | 9 ++++ backend/src/integrations/sync.ts | 23 ++++++---- backend/src/services/BotService.ts | 25 +++++++++++ 4 files changed, 116 insertions(+), 9 deletions(-) diff --git a/backend/src/helpers/bot.ts b/backend/src/helpers/bot.ts index c7c5904c7..d3db7febf 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -16,6 +16,7 @@ import { client, getEncryptionKey, getRootEncryptionKey } from "../config"; import { InternalServerError } from "../utils/errors"; import Folder from "../models/folder"; import { getFolderByPath } from "../services/FolderService"; +import { environment } from "../routes/v2"; /** * Create an inactive bot with name [name] for workspace with id [workspaceId] @@ -275,3 +276,70 @@ export const decryptSymmetricHelper = async ({ return plaintext; }; + +/** + * Return decrypted comments for workspace secrets with id [workspaceId] + * and [envionment] using bot + * @param {Object} obj + * @param {String} obj.workspaceId - id of workspace + * @param {String} obj.environment - environment + */ +export const getSecretsCommentBotHelper = async ({ + workspaceId, + environment, + secretPath +} : { + workspaceId: Types.ObjectId; + environment: string; + secretPath: string; +}) => { + const content = {} as any; + const key = await getKey({ workspaceId: workspaceId }); + + let folderId = "root"; + const folders = await Folder.findOne({ + workspace: workspaceId, + environment, + }); + + if (!folders && secretPath !== "/") { + throw InternalServerError({ message: "Folder not found" }); + } + + if (folders) { + const folder = getFolderByPath(folders.nodes, secretPath); + if (!folder) { + throw InternalServerError({ message: "Folder not found" }); + } + folderId = folder.id; + } + + const secrets = await Secret.find({ + workspace: workspaceId, + environment, + type: SECRET_SHARED, + folder: folderId, + }); + + secrets.forEach((secret: ISecret) => { + if(secret.secretCommentCiphertext && secret.secretCommentIV && secret.secretCommentTag) { + const secretKey = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: secret.secretKeyCiphertext, + iv: secret.secretKeyIV, + tag: secret.secretKeyTag, + key, + }); + + const commentValue = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: secret.secretCommentCiphertext, + iv: secret.secretCommentIV, + tag: secret.secretCommentTag, + key, + }); + + content[secretKey] = commentValue; + } + }); + + return content; +} \ No newline at end of file diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts index f2dd1ba66..aaa8c9610 100644 --- a/backend/src/helpers/integration.ts +++ b/backend/src/helpers/integration.ts @@ -137,6 +137,14 @@ export const syncIntegrationsHelper = async ({ secretPath: integration.secretPath, }); + // get workspace, environment (shared) secrets comments + const secretComments = await BotService.getSecretComments({ + workspaceId: integration.workspace, + environment: integration.environment, + secretPath: integration.secretPath, + }) + + const integrationAuth = await IntegrationAuth.findById( integration.integrationAuth ); @@ -154,6 +162,7 @@ export const syncIntegrationsHelper = async ({ secrets, accessId: access.accessId === undefined ? null : access.accessId, accessToken: access.accessToken, + secretComments }); } }; diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 14ab37ea5..59445ce4f 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -55,6 +55,7 @@ import { standardRequest} from "../config/request"; * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) * @param {String} obj.accessId - access id for integration * @param {String} obj.accessToken - access token for integration + * @param {Object} obj.secretComments - secret comments to push to integration (object where keys are secret keys and values are comment values) */ const syncSecrets = async ({ integration, @@ -62,12 +63,14 @@ const syncSecrets = async ({ secrets, accessId, accessToken, + secretComments }: { integration: IIntegration; integrationAuth: IIntegrationAuth; secrets: any; accessId: string | null; accessToken: string; + secretComments: any; }) => { switch (integration.integration) { case INTEGRATION_AZURE_KEY_VAULT: @@ -209,6 +212,7 @@ const syncSecrets = async ({ integration, secrets, accessToken, + secretComments }); break; } @@ -1953,24 +1957,24 @@ const syncSecretsCloudflarePages = async ({ * @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 windmill integration + * @param {Object} obj.secretComments - secret comments to push to integration (object where keys are secret keys and values are comment values) */ const syncSecretsWindmill = async ({ integration, secrets, accessToken, + secretComments }: { integration: IIntegration; secrets: any; accessToken: string; + secretComments: any; }) => { - interface WindmilSecretUpdate { + interface WindmillSecret { path: string; value: string; is_secret: boolean; - } - - interface WindmillSecretCreate extends WindmilSecretUpdate { - description: string; + description?: string; } // get secrets stored in windmill workspace @@ -1988,8 +1992,8 @@ const syncSecretsWindmill = async ({ const secretsResList = getSecretsRes.map((secretObj: any) => (secretObj.path)); // convert the secrets to [{}] format - const modifiedFormatForCreateSecretInjection: WindmillSecretCreate[] = []; - const modifiedFormatForUpdateSecretInjection: WindmilSecretUpdate[] = []; + const modifiedFormatForCreateSecretInjection: WindmillSecret[] = []; + const modifiedFormatForUpdateSecretInjection: WindmillSecret[] = []; Object.keys(secrets).forEach( (key) => { @@ -1999,14 +2003,15 @@ const syncSecretsWindmill = async ({ modifiedFormatForUpdateSecretInjection.push({ path: key, value: secrets[key], - is_secret: true + is_secret: true, + description: secretComments[key] || "" }); } else { modifiedFormatForCreateSecretInjection.push({ path: key, value: secrets[key], is_secret: true, - description: "" + description: secretComments[key] || "" }); } }; diff --git a/backend/src/services/BotService.ts b/backend/src/services/BotService.ts index ca31bf103..7ebf53ae8 100644 --- a/backend/src/services/BotService.ts +++ b/backend/src/services/BotService.ts @@ -5,6 +5,7 @@ import { getIsWorkspaceE2EEHelper, getKey, getSecretsBotHelper, + getSecretsCommentBotHelper, } from "../helpers/bot"; /** @@ -107,6 +108,30 @@ class BotService { tag, }); } + + /** + * Return decreypted secrets comment for workspace with id [worskpaceId] and + * environment [environment] shared to bot. + * @param {Object} obj + * @param {String} obj.workspaceId - id of workspace of secrets + * @param {String} obj.environment - environment for secrets + * @returns {Object} secretObj - object where keys are secret keys and values are comment values + */ + static async getSecretComments({ + workspaceId, + environment, + secretPath + }: { + workspaceId: Types.ObjectId; + environment: string; + secretPath: string; + }) { + return await getSecretsCommentBotHelper({ + workspaceId, + environment, + secretPath + }); + } } export default BotService; From c62504d6588c038cc99da840a8e4bfa30d543b66 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Thu, 20 Jul 2023 19:21:04 +0530 Subject: [PATCH 16/21] correct codefresh image file name --- .../integrations/{codefresh.png => Codefresh.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/public/images/integrations/{codefresh.png => Codefresh.png} (100%) diff --git a/frontend/public/images/integrations/codefresh.png b/frontend/public/images/integrations/Codefresh.png similarity index 100% rename from frontend/public/images/integrations/codefresh.png rename to frontend/public/images/integrations/Codefresh.png From d67e96507a41df71ca397819e8872dc1a612db46 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Wed, 26 Jul 2023 23:14:42 +0530 Subject: [PATCH 17/21] fix:unauthorized response for app name --- backend/src/integrations/apps.ts | 2 +- backend/src/integrations/sync.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 1b4069502..98b1f1170 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -789,7 +789,7 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { const authCheckForApps = async (data: any) => { const allAppResponse = data.map(async (app: any) => { return standardRequest.get( - `${INTEGRATION_WINDMILL_API_URL}/w/${app.name}/users/whoami`, + `${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/users/whoami`, { headers: { Authorization: `Bearer ${accessToken}`, diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index e8e382ed9..139f06ecb 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -2049,7 +2049,7 @@ const syncSecretsWindmill = async ({ // get secrets stored in windmill workspace const { data: getSecretsRes } = await standardRequest.get( - `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/list`, + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.appId}/variables/list`, { headers: { Authorization: `Bearer ${accessToken}`, @@ -2091,7 +2091,7 @@ const syncSecretsWindmill = async ({ // create new secrets in windmill workspace modifiedFormatForCreateSecretInjection.forEach(async (secretObj: any) => { await standardRequest.post( - `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/create`, + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.appId}/variables/create`, secretObj, { headers: { @@ -2105,7 +2105,7 @@ const syncSecretsWindmill = async ({ // update old secrets already present in windmill workspace modifiedFormatForUpdateSecretInjection.forEach(async (secretObj: any) => { await standardRequest.post( - `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/update/${secretObj.path}`, + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.appId}/variables/update/${secretObj.path}`, secretObj, { headers: { @@ -2127,7 +2127,7 @@ const syncSecretsWindmill = async ({ // delete all secrets from secretsToDelete List secretsToDelete.forEach(async (secret: string) => { await standardRequest.delete( - `${INTEGRATION_WINDMILL_API_URL}/w/${integration.app}/variables/delete/${secret}`, + `${INTEGRATION_WINDMILL_API_URL}/w/${integration.appId}/variables/delete/${secret}`, { headers: { Authorization: `Bearer ${accessToken}`, From 7457f573e98e120247f93c9edf9e85856b93863c Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Wed, 26 Jul 2023 23:43:44 +0530 Subject: [PATCH 18/21] add dash and underscores for secret pattern test --- backend/src/integrations/sync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 139f06ecb..709af24d0 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -2067,7 +2067,7 @@ const syncSecretsWindmill = async ({ Object.keys(secrets).forEach( (key) => { - const pattern = new RegExp('^([a-zA-Z0-9])\/([a-zA-Z-0-9])+\/([a-zA-Z0-9])+') + const pattern = new RegExp('^([a-zA-Z0-9])\/([a-zA-Z-0-9-_])+\/([a-zA-Z0-9-_])+') if((key.startsWith("u/") || key.startsWith("f/")) && pattern.test(key)) { if(secretsResList.includes(key)) { modifiedFormatForUpdateSecretInjection.push({ From 7b1a4fa8e4d7e9c09ee390c32fac193d2aff937c Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Thu, 27 Jul 2023 00:48:17 +0530 Subject: [PATCH 19/21] change regexp to accept deeper level paths --- backend/src/integrations/sync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 709af24d0..1c7b264a7 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -2067,7 +2067,7 @@ const syncSecretsWindmill = async ({ Object.keys(secrets).forEach( (key) => { - const pattern = new RegExp('^([a-zA-Z0-9])\/([a-zA-Z-0-9-_])+\/([a-zA-Z0-9-_])+') + const pattern = new RegExp('^[uf]+[\/](?:[a-zA-Z0-9-_]+[\/])*([a-zA-Z0-9-_]+)') if((key.startsWith("u/") || key.startsWith("f/")) && pattern.test(key)) { if(secretsResList.includes(key)) { modifiedFormatForUpdateSecretInjection.push({ From adb27bb72921bfdf74f89a5aa48ec3cd5d470c95 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Date: Thu, 27 Jul 2023 13:11:48 +0530 Subject: [PATCH 20/21] fix: allow apps which have write access --- backend/src/integrations/apps.ts | 91 ++++++++++++++----- .../pages/integrations/windmill/create.tsx | 2 +- 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index 98b1f1170..55462ec2f 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -785,41 +785,82 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { } ); - // make calls for each app to check user is admin for that app or not - const authCheckForApps = async (data: any) => { - const allAppResponse = data.map(async (app: any) => { - return standardRequest.get( - `${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/users/whoami`, + //check for write access of secrets in windmill workspaces + const writeAccessCheck = data.map(async (app: any) => { + try { + const userPath = "u/user/variable"; + const folderPath = "f/folder/variable"; + + const { data: writeUser } = await standardRequest.post( + `${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/create`, + { + path: userPath, + value: "variable", + is_secret: true, + description: "variable description" + }, { headers: { Authorization: `Bearer ${accessToken}`, "Accept-Encoding": "application/json", }, } - ) - .then((response: any) => { - const modifiedData = { ...response.data }; - modifiedData.appName = app.name; - return modifiedData; - }) - .catch((error: any) => { - return undefined; - }); - }); + ); - const appPromiseResponses = await Promise.all(allAppResponse) - const filteredAppResponses = appPromiseResponses.filter((authRes: any) => (authRes !== undefined) && (authRes.is_admin)); - - return filteredAppResponses; - } + const { data: writeFolder } = await standardRequest.post( + `${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/create`, + { + path: folderPath, + value: "variable", + is_secret: true, + description: "variable description" + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ); + + // is write access is allowed then delete the created secrets from workspace + if (writeUser && writeFolder) { + await standardRequest.delete( + `${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/delete/${userPath}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ); - // get apps that user(auth token) is authorized for - const authorizedApps = await authCheckForApps(data); + await standardRequest.delete( + `${INTEGRATION_WINDMILL_API_URL}/w/${app.id}/variables/delete/${folderPath}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json", + }, + } + ); - const apps = authorizedApps.map((a: any) => { + return app; + } else { + return { error: "cannot write secret" }; + } + } catch (err: any) { + return { error: err.message }; + } + }); + + const appsWriteResponses = await Promise.all(writeAccessCheck); + const appsWithWriteAccess = appsWriteResponses.filter((appRes: any) => !appRes.error); + + const apps = appsWithWriteAccess.map((a: any) => { return { - name: a.appName, - appId: a.workspace_id, + name: a.name, + appId: a.id, }; }); diff --git a/frontend/src/pages/integrations/windmill/create.tsx b/frontend/src/pages/integrations/windmill/create.tsx index 815da396a..fd4662ce1 100644 --- a/frontend/src/pages/integrations/windmill/create.tsx +++ b/frontend/src/pages/integrations/windmill/create.tsx @@ -114,7 +114,7 @@ export default function WindmillCreateIntegrationPage() { placeholder="Provide a path, default is /" /> - + setTargetApp(val)}