diff --git a/backend/src/helpers/integration.ts b/backend/src/helpers/integration.ts
index 9aaff9741a..ccfd72a535 100644
--- a/backend/src/helpers/integration.ts
+++ b/backend/src/helpers/integration.ts
@@ -53,12 +53,12 @@ const handleOAuthExchangeHelper = async ({
if (!bot) throw new Error('Bot must be enabled for OAuth2 code-token exchange');
// exchange code for access and refresh tokens
- let res = await exchangeCode({
+ const res = await exchangeCode({
integration,
code
});
- let update: Update = {
+ const update: Update = {
workspace: workspaceId,
integration
}
@@ -138,7 +138,7 @@ const syncIntegrationsHelper = async ({
// to that integration
for await (const integration of integrations) {
// get workspace, environment (shared) secrets
- const secrets = await BotService.getSecrets({
+ const secrets = await BotService.getSecrets({ // issue here?
workspaceId: integration.workspace.toString(),
environment: integration.environment
});
diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts
index e6545a1021..b26bdee740 100644
--- a/backend/src/integrations/sync.ts
+++ b/backend/src/integrations/sync.ts
@@ -288,176 +288,25 @@ const syncSecretsNetlify = async ({
secrets: any;
accessToken: string;
}) => {
- // TODO: Netlify revision in progress
- // try {
- // const getParams = new URLSearchParams({
- // context_name: integration.context,
- // site_id: integration.siteId
- // });
-
- // const res = (await axios.get(
- // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
- // {
- // params: getParams,
- // headers: {
- // Authorization: `Bearer ${accessToken}`
- // }
- // }
- // ))
- // .data
- // .reduce((obj: any, secret: any) => ({
- // ...obj,
- // [secret.key]: secret
- // }), {});
-
- // res.forEach((r: any) => console.log(r));
- // console.log('getParams', getParams);
-
- // interface UpdateNetlifySecret {
- // key: string;
- // context: string;
- // value: string;
- // }
-
- // interface DeleteNetlifySecret {
- // key: string;
- // }
-
- // interface NewNetlifySecretValue {
- // value: string;
- // context: string;
- // }
-
- // interface NewNetlifySecret {
- // key: string;
- // values: NewNetlifySecretValue[];
- // }
+ try {
+
+ interface NetlifyValue {
+ id?: string;
+ context: string; // 'dev' | 'branch-deploy' | 'deploy-preview' | 'production',
+ value: string;
+ }
- // let updateSecrets: UpdateNetlifySecret[] = [];
- // let deleteSecrets: DeleteNetlifySecret[] = [];
- // let newSecrets: NewNetlifySecret[] = [];
+ interface NetlifySecret {
+ key: string;
+ values: NetlifyValue[];
+ }
- // interface NetlifyValue {
- // id: string;
- // value: string;
- // context: string;
- // role: string;
- // }
-
- // // Identify secrets to create - GOOD
- // Object.keys(secrets).map((key) => {
- // if (!(key in res)) {
- // // case: secret does not exist in Netlify -> create secret
- // newSecrets.push({
- // key: key,
- // values: [{
- // value: secrets[key],
- // context: integration.context
- // }]
- // });
- // } else {
- // // case: secret exists in Netlify
-
- // const netlifyContextsSet = new Set (res[key].values.map((netlifyValue: NetlifyValue) => netlifyValue.context));
-
- // // TODO: check context/env.
- // res[key].values.forEach((netlifyValue: NetlifyValue) => {
- // if (netlifyValue.context === integration.context) {
- // // case: Netlify value context matches integration context
- // // TODO: check if value has changed
- // }
- // });
- // }
- // });
-
- // // Identify secrets to update and delete
- // Object.keys(res).map((key) => {
- // if (key in secrets) {
- // // if (res[key] !== secrets[key]) {
- // // // case: secret value has changed
- // // updateSecrets.push({
- // // key: key,
- // // context: integration.context,
- // // value: secrets[key]
- // // });
- // // }
-
- // // TODO: modify check and record of updated secrets
-
- // // case 1: new context added.
- // } else {
- // // case: secret has been deleted
- // deleteSecrets.push({
- // key
- // });
- // }
- // });
+ interface NetlifySecretsRes {
+ [index: string]: NetlifySecret;
+ }
- // const syncParams = new URLSearchParams({
- // site_id: integration.siteId
- // });
-
- // console.log('Netlify newSecrets', newSecrets);
- // newSecrets.forEach(secret => console.log(secret.values));
- // // Sync/push new secrets
- // if (newSecrets.length > 0) {
- // await axios.post(
- // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
- // newSecrets,
- // {
- // params: syncParams,
- // headers: {
- // Authorization: `Bearer ${accessToken}`
- // }
- // }
- // );
- // }
-
- // console.log('Netlify updateSecrets', updateSecrets);
- // // Sync/push updated secrets
- // if (updateSecrets.length > 0) {
-
- // updateSecrets.forEach(async (secret: UpdateNetlifySecret) => {
- // await axios.patch(
- // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
- // {
- // context: secret.context,
- // value: secret.value
- // },
- // {
- // params: syncParams,
- // headers: {
- // Authorization: `Bearer ${accessToken}`
- // }
- // }
- // );
- // });
- // }
-
- // console.log('Netlify deleteSecrets', deleteSecrets);
- // // Delete secrets
- // if (deleteSecrets.length > 0) {
- // deleteSecrets.forEach(async (secret: DeleteNetlifySecret) => {
- // await axios.delete(
- // `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
- // {
- // params: syncParams,
- // headers: {
- // Authorization: `Bearer ${accessToken}`
- // }
- // }
- // );
- // });
- // }
- // } catch (err) {
- // Sentry.setUser(null);
- // Sentry.captureException(err);
- // throw new Error('Failed to sync secrets to Heroku');
- // }
-
- try {
const getParams = new URLSearchParams({
- context_name: integration.context,
+ context_name: 'all', // integration.context or all
site_id: integration.siteId
});
@@ -473,71 +322,94 @@ const syncSecretsNetlify = async ({
.data
.reduce((obj: any, secret: any) => ({
...obj,
- [secret.key]: secret.values[0].value
+ [secret.key]: secret
}), {});
- interface UpdateNetlifySecret {
- key: string;
- context: string;
- value: string;
- }
-
- interface DeleteNetlifySecret {
- key: string;
- }
-
- interface NewNetlifySecretValue {
- value: string;
- context: string;
- }
-
- interface NewNetlifySecret {
- key: string;
- values: NewNetlifySecretValue[];
- }
-
- const updateSecrets: UpdateNetlifySecret[] = [];
- const deleteSecrets: DeleteNetlifySecret[] = [];
- const newSecrets: NewNetlifySecret[] = [];
+ const newSecrets: NetlifySecret[] = []; // createEnvVars
+ const deleteSecrets: string[] = []; // deleteEnvVar
+ const deleteSecretValues: NetlifySecret[] = []; // deleteEnvVarValue
+ const updateSecrets: NetlifySecret[] = []; // setEnvVarValue
- // Identify secrets to create
+ // identify secrets to create and update
Object.keys(secrets).map((key) => {
if (!(key in res)) {
- // case: secret has been created
+ // case: Infisical secret does not exist in Netlify -> create secret
newSecrets.push({
- key: key,
+ key,
values: [{
- value: secrets[key], // include id?
+ value: secrets[key],
context: integration.context
}]
});
- }
- });
-
- // Identify secrets to update and delete
- Object.keys(res).map((key) => {
- if (key in secrets) {
- if (res[key] !== secrets[key]) {
- // case: secret value has changed
+ } else {
+ // case: Infisical secret exists in Netlify
+ const contexts = res[key].values
+ .reduce((obj: any, value: NetlifyValue) => ({
+ ...obj,
+ [value.context]: value
+ }), {});
+
+ if (integration.context in contexts) {
+ // case: Netlify secret value exists in integration context
+ if (secrets[key] !== contexts[integration.context].value) {
+ // case: Infisical and Netlify secret values are different
+ // -> update Netlify secret context and value
+ updateSecrets.push({
+ key,
+ values: [{
+ context: integration.context,
+ value: secrets[key]
+ }]
+ });
+ }
+ } else {
+ // case: Netlify secret value does not exist in integration context
+ // -> add the new Netlify secret context and value
updateSecrets.push({
- key: key,
- context: integration.context,
- value: secrets[key]
+ key,
+ values: [{
+ context: integration.context,
+ value: secrets[key]
+ }]
});
}
- } else {
- // case: secret has been deleted
- deleteSecrets.push({
- key
+ }
+ })
+
+ // identify secrets to delete
+ // TODO: revise (patch case where 1 context was deleted but others still there
+ Object.keys(res).map((key) => {
+ // loop through each key's context
+ if (!(key in secrets)) {
+ // case: Netlify secret does not exist in Infisical
+
+ const numberOfValues = res[key].values.length;
+
+ res[key].values.forEach((value: NetlifyValue) => {
+ if (value.context === integration.context) {
+ if (numberOfValues <= 1) {
+ // case: Netlify secret value has less than 1 context -> delete secret
+ deleteSecrets.push(key);
+ } else {
+ // case: Netlify secret value has more than 1 context -> delete secret value context
+ deleteSecretValues.push({
+ key,
+ values: [{
+ id: value.id,
+ context: integration.context,
+ value: value.value
+ }]
+ });
+ }
+ }
});
}
});
-
+
const syncParams = new URLSearchParams({
site_id: integration.siteId
});
- // Sync/push new secrets
if (newSecrets.length > 0) {
await axios.post(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
@@ -551,15 +423,13 @@ const syncSecretsNetlify = async ({
);
}
- // Sync/push updated secrets
if (updateSecrets.length > 0) {
-
- updateSecrets.forEach(async (secret: UpdateNetlifySecret) => {
+ updateSecrets.forEach(async (secret: NetlifySecret) => {
await axios.patch(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
{
- context: secret.context,
- value: secret.value
+ context: secret.values[0].context,
+ value: secret.values[0].value
},
{
params: syncParams,
@@ -571,11 +441,24 @@ const syncSecretsNetlify = async ({
});
}
- // Delete secrets
if (deleteSecrets.length > 0) {
- deleteSecrets.forEach(async (secret: DeleteNetlifySecret) => {
+ deleteSecrets.forEach(async (key: string) => {
await axios.delete(
- `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
+ `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`,
+ {
+ params: syncParams,
+ headers: {
+ Authorization: `Bearer ${accessToken}`
+ }
+ }
+ );
+ });
+ }
+
+ if (deleteSecretValues.length > 0) {
+ deleteSecretValues.forEach(async (secret: NetlifySecret) => {
+ await axios.delete(
+ `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`,
{
params: syncParams,
headers: {
@@ -585,13 +468,13 @@ const syncSecretsNetlify = async ({
);
});
}
-
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to Heroku');
}
}
+
export {
syncSecrets
diff --git a/backend/src/utils/crypto.ts b/backend/src/utils/crypto.ts
index 585dae51d2..28f96b0cfb 100644
--- a/backend/src/utils/crypto.ts
+++ b/backend/src/utils/crypto.ts
@@ -1,6 +1,7 @@
import nacl from 'tweetnacl';
import util from 'tweetnacl-util';
import AesGCM from './aes-gcm';
+import * as Sentry from '@sentry/node';
/**
* Return new base64, NaCl, public-private key pair.
@@ -47,6 +48,8 @@ const encryptAsymmetric = ({
util.decodeBase64(privateKey)
);
} catch (err) {
+ Sentry.setUser(null);
+ Sentry.captureException(err);
throw new Error('Failed to perform asymmetric encryption');
}
@@ -86,6 +89,8 @@ const decryptAsymmetric = ({
util.decodeBase64(privateKey)
);
} catch (err) {
+ Sentry.setUser(null);
+ Sentry.captureException(err);
throw new Error('Failed to perform asymmetric decryption');
}
@@ -112,6 +117,8 @@ const encryptSymmetric = ({
iv = obj.iv;
tag = obj.tag;
} catch (err) {
+ Sentry.setUser(null);
+ Sentry.captureException(err);
throw new Error('Failed to perform symmetric encryption');
}
@@ -147,6 +154,8 @@ const decryptSymmetric = ({
try {
plaintext = AesGCM.decrypt(ciphertext, iv, tag, key);
} catch (err) {
+ Sentry.setUser(null);
+ Sentry.captureException(err);
throw new Error('Failed to perform symmetric decryption');
}
diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts
index 35c6323894..55ae271a1d 100644
--- a/backend/src/variables/integration.ts
+++ b/backend/src/variables/integration.ts
@@ -51,7 +51,7 @@ const INTEGRATION_OPTIONS = [
name: 'Netlify',
slug: 'netlify',
image: 'Netlify',
- isAvailable: false,
+ isAvailable: true,
type: 'oauth2',
clientId: CLIENT_ID_NETLIFY,
docsLink: ''
diff --git a/docker-compose.yml b/docker-compose.yml
index 206e7afa3f..bd9022cef8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,7 @@ services:
- 80:80
- 443:443
volumes:
- - ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro
+ - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- frontend
- backend
diff --git a/docs/images/integrations-heroku-auth.png b/docs/images/integrations-heroku-auth.png
new file mode 100644
index 0000000000..da9b4cf529
Binary files /dev/null and b/docs/images/integrations-heroku-auth.png differ
diff --git a/docs/images/integrations-heroku.png b/docs/images/integrations-heroku.png
new file mode 100644
index 0000000000..cc225f2860
Binary files /dev/null and b/docs/images/integrations-heroku.png differ
diff --git a/docs/images/integrations-netlify-auth.png b/docs/images/integrations-netlify-auth.png
new file mode 100644
index 0000000000..fe25d7acf2
Binary files /dev/null and b/docs/images/integrations-netlify-auth.png differ
diff --git a/docs/images/integrations-netlify.png b/docs/images/integrations-netlify.png
new file mode 100644
index 0000000000..60261043e3
Binary files /dev/null and b/docs/images/integrations-netlify.png differ
diff --git a/docs/images/integrations-vercel-auth.png b/docs/images/integrations-vercel-auth.png
new file mode 100644
index 0000000000..d8f3d2d185
Binary files /dev/null and b/docs/images/integrations-vercel-auth.png differ
diff --git a/docs/images/integrations-vercel.png b/docs/images/integrations-vercel.png
new file mode 100644
index 0000000000..f3a814c7a3
Binary files /dev/null and b/docs/images/integrations-vercel.png differ
diff --git a/docs/images/integrations.png b/docs/images/integrations.png
new file mode 100644
index 0000000000..88b0c7aec9
Binary files /dev/null and b/docs/images/integrations.png differ
diff --git a/docs/integrations/cloud/heroku.mdx b/docs/integrations/cloud/heroku.mdx
index 5f0debd3e4..e16cd0d540 100644
--- a/docs/integrations/cloud/heroku.mdx
+++ b/docs/integrations/cloud/heroku.mdx
@@ -1,26 +1,29 @@
---
title: "Heroku"
-description: "With this integration, you can automatically sync your secrets to Heroku as soon as you update secrets in Infisical."
---
-## Instructions
+Prerequisites:
-### Step 1: Open the integrations console
+- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
-Open the Infisical Dashboard. Choose the project in which you want to set up the intergation. Go to the integrations tab in the left sidebar.
+## Navigate to your project's integrations tab
-### Step 2: Authenticate with Heroku
+![integrations](../../images/integrations.png)
-Click on "Heroku" tile. Log in if required and provide the necessary permissions to Infisical. You will afterwards be redirected back to the integrations page.
+## Authorize Infisical for Heroku
-Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. With any questions, reach out support@infisical.com.
+Press on the Heroku tile and grant Infisical access to your Heroku account.
-### Step 3: Start integration
+![integrations heroku authorization](../../images/integrations-heroku-auth.png)
-Choose a Heroku App that you want to sync the secrets to, and the Infisical project environment that you want to sync the secrets from. Start the integration.
+
+ If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
+ Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
+
-The integration should now show status 'In Sync'. Every time you edit secrets, they will be automatically pushed to Heroku.
+## Start integration
+
+Select which Infisical environment secrets you want to sync to which Heroku app and press start integration to start syncing secrets to Heroku.
+
+![integrations heroku](../../images/integrations-heroku.png)
-
- If you need to update your integration, you will have to delete the current one and create a new one.
-
diff --git a/docs/integrations/cloud/netlify.mdx b/docs/integrations/cloud/netlify.mdx
new file mode 100644
index 0000000000..e78f013686
--- /dev/null
+++ b/docs/integrations/cloud/netlify.mdx
@@ -0,0 +1,32 @@
+---
+title: "Netlify"
+---
+
+
+ Infisical integrates with Netlify's new environment variable experience. If your site uses Netlify's old environment variable experience, you'll have to upgrade it to the new one to use this integration.
+
+
+Prerequisites:
+
+- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
+
+## Navigate to your project's integrations tab
+
+![integrations](../../images/integrations.png)
+
+## Authorize Infisical for Netlify
+
+Press on the Netlify tile and grant Infisical access to your Netlify account.
+
+![integrations netlify authorization](../../images/integrations-netlify-auth.png)
+
+
+ If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables.
+ Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform.
+
+
+## Start integration
+
+Select which Infisical environment secrets you want to sync to which Netlify app and context. Lastly, press start integration to start syncing secrets to Netlify.
+
+![integrations netlify](../../images/integrations-netlify.png)
\ No newline at end of file
diff --git a/docs/integrations/cloud/vercel.mdx b/docs/integrations/cloud/vercel.mdx
index eb09203b57..59b416c446 100644
--- a/docs/integrations/cloud/vercel.mdx
+++ b/docs/integrations/cloud/vercel.mdx
@@ -2,4 +2,22 @@
title: "Vercel"
---
-Coming soon.
+Prerequisites:
+
+- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
+
+## Navigate to your project's integrations tab
+
+![integrations](../../images/integrations.png)
+
+## Authorize Infisical for Vercel
+
+Press on the Vercel tile and grant Infisical access to your Vercel account.
+
+![integrations vercel authorization](../../images/integrations-vercel-auth.png)
+
+## Start integration
+
+Select which Infisical environment secrets you want to sync to which Vercel app and environment. Lastly, press start integration to start syncing secrets to Vercel.
+
+![integrations vercel](../../images/integrations-vercel.png)
\ No newline at end of file
diff --git a/docs/integrations/overview.mdx b/docs/integrations/overview.mdx
index 676fe79413..fb8a5d6314 100644
--- a/docs/integrations/overview.mdx
+++ b/docs/integrations/overview.mdx
@@ -12,6 +12,8 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
+| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
+| [Netlify](/integrations/cloud/netlify) | Cloud | Available |
| [React](/integrations/frameworks/react) | Framework | Available |
| [Vue](/integrations/frameworks/vue) | Framework | Available |
| [Express](/integrations/frameworks/express) | Framework | Available |
@@ -26,7 +28,6 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [Flask](/integrations/frameworks/flask) | Framework | Available |
| [Laravel](/integrations/frameworks/laravel) | Framework | Available |
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
-| [Vercel](/integrations/cloud/vercel) | Cloud | Coming soon |
| [Render](/integrations/cloud/render) | Cloud | Coming soon |
| [Fly.io](/integrations/cloud/flyio) | Cloud | Coming soon |
| AWS | Cloud | Coming soon |
diff --git a/docs/mint.json b/docs/mint.json
index 19a499fe86..ff00ca83a1 100644
--- a/docs/mint.json
+++ b/docs/mint.json
@@ -133,6 +133,7 @@
"pages": [
"integrations/cloud/heroku",
"integrations/cloud/vercel",
+ "integrations/cloud/netlify",
"integrations/cloud/render",
"integrations/cloud/flyio"
]