diff --git a/frontend/components/integrations/CloudIntegration.tsx b/frontend/components/integrations/CloudIntegration.tsx new file mode 100644 index 0000000000..4bdad9cf5a --- /dev/null +++ b/frontend/components/integrations/CloudIntegration.tsx @@ -0,0 +1,113 @@ +import React from "react"; +import Image from "next/image"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + faCheck, + faX, + } from "@fortawesome/free-solid-svg-icons"; + +interface CloudIntegration { + integration: IntegrationOption; + setSelectedIntegrationOption: () => void; + integrationOptionPress: () => void; + deleteIntegrationAuth: () => void; + authorizations: any; +} + +interface IntegrationOption { + name: string; + type: string; + clientId: string; + docsLink: string; +} + +const CloudIntegration = ({ + integration, + setSelectedIntegrationOption, + integrationOptionPress, + deleteIntegrationAuth, + authorizations +}: CloudIntegration) => { + console.log('cio', integration); + return ( +
{ + if (!["Heroku"].includes(integration.name)) return; + setSelectedIntegrationOption(integration); + integrationOptionPress({ + integrationOption: integration + }); + }} + key={integration.name} + > + integration logo + {integration.name.split(" ").length > 2 ? ( +
+
{integration.name.split(" ")[0]}
+
+ {integration.name.split(" ")[1]}{" "} + {integration.name.split(" ")[2]} +
+
+ ) : ( +
+ {integration.name} +
+ )} + {["Heroku"].includes(integration.name) && + authorizations + .map((authorization) => authorization.integration) + .includes(integration.name.toLowerCase()) && ( +
+
{ + deleteIntegrationAuth({ + integrationAuthId: authorizations + .filter( + (authorization) => + authorization.integration == + integration.name.toLowerCase() + ) + .map((authorization) => authorization._id)[0], + }); + router.reload(); + }} + className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200" + > + + Revoke +
+
+ + Authorized +
+
+ )} + {!["Heroku"].includes(integration.name) && ( +
+
+ Coming Soon +
+
+ )} +
+ ); +} + +export default CloudIntegration; \ No newline at end of file diff --git a/frontend/components/integrations/FrameworkIntegration.tsx b/frontend/components/integrations/FrameworkIntegration.tsx new file mode 100644 index 0000000000..2f33c00230 --- /dev/null +++ b/frontend/components/integrations/FrameworkIntegration.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import Image from "next/image"; + +interface Framework { + name: string; + link: string; + image: string; +} + +const FrameworkIntegration = ({ + framework +}: { + framework: Framework; +}) => { + return ( +
+ +
1 ? "text-sm px-1" : "text-xl px-2"} text-center w-full max-w-xs`}> + {framework?.image && integration logo} + {framework?.name && framework?.image &&
} + {framework?.name && framework.name} +
+
+
+ ); +} + +export default FrameworkIntegration; diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx new file mode 100644 index 0000000000..6fd31ef2d7 --- /dev/null +++ b/frontend/components/integrations/Integration.tsx @@ -0,0 +1,140 @@ +import React, { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import { + faArrowRight, + faRotate, + faX, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { + reverseEnvMapping +} from "../../public/data/frequentConstants"; +import updateIntegration from "../../pages/api/integrations/updateIntegration" +import deleteIntegration from "../../pages/api/integrations/DeleteIntegration" +import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps"; +import Button from "~/components/basic/buttons/Button"; +import ListBox from "~/components/basic/Listbox"; + +interface ProjectIntegration { + app?: string; + environment: string; + integration: string; + integrationAuth: string; + isActive: Boolean; +} + +const Integration = ({ + projectIntegration +}: { + projectIntegration: ProjectIntegration; +}) => { + const [integrationEnvironment, setIntegrationEnvironment] = useState( + reverseEnvMapping[projectIntegration.environment] + ); + const [fileState, setFileState] = useState([]); + const router = useRouter(); + const [apps, setApps] = useState([]); + const [integrationApp, setIntegrationApp] = useState( + projectIntegration.app ? projectIntegration.app : apps[0] + ); + + useEffect(async () => { + const tempHerokuApps = await getIntegrationApps({ + integrationAuthId: projectIntegration.integrationAuth, + }); + const tempHerokuAppNames = tempHerokuApps.map((app) => app.name); + setApps(tempHerokuAppNames); + setIntegrationApp( + projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0] + ); + }, []); + + return ( +
+
+
+
+
+ ENVIRONMENT +
+ +
+ +
+
+ INTEGRATION +
+
+ {projectIntegration.integration.charAt(0).toUpperCase() + + projectIntegration.integration.slice(1)} +
+
+
+
+ HEROKU APP +
+ +
+
+
+ {projectIntegration.isActive ? ( +
+ +
In Sync
+
+ ) : ( +
+
+
+ + ); + }; + +export default Integration; \ No newline at end of file diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index f68f6a9dcd..d6363d0898 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -2,208 +2,85 @@ import React, { useEffect, useState } from "react"; import Head from "next/head"; import Image from "next/image"; import { useRouter } from "next/router"; -import { - faArrowRight, - faCheck, - faRotate, - faX, -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -import Button from "~/components/basic/buttons/Button"; -import ListBox from "~/components/basic/Listbox"; import NavHeader from "~/components/navigation/NavHeader"; -import getSecretsForProject from "~/components/utilities/secrets/getSecretsForProject"; +import Integration from "~/components/integrations/Integration"; +import FrameworkIntegration from "~/components/integrations/FrameworkIntegration"; +import CloudIntegration from "~/components/integrations/CloudIntegration"; import guidGenerator from "~/utilities/randomId"; - import { - envMapping, - frameworks, - reverseEnvMapping + frameworks } from "../../public/data/frequentConstants"; -import deleteIntegration from "../api/integrations/DeleteIntegration"; import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth"; -import getIntegrationApps from "../api/integrations/GetIntegrationApps"; import getIntegrations from "../api/integrations/GetIntegrations"; import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations"; import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations"; -import updateIntegration from "../api/integrations/updateIntegration"; import getBot from "../api/bot/getBot"; import setBotActiveStatus from "../api/bot/setBotActiveStatus"; import getLatestFileKey from "../api/workspace/getLatestFileKey"; import ActivateBotDialog from "~/components/basic/dialog/ActivateBotDialog"; - const { decryptAssymmetric, encryptAssymmetric } = require('../../components/utilities/cryptography/crypto'); const crypto = require("crypto"); -const Integration = ({ projectIntegration }) => { - const [integrationEnvironment, setIntegrationEnvironment] = useState( - reverseEnvMapping[projectIntegration.environment] - ); - const [fileState, setFileState] = useState([]); - const [data, setData] = useState(); - const [isKeyAvailable, setIsKeyAvailable] = useState(true); - const router = useRouter(); - const [apps, setApps] = useState([]); - const [integrationApp, setIntegrationApp] = useState( - projectIntegration.app ? projectIntegration.app : apps[0] - ); - - useEffect(async () => { - const tempHerokuApps = await getIntegrationApps({ - integrationAuthId: projectIntegration.integrationAuth, - }); - const tempHerokuAppNames = tempHerokuApps.map((app) => app.name); - setApps(tempHerokuAppNames); - setIntegrationApp( - projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0] - ); - }, []); - - return ( -
-
-
-
-
- ENVIRONMENT -
- -
- -
-
- INTEGRATION -
-
- {projectIntegration.integration.charAt(0).toUpperCase() + - projectIntegration.integration.slice(1)} -
-
-
-
- HEROKU APP -
- -
-
-
- {projectIntegration.isActive ? ( -
- -
In Sync
-
- ) : ( -
-
-
- - ); -}; - export default function Integrations() { - const [integrations, setIntegrations] = useState({}); + const [integrations, setIntegrations] = useState([]); const [projectIntegrations, setProjectIntegrations] = useState([]); const [authorizations, setAuthorizations] = useState(); - const router = useRouter(); - const [csrfToken, setCsrfToken] = useState(""); const [bot, setBot] = useState(null); const [isActivateBotOpen, setIsActivateBotOpen] = useState(false); const [selectedIntegrationOption, setSelectedIntegrationOption] = useState(null); + const router = useRouter(); + useEffect(async () => { try { - // generate CSRF token for OAuth2 code-token exchange integrations - const tempCSRFToken = crypto.randomBytes(16).toString("hex"); - setCsrfToken(tempCSRFToken); - localStorage.setItem("latestCSRFToken", tempCSRFToken); - + // get integrations authorized for project let projectAuthorizations = await getWorkspaceAuthorizations({ workspaceId: router.query.id, }); setAuthorizations(projectAuthorizations); + // get active/inactive (cloud) integrations for project const projectIntegrations = await getWorkspaceIntegrations({ workspaceId: router.query.id, }); - setProjectIntegrations(projectIntegrations); + // get bot for project const bot = await getBot({ workspaceId: router.query.id }); - setBot(bot.bot); - const integrationsData = await getIntegrations(); - setIntegrations(integrationsData); + // get cloud integration options + let integrationOptions = await getIntegrations(); + integrationOptions = Object + .keys(integrationOptions) + .map(integrationOption => integrationOptions[integrationOption]); + + setIntegrations(integrationOptions); } catch (err) { console.log(err); } }, []); /** - * Toggle activate/deactivate bot + * Activate bot for project by performing the following steps: + * 1. Get the (encrypted) project key + * 2. Decrypt project key with user's private key + * 3. Encrypt project key with bot's public key + * 4. Send encrypted project key to backend and set bot status to active */ const handleBotActivate = async () => { - const key = await getLatestFileKey({ workspaceId: router.query.id }); try { + const key = await getLatestFileKey({ workspaceId: router.query.id }); + if (bot) { - let botKey; - if (!bot.isActive) { - // case: bot is active -> deactivate - + // case: there is a bot const PRIVATE_KEY = localStorage.getItem('PRIVATE_KEY'); + const WORKSPACE_KEY = decryptAssymmetric({ ciphertext: key.latestKey.encryptedKey, nonce: key.latestKey.nonce, @@ -221,16 +98,14 @@ export default function Integrations() { encryptedKey: ciphertext, nonce } + + // case: bot is not active + setBot((await setBotActiveStatus({ + botId: bot._id, + isActive: bot.isActive ? false : true, + botKey + })).bot); } - - // case: bot is not active - const bot2 = await setBotActiveStatus({ - botId: bot._id, - isActive: bot.isActive ? false : true, - botKey - }); - setBot(bot2.bot); - } } catch (err) { console.error(err); } @@ -244,6 +119,10 @@ export default function Integrations() { */ const handleIntegrationOption = async ({ integrationOption }) => { // TODO: modularize + + // generate CSRF token for OAuth2 code-token exchange integrations + const csrfToken = crypto.randomBytes(16).toString("hex"); + switch (integrationOption.name) { case 'Heroku': window.location = `https://id.heroku.com/oauth/authorize?client_id=7b1311a1-1cb2-4938-8adf-f37a399ec41b&response_type=code&scope=write-protected&state=${csrfToken}`; @@ -286,152 +165,58 @@ export default function Integrations() { content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files." /> -
-
- - setIsActivateBotOpen(false)} - selectedIntegrationOption={selectedIntegrationOption} - handleBotActivate={handleBotActivate} - handleIntegrationOption={handleIntegrationOption} - /> - {projectIntegrations.length > 0 && ( - <> -
-
-

Current Project Integrations

-
-

- Manage your integrations of Infisical with third-party services. -

-
- {projectIntegrations.map((projectIntegration) => ( - - ))} - - )} -
0 ? 'mt-12' : 'mt-6'} mb-4 text-xl max-w-5xl px-2`}> -
-

Cloud Integrations

-
-

- Click on an integration to begin syncing secrets to it. -

-
-
- {Object.keys(integrations).map((integration) => ( -
{ - if (!["Heroku"].includes(integrations[integration].name)) return; - setSelectedIntegrationOption(integrations[integration]); - integrationOptionPress({ - integrationOption: integrations[integration] - }); - }} - key={integrations[integration].name} - > - integration logo - {integrations[integration].name.split(" ").length > 2 ? ( -
-
{integrations[integration].name.split(" ")[0]}
-
- {integrations[integration].name.split(" ")[1]}{" "} - {integrations[integration].name.split(" ")[2]} -
-
- ) : ( -
- {integrations[integration].name} -
- )} - {["Heroku"].includes(integrations[integration].name) && - authorizations - .map((authorization) => authorization.integration) - .includes(integrations[integration].name.toLowerCase()) && ( -
-
{ - deleteIntegrationAuth({ - integrationAuthId: authorizations - .filter( - (authorization) => - authorization.integration == - integrations[integration].name.toLowerCase() - ) - .map((authorization) => authorization._id)[0], - }); - router.reload(); - }} - className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200" - > - - Revoke -
-
- - Authorized -
-
- )} - {!["Heroku"].includes(integrations[integration].name) && ( -
-
- Coming Soon -
-
- )} -
- ))} -
-
-
-

Framework Integrations

+
+ + setIsActivateBotOpen(false)} + selectedIntegrationOption={selectedIntegrationOption} + handleBotActivate={handleBotActivate} + handleIntegrationOption={handleIntegrationOption} + /> + {projectIntegrations.length > 0 && ( + <> +
+

Current Project Integrations

+

+ Manage your integrations of Infisical with third-party services. +

-

- Click on a framework to get the setup instructions. -

-
+ + )} +
0 ? 'mt-12' : 'mt-6'} text-xl max-w-5xl px-2`}> +

Cloud Integrations

+

+ Click on an integration to begin syncing secrets to it. +

+
+
+ {integrations.map((integration) => ( + + ))} +
+
+

Framework Integrations

+

+ Click on a framework to get the setup instructions. +

+
+
+ {frameworks.map((framework) => ( + + ))}