From 547555591b72430b8d9eb336ce32dfed464ce46d Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 18 Dec 2022 12:18:50 -0500 Subject: [PATCH] Refactor integrations logic and replace hardcoded client ids with envars --- .env.example | 7 +- backend/environment.d.ts | 8 +- backend/src/config/index.ts | 4 +- backend/src/integrations/exchange.ts | 4 +- backend/src/integrations/refresh.ts | 4 +- .../requireIntegrationAuthorizationAuth.ts | 14 +-- backend/src/routes/integrationAuth.ts | 3 +- docker-compose.dev.yml | 4 + docker-compose.yml | 3 + docs/self-hosting/configuration/envars.mdx | 7 +- .../integrations/CloudIntegration.tsx | 1 + .../components/integrations/Integration.tsx | 2 - frontend/components/utilities/config/index.ts | 8 +- frontend/pages/integrations/[id].js | 25 +++--- frontend/public/data/cloudIntegrations.js | 83 ++++++++++++++++++ .../public/images/integrations/Vercel.png | Bin 0 -> 5758 bytes frontend/public/json/cloudIntegrations.json | 9 -- 17 files changed, 144 insertions(+), 42 deletions(-) create mode 100644 frontend/public/data/cloudIntegrations.js create mode 100644 frontend/public/images/integrations/Vercel.png diff --git a/.env.example b/.env.example index b662b79617..989a285e33 100644 --- a/.env.example +++ b/.env.example @@ -47,7 +47,12 @@ SMTP_PASSWORD= # Integration # Optional only if integration is used -OAUTH_CLIENT_SECRET_HEROKU= +CLIENT_ID_HEROKU= +CLIENT_ID_VERCEL= +CLIENT_ID_NETLIFY= +CLIENT_SECRET_HEROKU= +CLIENT_SECRET_VERCEL= +CLIENT_SECRET_NETLIFY= # Sentry (optional) for monitoring errors SENTRY_DSN= diff --git a/backend/environment.d.ts b/backend/environment.d.ts index 33827fb2b6..853f52e5b2 100644 --- a/backend/environment.d.ts +++ b/backend/environment.d.ts @@ -14,8 +14,12 @@ declare global { JWT_SIGNUP_SECRET: string; MONGO_URL: string; NODE_ENV: 'development' | 'staging' | 'testing' | 'production'; - OAUTH_CLIENT_SECRET_HEROKU: string; - OAUTH_TOKEN_URL_HEROKU: string; + CLIENT_ID_HEROKU: string; + CLIENT_ID_VERCEL: string; + CLIENT_ID_NETLIFY: string; + CLIENT_SECRET_HEROKU: string; + CLIENT_SECRET_VERCEL: string; + CLIENT_SECRET_NETLIFY: string; POSTHOG_HOST: string; POSTHOG_PROJECT_API_KEY: string; PRIVATE_KEY: string; diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index ea5f653a3a..7c23bee4a8 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -10,7 +10,7 @@ const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m'; 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_HEROKU = process.env.CLIENT_SECRET_HEROKU!; 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!; @@ -49,9 +49,9 @@ export { JWT_SIGNUP_SECRET, MONGO_URL, NODE_ENV, - OAUTH_CLIENT_SECRET_HEROKU, CLIENT_ID_VERCEL, CLIENT_ID_NETLIFY, + CLIENT_SECRET_HEROKU, CLIENT_SECRET_VERCEL, CLIENT_SECRET_NETLIFY, POSTHOG_HOST, diff --git a/backend/src/integrations/exchange.ts b/backend/src/integrations/exchange.ts index 5b7bc71ae3..26e6521a12 100644 --- a/backend/src/integrations/exchange.ts +++ b/backend/src/integrations/exchange.ts @@ -11,7 +11,7 @@ import { } from '../variables'; import { SITE_URL, - OAUTH_CLIENT_SECRET_HEROKU, + CLIENT_SECRET_HEROKU, CLIENT_ID_VERCEL, CLIENT_ID_NETLIFY, CLIENT_SECRET_VERCEL, @@ -114,7 +114,7 @@ const exchangeCodeHeroku = async ({ new URLSearchParams({ grant_type: 'authorization_code', code: code, - client_secret: OAUTH_CLIENT_SECRET_HEROKU + client_secret: CLIENT_SECRET_HEROKU } as any) )).data; diff --git a/backend/src/integrations/refresh.ts b/backend/src/integrations/refresh.ts index b19a6a663e..16870944da 100644 --- a/backend/src/integrations/refresh.ts +++ b/backend/src/integrations/refresh.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import * as Sentry from '@sentry/node'; import { INTEGRATION_HEROKU } from '../variables'; import { - OAUTH_CLIENT_SECRET_HEROKU + CLIENT_SECRET_HEROKU } from '../config'; import { INTEGRATION_HEROKU_TOKEN_URL @@ -59,7 +59,7 @@ const exchangeRefreshHeroku = async ({ new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, - client_secret: OAUTH_CLIENT_SECRET_HEROKU + client_secret: CLIENT_SECRET_HEROKU } as any) ); diff --git a/backend/src/middleware/requireIntegrationAuthorizationAuth.ts b/backend/src/middleware/requireIntegrationAuthorizationAuth.ts index 0bf5526541..ed44ffec52 100644 --- a/backend/src/middleware/requireIntegrationAuthorizationAuth.ts +++ b/backend/src/middleware/requireIntegrationAuthorizationAuth.ts @@ -10,14 +10,16 @@ import { validateMembership } from '../helpers/membership'; * @param {Object} obj * @param {String[]} obj.acceptedRoles - accepted workspace roles * @param {String[]} obj.acceptedStatuses - accepted workspace statuses - * @param {Boolean} obj.attachRefresh - whether or not to decrypt and attach integration authorization refresh token onto request + * @param {Boolean} obj.attachAccessToken - whether or not to decrypt and attach integration authorization access token onto request */ const requireIntegrationAuthorizationAuth = ({ acceptedRoles, - acceptedStatuses + acceptedStatuses, + attachAccessToken = true }: { acceptedRoles: string[]; acceptedStatuses: string[]; + attachAccessToken?: boolean; }) => { return async (req: Request, res: Response, next: NextFunction) => { try { @@ -41,9 +43,11 @@ const requireIntegrationAuthorizationAuth = ({ }); req.integrationAuth = integrationAuth; - req.accessToken = await IntegrationService.getIntegrationAuthAccess({ - integrationAuthId: integrationAuth._id.toString() - }); + if (attachAccessToken) { + req.accessToken = await IntegrationService.getIntegrationAuthAccess({ + integrationAuthId: integrationAuth._id.toString() + }); + } return next(); } catch (err) { diff --git a/backend/src/routes/integrationAuth.ts b/backend/src/routes/integrationAuth.ts index 650221f82a..159d319343 100644 --- a/backend/src/routes/integrationAuth.ts +++ b/backend/src/routes/integrationAuth.ts @@ -42,7 +42,8 @@ router.delete( requireAuth, requireIntegrationAuthorizationAuth({ acceptedRoles: [ADMIN, MEMBER], - acceptedStatuses: [GRANTED] + acceptedStatuses: [GRANTED], + attachAccessToken: false }), param('integrationAuthId'), validateRequest, diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 623462d5be..4d6c3fe86f 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -51,8 +51,12 @@ services: env_file: .env environment: - NEXT_PUBLIC_ENV=development + - INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED} + - NEXT_PUBLIC_SITE_URL=${SITE_URL} - NEXT_PUBLIC_STRIPE_PRODUCT_PRO=${STRIPE_PRODUCT_PRO} - NEXT_PUBLIC_STRIPE_PRODUCT_STARTER=${STRIPE_PRODUCT_STARTER} + - NEXT_PUBLIC_CLIENT_ID_HEROKU=${CLIENT_ID_HEROKU} + - NEXT_PUBLIC_CLIENT_ID_NETLIFY=${CLIENT_ID_NETLIFY} networks: - infisical-dev diff --git a/docker-compose.yml b/docker-compose.yml index bd9022cef8..366b512db9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,6 +41,9 @@ services: - INFISICAL_TELEMETRY_ENABLED=${TELEMETRY_ENABLED} - NEXT_PUBLIC_STRIPE_PRODUCT_PRO=${STRIPE_PRODUCT_PRO} - NEXT_PUBLIC_STRIPE_PRODUCT_STARTER=${STRIPE_PRODUCT_STARTER} + - NEXT_PUBLIC_SITE_URL=${SITE_URL} + - NEXT_PUBLIC_CLIENT_ID_HEROKU=${CLIENT_ID_HEROKU} + - NEXT_PUBLIC_CLIENT_ID_NETLIFY=${CLIENT_ID_NETLIFY} networks: - infisical diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index a55efbebc6..9c6697df56 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -28,6 +28,9 @@ Configuring Infisical requires setting some environment variables. There is a fi | `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` | | `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` | | `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` | -| `OAUTH_CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` | -| `OAUTH_TOKEN_URL_HEROKU` | OAuth token URL for Heroku integration | `None` | +| `CLIENT_ID_VERCEL` | OAuth client id for Vercel integration | `None` | +| `CLIENT_ID_NETLIFY` | OAuth client id for Netlify integration | `None` | +| `CLIENT_SECRET_HEROKU` | OAuth client secret for Heroku integration | `None` | +| `CLIENT_SECRET_VERCEL` | OAuth client secret for Vercel integration | `None` | +| `CLIENT_SECRET_NETLIFY` | OAuth client secret for Netlify integration | `None` | | `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` | diff --git a/frontend/components/integrations/CloudIntegration.tsx b/frontend/components/integrations/CloudIntegration.tsx index b9bbb08c8e..f64268dbac 100644 --- a/frontend/components/integrations/CloudIntegration.tsx +++ b/frontend/components/integrations/CloudIntegration.tsx @@ -87,6 +87,7 @@ const CloudIntegration = ({ ) .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" diff --git a/frontend/components/integrations/Integration.tsx b/frontend/components/integrations/Integration.tsx index 8a323336eb..d3fe58d654 100644 --- a/frontend/components/integrations/Integration.tsx +++ b/frontend/components/integrations/Integration.tsx @@ -17,8 +17,6 @@ import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps" import Button from "~/components/basic/buttons/Button"; import ListBox from "~/components/basic/Listbox"; -// TODO: optimize laggy dropdown for app options - interface Integration { app?: string; environment: string; diff --git a/frontend/components/utilities/config/index.ts b/frontend/components/utilities/config/index.ts index d0ffed00c0..256c350a8d 100644 --- a/frontend/components/utilities/config/index.ts +++ b/frontend/components/utilities/config/index.ts @@ -4,6 +4,9 @@ const POSTHOG_HOST = process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com"; const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!; const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!; +const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL!; +const CLIENT_ID_HEROKU = process.env.NEXT_PUBLIC_CLIENT_ID_HEROKU!; +const CLIENT_ID_NETLIFY = process.env.NEXT_PUBLIC_CLIENT_ID_NETLIFY!; export { ENV, @@ -11,4 +14,7 @@ export { POSTHOG_HOST, STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER, -}; + SITE_URL, + CLIENT_ID_HEROKU, + CLIENT_ID_NETLIFY +}; \ No newline at end of file diff --git a/frontend/pages/integrations/[id].js b/frontend/pages/integrations/[id].js index 46aec22204..335fc4d40e 100644 --- a/frontend/pages/integrations/[id].js +++ b/frontend/pages/integrations/[id].js @@ -8,7 +8,8 @@ import FrameworkIntegrationSection from "~/components/integrations/FrameworkInte import CloudIntegrationSection from "~/components/integrations/CloudIntegrationSection"; import IntegrationSection from "~/components/integrations/IntegrationSection"; import frameworkIntegrationOptions from "../../public/json/frameworkIntegrations.json"; -import cloudIntegrationOptions from "../../public/json/cloudIntegrations.json"; +// import cloudIntegrationOptions from "../../public/json/cloudIntegrations.json"; +import { cloudIntegrationOptions } from "../../public/data/cloudIntegrations"; import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations"; import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations"; import getBot from "../api/bot/getBot"; @@ -29,7 +30,7 @@ export default function Integrations() { const [integrations, setIntegrations] = useState([]); const [bot, setBot] = useState(null); const [isActivateBotDialogOpen, setIsActivateBotDialogOpen] = useState(false); - const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true); + // const [isIntegrationAccessTokenDialogOpen, setIntegrationAccessTokenDialogOpen] = useState(true); const [selectedIntegrationOption, setSelectedIntegrationOption] = useState(null); const router = useRouter(); @@ -122,22 +123,20 @@ export default function Integrations() { const state = crypto.randomBytes(16).toString("hex"); localStorage.setItem('latestCSRFToken', state); - // TODO: Add CircleCI, Render, Fly.io - 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}`; + window.location = `https://vercel.com/integrations/infisical-dev/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; - case 'Fly.io': - console.log('fly.io'); - setIntegrationAccessTokenDialogOpen(true); + window.location = `https://app.netlify.com/authorize?client_id=${integrationOption.clientId}&response_type=code&state=${state}&redirect_uri=${integrationOption.redirectURL}`; break; + // case 'Fly.io': + // console.log('fly.io'); + // setIntegrationAccessTokenDialogOpen(true); + // break; } } catch (err) { console.log(err); @@ -163,7 +162,7 @@ export default function Integrations() { } // case: bot is not active -> open modal to activate bot - setIsActivateBotOpen(true); + setIsActivateBotDialogOpen(true); } catch (err) { console.error(err); } @@ -193,13 +192,13 @@ export default function Integrations() { handleBotActivate={handleBotActivate} handleIntegrationOption={handleIntegrationOption} /> - setIntegrationAccessTokenDialogOpen(false)} selectedIntegrationOption={selectedIntegrationOption} handleBotActivate={handleBotActivate} handleIntegrationOption={handleIntegrationOption} - /> + /> */} ((q}>zjEqlV;k`v(**xa%w;1dA1-BZjh zQT#GUIS?f%MhQ_81-nBO$Qog6K!T02+2`MMnVE4_b#+bmOusxosWs#79)s=r*YjG{ z>jV=E)oQie-Q8VTSy`#9Teq%4r*=Pm`m{{%%k;h+oD(>QuQlIhf$m!f?(^g;o%EM7+rpL#}Yl3ltg%Cn8 z@Q=~JKY%aAvq7I`AdDLQe=jdDFHe)@@B|YI3!w=N{PpYCW3V43mjnW+h-@=WKLf;Z zX<}kxOfX7V2x4{c57XPDqBCwzI~k)m<`Nw>wieGRtPtv4p5x+~%Tq*ZB#4W2j1@ij zE}RgChK8!**~~vIVxIXQKYlFk-@h-OJbB{3$3Yn$|LaGO9{Km+Jq{IQl~xZD>FVo- z4IBKUr>Dn1w`tQR5i`h0jgc#Ov8VxeA%xK6`cy@WU~u2Nch4W_4<9}h^Yiom`*yN% z<3?W?5Jhiqum3ef(<&qekAm#elarGdM1jb)5CQ?5q_-c77G{48^0~RWjtzQSU^O6& zZQHi_udP@P2m)~gL{WrTB9}rS0aOA33=0PVV8*|5=Z;udSnvg)b7j&FgaK;-Vc^x( zVvyT(A@737l@K6+uCA^Tn$3nqL@@Yo-@fe&0B%nnff#n|*x|pzt%^#X1(6FOxVk_X z{BU<>W@d6b_;tWa;Mloyr-(`(1(A6nP!?!JL=?b-2M_$2|K`n`nRR>OLb^|T_Uwsd zB@jc9AkI;i>8!{BnH2)kp!CN$AEok(S-*(0zbz2Nu3fvt_U+rPKNAGir?OtP&YTe7 z22mVv9_Z)ya14+v5XAod`~6rVjI3FQ z)CwU;iOP(S5%lr={PWL#CWuKM)FL8ena)yX?sVFl`cf$bxI$C~owCUY{r1~$;>L{| zg-?UF8#Mp*_4WCJ2m{uEYBuks*;PuN5XcoePQN(hrj4!;Y5~`;U-t`QOy_{RbnxIo z8|$!uG;3O2r7n>oP#gT7ok&yM&~YKy#seG+WCO$wnDr4qm`GdP&{5aOMg(Y# zg8<+Tk&h{ubc4Uq-Me@FzuV@`n*~{V-L3Efj*(z4d7jsC%g`~4Kq45R*<(r=f?{@s zvW<4*?)M1Z0gM8K|4wt*N995@iRS`It>z1K-GwB0pAdX!A! zzlA{rhzGv=?mKpcasVc>-+%wzj~(DDX+W0n!~Xt$Z+3R}n&<#+6+%!WxF8JaQN#kj z{PK%n@&)1sU`nwfYuG6tjpBtLMNG785#(0vy6g0xWf3ruk-L=Zz+P8z7Ta1D0z{y5i4YuP$7zOfA!PiN zfg8)`TfJU`nR zCm~Q=a7K3xE)l~dfvFZ1?3nIMlOWMo8CDi!}29UT>n8zHS1)>a`@2vkL`s?Kw4;0^{q z+#<=zlP5*F9Q*3SLVz)-ixotKx`I&%XtOre4H!41HIP6AXU?1vj2nj3J*>j8u)rt; zom+%FE?WaPnyrC^17~-TK{C`8jtUF3LO?g4&Khtlg*#JIQ`&2=6pR}N52b3;nW_~6 ziQu^EJo-Eg>SGLG)~|Pont?@7Sqml{j7eRacGzQ!{d-g=gXbR4(l=NWXz+6k+<*XJ z4NO7+3kwT=Iu)->G_Wls4h5m9RHa3rOwlow2mo6HH;x}a9x4KW!1?2ko%+TK4_tQ5 zHuGhSY13zQLQuLzoHBJIYOSC-tHjuW&Q#qF>$)+$dcFBX(4$dR-OP(_+;9nk&!10x zC-hum3Ytwdg*8IZ70?hQvk;<#;MXMpM&Qip)ab|u7OF7S6xIlVVuPc)e}IWjD=AU7 z3T_yZIog_nN(jdyese}ctG+(>A-Q4jTn2RRQG*b$O^fO#1R-n@qM@rGM}_;4qGNE6 z&~h=Ly3lM9*e!~QxH3Q7hvbIND(F0(QqxUJwam@U3C5ndtbv64kfLKKNGd1$RUrgv zQ>75tB1FT~Pvi>sAw@$sd82<$6(_6;p{uJ)DFXCwa1qU(uzn&}xDP2Bh--D@R#j!L zca5s54;l8GWSW2glAO5_D{$a!kIpKnCjC_*P<-&N>O)*av&Xid$QABGiiWP;g50Au zV|}&zbZBVksp>;te)%Q4MixK-axs3=pgWbHRUOWT4I9Ma!-tjE$aU(i??&}4f^Ol0 z$dvqC3a*wL@UzdWDl_=kY zX`~aWO|}Yl=sX&Vi7WMAmx7ZAR5!Cl2&YqP2e=Q(4nx4)Fy^I2P`XAOADD@|f*cj@ zLkeepeqMK}Va%6MWz#LoWQ$;?(<{li4=EhOWL~bXFvB8LRClom%yilsNVpFv9Gw{I zGe;$=vQ<sA?dM6p_X(t1hxojlI|yRh5L~7=!8J7(du(*;Tr8$-NZ4% zTGF1o=lzh>=-Mo3?Dxv|+PzO%2y4kgP|6kOwHU^7gGH!B{BR*@cR!IU+=sLlotxhr zEGyBOlcJKP6eU}Q=#(g6P^KE#B0MK+zBl(FHKH5Klqh;$S=O~zun?Y+mI`uIxDV+$ zhOykxV!;^5Y9@yJkfOmtFpyPiAmKhFdsqkt($r7n3ilz|!9viGwtga4xDUw=7J`Db z_7l0neMsS;*GRcZKand%??W<-<>%YE5G;f`@|uG$avzdzEK}*AabF|m zIe88)i&}BEnbhGLd8!LNJw2jmWTl_Tm7?|`b$53w&)1KAjuGl8LaMQdR>8_5(6Q+H z;XF|kAG(NrNLCg>)J4wq6v;KpdIdQuMe9StX22>aSpHSMN7J1b<->WTXc?8F^&uI? z^7E{bE?Cx?le|$hYal67ACk_yX>g5}mzO=&O`A4t5=9^n{Y0)5r4LCL?@|rTTYXQ5 zr@E;Ku2Ek4iCig4AChiTUs_u7~=2;AR>nCz0?|n!cH*VBjT3GYM9157H zRd@FG_KLicw|*j5^4^DJ$bL4AdATk8HPxLjz4VgEBfO@!kvwP3bZ!w%{taVZS_DsZ zb6#B|c8S)K*NU^wt7*=b`a*c}dBN^OTf46*Grjdid2WW$eZ{_-o1N*VbThM=FTfi)H(Xt$`TZr-zJk6@TIV@UPabRq!CEgvaPxTrdh0!5KG2WHX@ifB7`?o?hQ zx9Fnjvq~YL%B;&C_4V}$hDjra%;thAJ^)G~1n!YiL0PRSF!^Wi-o486G^vl(uTNH? z-3?n;T*E?#Jg;SDvI)kdVMn`*VV~CuAy~LjQ(fbrnMoVFcI{H0$CAwEMK~(Lo{ymI zJz}fCqy<<4oy$%_I4vwN3W4&j7j%0bWv#%*OUtGmWlf$0=+Sp#4t1SpFXrnL&3L1NlXoYnwXKVFs;Hp;OAlNR>x->_lV;KCJGP%Ql&4v@PhI> z3TKoh`b5M?%N9WrH061!Yxu!*+wd_=Gia#ORgW>omX?-AMa;w|1hij}7~a#x3uuyh z{q@%c!Ws( zz58<#!UuFHD5t^}y8DJF< zH#~m)STNZIlN$QWOuHd=A|`02BSHXy7(UhQh5;agZ@&44#gI{u85$TEFm4o`1`&Xc z2>}GD)hUx05I|F9Efa_dOq;&7J4I@utuaC~Xwsu>(j~+UIyVXH84yI{ z7mAD$uW3j(_&cFz2qM7WPDh$V06J*r#h7$4I_0z05(CiL^T|Y4~F)=1GKq@UlJ!s1aQmoi6X25iBPfyS4{`qL@W_Kwb@cTrI1p0*VJGP)k zuF&77rlziljF5VZP)`)=0I-)5VhFYnZpclx3=i~f09JthHpmJkwzfDaG9?5M1k$7Q z3qNlA85Tq|;QF9l+ExG{jkSR&WKIZ@!NEZk-_F=t2XN=k9Y4dwZQg34!~haxgz(@P zvH~@cVKOTO3Asxj(;wnw6hXi;+_-VW7evvm1k#|0tB?tbvVJ^pgU+R~IOam;g#cXK zB?;UnSPC3PLeBme{865UDg}TitY*~>QjrTGNLmpDEW`Z#yuWTbudb7P9yWY`mq!8P zc?8&sJ;fVGgq zX!pKM^@AQoF1gk_25NL%qMz|X5d?(XbBJsK{zI_H~6gaG(bY z=Wx`&SAl^n2QvmY-gkF*`-2__-XpkeWu{jl-5>gWjAcCy>Pq2+Ai-jwQmJ%|4#W{v zQA}zy_%D(`$BH!jt0=4x>Om|)f~eA&I46qpQiu4O(nr8mXWZ|aS<*|o_-~a+{dKsVr zSAo@_*MYbL?=YmBL$B8`>(gvsqthM_d^cDKF@fNlUSK^cL5j9agLJn{*UDFNQ_4Eg wU~tn+z7V|kf@}_sMGW>}$)hKDz{k%20gAaWf|RxmfB*mh07*qoM6N<$g0^&3dH?_b literal 0 HcmV?d00001 diff --git a/frontend/public/json/cloudIntegrations.json b/frontend/public/json/cloudIntegrations.json index 26ff79fc57..e932879d84 100644 --- a/frontend/public/json/cloudIntegrations.json +++ b/frontend/public/json/cloudIntegrations.json @@ -27,15 +27,6 @@ "redirectURL": "http://localhost:8080/netlify", "docsLink": "" }, - { - "name": "Fly.io", - "slug": "flyio", - "image": "Google Cloud Platform", - "isAvailable": true, - "type": "accessToken", - "clientId": "", - "docsLink": "" - }, { "name": "Google Cloud Platform", "slug": "gcp",