diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 83d2bacdc..ea5f653a3 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -11,8 +11,10 @@ const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!; const MONGO_URL = process.env.MONGO_URL!; const NODE_ENV = process.env.NODE_ENV! || 'production'; const OAUTH_CLIENT_SECRET_HEROKU = process.env.OAUTH_CLIENT_SECRET_HEROKU!; -const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!; const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!; +const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!; +const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!; +const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!; const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com'; const POSTHOG_PROJECT_API_KEY = process.env.POSTHOG_PROJECT_API_KEY! || @@ -48,8 +50,10 @@ export { MONGO_URL, NODE_ENV, OAUTH_CLIENT_SECRET_HEROKU, - CLIENT_SECRET_VERCEL, CLIENT_ID_VERCEL, + CLIENT_ID_NETLIFY, + CLIENT_SECRET_VERCEL, + CLIENT_SECRET_NETLIFY, POSTHOG_HOST, POSTHOG_PROJECT_API_KEY, PRIVATE_KEY, diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts index 0c4bd4949..aae9d4b00 100644 --- a/backend/src/helpers/integration.ts +++ b/backend/src/helpers/integration.ts @@ -55,8 +55,8 @@ const handleOAuthExchangeHelper = async ({ integration, code }); - - // TODO: continue ironing out Vercel integration + + return; let update: Update = { workspace: workspaceId, diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 9ce670144..1c764c1da 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -3,15 +3,19 @@ import * as Sentry from '@sentry/node'; import { INTEGRATION_HEROKU, INTEGRATION_VERCEL, + INTEGRATION_NETLIFY, INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL, + INTEGRATION_NETLIFY_TOKEN_URL, ACTION_PUSH_TO_HEROKU } from '../variables'; import { SITE_URL, OAUTH_CLIENT_SECRET_HEROKU, CLIENT_ID_VERCEL, - CLIENT_SECRET_VERCEL + CLIENT_ID_NETLIFY, + CLIENT_SECRET_VERCEL, + CLIENT_SECRET_NETLIFY } from '../config'; interface ExchangeCodeHerokuResponse { @@ -63,6 +67,12 @@ const exchangeCode = async ({ obj = await exchangeCodeVercel({ code }); + break; + case INTEGRATION_NETLIFY: + obj = await exchangeCodeNetlify({ + code + }); + break; } } catch (err) { Sentry.setUser(null); @@ -157,6 +167,60 @@ const exchangeCodeVercel = async ({ }); } +/** + * Return [accessToken], [accessExpiresAt], and [refreshToken] for Vercel + * 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 Heroku API + * @returns {String} obj2.refreshToken - refresh token for Heroku API + * @returns {Date} obj2.accessExpiresAt - date of expiration for access token + */ +const exchangeCodeNetlify = async ({ + code +}: { + code: string; +}) => { + console.log('exchangeCodeNetlify'); + let res: ExchangeCodeVercelResponse; + try { + res = (await axios.post( + INTEGRATION_VERCEL_TOKEN_URL, + new URLSearchParams({ + code: code, + client_id: CLIENT_ID_VERCEL, + client_secret: CLIENT_SECRET_VERCEL, + redirect_uri: `${SITE_URL}/vercel` + } as any) + )).data; + + res = (await axios.post( + INTEGRATION_NETLIFY_TOKEN_URL, + `${"https://api.netlify.com/oauth/token"}?code=${code}&client_id=${CLIENT_ID_NETLIFY}&client_secret=${CLIENT_SECRET_NETLIFY}&grant_type=authorization_code&redirect_uri=${SITE_URL}/netlify` + // INTEGRATION_NETLIFY_TOKEN_URL, + // new URLSearchParams({ + // code: code, + // client_id: CLIENT_ID_NETLIFY, + // client_secret: CLIENT_SECRET_NETLIFY, + // redirect_uri: `${SITE_URL}/netlify` + // } as any) + )); + + console.log('resss', res); + + } catch (err) { + console.error('netlify err', err); + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed OAuth2 code-token exchange with Netlify'); + } + + return ({ + + }); +} + export { exchangeCode } \ No newline at end of file diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index da45336fc..1dd721a11 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -14,7 +14,7 @@ const INTEGRATION_OAUTH2 = 'oauth2'; // integration oauth endpoints const INTEGRATION_HEROKU_TOKEN_URL = 'https://id.heroku.com/oauth/token'; const INTEGRATION_VERCEL_TOKEN_URL = 'https://api.vercel.com/v2/oauth/access_token'; -const INTEGRATION_NETLIFY_TOKEN_URL = 'https://app.netlify.com/authorize'; +const INTEGRATION_NETLIFY_TOKEN_URL = 'https://api.netlify.com/oauth/token'; // integration apps endpoints const INTEGRATION_HEROKU_API_URL = 'https://api.heroku.com'; diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index 864229d72..f8a8e3de0 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -21,6 +21,8 @@ const { } = require('../../components/utilities/cryptography/crypto'); const crypto = require("crypto"); +import axios from "axios"; + export default function Integrations() { const [integrationAuths, setIntegrationAuths] = useState([]); const [integrations, setIntegrations] = useState([]); @@ -115,20 +117,41 @@ export default function Integrations() { 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 csrfToken = crypto.randomBytes(16).toString("hex"); - localStorage.setItem('latestCSRFToken', csrfToken); + 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=${csrfToken}`; + 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=${csrfToken}`; + window.location = `https://vercel.com/integrations/infisical/new?state=${state}`; break; case 'Netlify': - console.log('netlifyyy'); + // 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; } } diff --git a/frontend/pages/netlify.js b/frontend/pages/netlify.js index 06d6ab32b..4217ba54d 100644 --- a/frontend/pages/netlify.js +++ b/frontend/pages/netlify.js @@ -7,6 +7,8 @@ import AuthorizeIntegration from "./api/integrations/authorizeIntegration"; export default function Netlify() { const router = useRouter(); const parsedUrl = queryString.parse(router.asPath.split("?")[1]); + const code = parsedUrl.code; + const state = parsedUrl.state; // modify comment here /** @@ -14,16 +16,19 @@ export default function Netlify() { */ // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(async () => { + console.log('AA'); if (state === localStorage.getItem('latestCSRFToken')) { localStorage.removeItem('latestCSRFToken'); console.log('Netlify', parsedUrl); + // http://localhost:8080/netlify?code=qnG0g_krhklWDpdUqfhU-t1sLeZzYI3gF2d6QVnL-Gc&state=3a78cd3154a9a99ddd4eb5d99dbb3289 + // http://localhost:8080/netlify#access_token=d-78qUAnnSzvlgfG9Y_oUV6_4TQBxLbofImiBbKAjzE&token_type=Bearer&state=5da34fa49e301e9fa1a6e40925694b77 - // await AuthorizeIntegration({ - // workspaceId: localStorage.getItem('projectData.id'), - // code, - // integration: "vercel" - // }); + await AuthorizeIntegration({ + workspaceId: localStorage.getItem('projectData.id'), + code, + integration: "netlify" + }); // router.push("/integrations/" + localStorage.getItem("projectData.id")); } diff --git a/frontend/public/json/cloudIntegrations.json b/frontend/public/json/cloudIntegrations.json index a1679c993..48a525a7e 100644 --- a/frontend/public/json/cloudIntegrations.json +++ b/frontend/public/json/cloudIntegrations.json @@ -21,7 +21,8 @@ "image": "Netlify", "isAvailable": true, "type": "oauth2", - "clientId": "" + "clientId": "frGheMeEzVEUgM5yHLcPeTj9kYJhYvqkR5IkSexOS50", + "redirectURL": "http://localhost:8080/netlify" }, { "name": "Google Cloud Platform",