diff --git a/backend/src/controllers/integrationAuthController.ts b/backend/src/controllers/integrationAuthController.ts index b5c9167aa..fc2767e0a 100644 --- a/backend/src/controllers/integrationAuthController.ts +++ b/backend/src/controllers/integrationAuthController.ts @@ -5,7 +5,7 @@ import { readFileSync } from 'fs'; import { IntegrationAuth, Integration } from '../models'; import { INTEGRATION_SET, ENV_DEV } from '../variables'; import { IntegrationService } from '../services'; -import { getApps } from '../integrations'; +import { getApps, revokeAccess } from '../integrations'; /** * Perform OAuth2 code-token exchange as part of integration [integration] for workspace with id [workspaceId] @@ -74,46 +74,22 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => { * @returns */ export const deleteIntegrationAuth = async (req: Request, res: Response) => { - // TODO: unfinished - disable application via Heroku API and make compatible with other integration types try { const { integrationAuthId } = req.params; - // TODO: disable application via Heroku API; figure out what authorization id is - - const integrations = JSON.parse( - readFileSync('./src/json/integrations.json').toString() - ); - - let authorizationId; - switch (req.integrationAuth.integration) { - case 'heroku': - authorizationId = integrations.heroku.clientId; - } - - // not sure what authorizationId is? - // // revoke authorization - // const res2 = await axios.delete( - // `https://api.heroku.com/oauth/authorizations/${authorizationId}`, - // { - // headers: { - // 'Accept': 'application/vnd.heroku+json; version=3', - // 'Authorization': 'Bearer ' + req.accessToken - // } - // } - // ); - - const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({ - _id: integrationAuthId + await revokeAccess({ + integrationAuth: req.integrationAuth, + accessToken: req.accessToken }); - - if (deletedIntegrationAuth) { - await Integration.deleteMany({ - integrationAuth: deletedIntegrationAuth._id - }); - } } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); return res.status(400).send({ message: 'Failed to delete integration authorization' }); } -}; + + return res.status(200).send({ + message: 'Successfully deleted integration authorization' + }); +} \ No newline at end of file diff --git a/backend/src/integrations/index.ts b/backend/src/integrations/index.ts index 3d8e8cc95..86c22de0c 100644 --- a/backend/src/integrations/index.ts +++ b/backend/src/integrations/index.ts @@ -2,10 +2,12 @@ import { exchangeCode } from './exchange'; import { exchangeRefresh } from './refresh'; import { getApps } from './apps'; import { syncSecrets } from './sync'; +import { revokeAccess } from './revoke'; export { exchangeCode, exchangeRefresh, getApps, - syncSecrets + syncSecrets, + revokeAccess } \ No newline at end of file diff --git a/backend/src/integrations/revoke.ts b/backend/src/integrations/revoke.ts new file mode 100644 index 000000000..833e6c88a --- /dev/null +++ b/backend/src/integrations/revoke.ts @@ -0,0 +1,50 @@ +import axios from 'axios'; +import * as Sentry from '@sentry/node'; +import { + IIntegrationAuth, + IntegrationAuth, + Integration +} from '../models'; +import { + INTEGRATION_HEROKU, + INTEGRATION_VERCEL, + INTEGRATION_NETLIFY +} from '../variables'; + +const revokeAccess = async ({ + integrationAuth, + accessToken +}: { + integrationAuth: IIntegrationAuth, + accessToken: String +}) => { + try { + // add any integration-specific revocation logic + switch (integrationAuth.integration) { + case INTEGRATION_HEROKU: + break; + case INTEGRATION_VERCEL: + break; + case INTEGRATION_NETLIFY: + break; + } + + const deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({ + _id: integrationAuth._id + }); + + if (deletedIntegrationAuth) { + await Integration.deleteMany({ + integrationAuth: deletedIntegrationAuth._id + }); + } + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to delete integration authorization'); + } +} + +export { + revokeAccess +} \ No newline at end of file diff --git a/frontend/components/integrations/CloudIntegration.tsx b/frontend/components/integrations/CloudIntegration.tsx index f3f1ad153..b9bbb08c8 100644 --- a/frontend/components/integrations/CloudIntegration.tsx +++ b/frontend/components/integrations/CloudIntegration.tsx @@ -1,5 +1,6 @@ import React from "react"; import Image from "next/image"; +import { useRouter } from "next/router"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faCheck, @@ -33,6 +34,7 @@ const CloudIntegration = ({ integrationOptionPress, integrationAuths }: Props) => { + const router = useRouter(); return integrationAuths ? (
{ + onClick={(event) => { + event.stopPropagation(); deleteIntegrationAuth({ integrationAuthId: integrationAuths .filter( diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index f8a8e3de0..6ebf3888e 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -115,44 +115,24 @@ export default function Integrations() { * @returns */ const handleIntegrationOption = async ({ integrationOption }) => { - // TODO: modularize and handle switch by slug - - console.log('handle', integrationOption); - - // generate CSRF token for OAuth2 code-token exchange integrations - const state = crypto.randomBytes(16).toString("hex"); - localStorage.setItem('latestCSRFToken', state); - - switch (integrationOption.name) { - case 'Heroku': - // console.log('Heroku integration ', integrationOption); - window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`; - break; - case 'Vercel': - window.location = `https://vercel.com/integrations/infisical/new?state=${state}`; - break; - case 'Netlify': - // window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=token&redirect_uri=${integrationOption.redirectURL}&state=${state}`; - window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${integrationOption.redirectURL}&state=${state}`; - // const res = await axios.post('https://api.netlify.com/api/v1/oauth/tickets' + '?client_id=' + integrationOption.clientId); - - // window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=ticket&redirect_uri=${integrationOption.redirectURL}&state=${state}&ticket=${res.data.id}`; - // `https://app.netlify.com/authorize?response_type=ticket&ticket=${ticket.id}` - try { - // const res = await axios.post('https://api.netlify.com/api/v1/oauth/tickets' + '?client_id=' + integrationOption.clientId); - // console.log('res response', res); - // const res2 = await axios.get('https://api.netlify.com/api/v1/oauth/tickets/' + res.data.id); - // console.log('res2 response', res2); - // console.log('ticket_id', res.data.id); - // // exchange ticket: - // const res3 = await axios.get(`https://api.netlify.com/api/v1/oauth/tickets/${res.data.id}/exchange`); - // console.log('res3 response', res3); - - } catch (err) { - console.error('Netlify ', err); - } - - break; + try { + // generate CSRF token for OAuth2 code-token exchange integrations + const state = crypto.randomBytes(16).toString("hex"); + localStorage.setItem('latestCSRFToken', state); + + switch (integrationOption.name) { + case 'Heroku': + window.location = `https://id.heroku.com/oauth/authorize?client_id=${integrationOption.clientId}&response_type=code&scope=write-protected&state=${state}`; + break; + case 'Vercel': + window.location = `https://vercel.com/integrations/infisical/new?state=${state}`; + break; + case 'Netlify': + window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&redirect_uri=${integrationOption.redirectURL}&state=${state}`; + break; + } + } catch (err) { + console.log(err); } }