Compare commits

..

124 Commits

Author SHA1 Message Date
Maidul Islam
bf7801f8c1 update service type 2023-03-14 14:35:53 -07:00
Maidul Islam
753e9250d1 rename both servces to have same name 2023-03-14 14:34:19 -07:00
Maidul Islam
6b6b15f3ea add back health check 2023-03-14 13:10:04 -07:00
Maidul Islam
040d93080f update docker file 2023-03-14 12:58:55 -07:00
Maidul Islam
3d32882cef remove root path 2023-03-14 12:43:40 -07:00
Maidul Islam
620c453b12 bring back render.yaml to root 2023-03-14 12:40:17 -07:00
Maidul Islam
4d8d1f721c delete render.yaml from root 2023-03-14 12:38:16 -07:00
Maidul Islam
9e4267a5d8 add to self host folder render 2023-03-14 12:37:19 -07:00
Maidul Islam
40f95e0cc5 disable auto deploy 2023-03-14 12:34:43 -07:00
Maidul Islam
43cc398ef0 remove health check from render 2023-03-14 12:33:10 -07:00
Maidul Islam
e99ea1f573 render: add backend health check 2023-03-14 12:23:07 -07:00
Maidul Islam
034f473e58 add render one click 2023-03-14 12:22:16 -07:00
Maidul Islam
759220263f Rename cf template to .template 2023-03-14 09:02:30 -07:00
Maidul Islam
14e3f62b95 updated userData script for ec2 deploy 2023-03-13 23:52:13 -07:00
Maidul Islam
4d1462624b update template Description 2023-03-11 14:15:42 -08:00
Maidul Islam
51657449af add aws cloudformation 2023-03-11 14:10:31 -08:00
BlackMagiq
d89976802d Merge pull request #424 from Aashish-Upadhyay-101/aashish/example-docs
docs: python docs for example CRUD
2023-03-11 21:53:51 +07:00
Aashish-Upadhyay-101
1090a61162 python create_secrets, update_secrets and delete_secrets docs 2023-03-10 22:45:58 +05:45
Maidul Islam
0e11ff198c Merge pull request #400 from Infisical/snyk-fix-0ab98e0c00b32ecebcd11cb2298f542f
[Snyk] Security upgrade styled-components from 5.3.5 to 5.3.7
2023-03-10 08:46:15 -08:00
BlackMagiq
cdbc6f5619 Merge pull request #423 from Infisical/check-integrations
Patch create integration page on no integration projects and add support for groups in GitLab integration
2023-03-10 21:50:10 +07:00
Tuan Dang
78cb18ad0e Fix lint errors 2023-03-10 21:45:51 +07:00
Aashish-Upadhyay-101
42374a775d python retrieve_secrets docs 2023-03-10 20:29:58 +05:45
Tuan Dang
0269b58a3c Finish support for GitLab groups integration 2023-03-10 21:25:04 +07:00
Maidul Islam
ef4a316558 Update docker.mdx 2023-03-09 16:26:32 -08:00
mv-turtle
a676ce7c21 Update features.mdx 2023-03-09 13:13:38 -08:00
mv-turtle
f475daf7a6 Update README.md 2023-03-08 22:00:28 -08:00
Maidul Islam
c8110c31ef update helm chart with rbac for configmaps 2023-03-08 21:37:04 -08:00
Maidul Islam
a5c8c9c279 add rbac for config 2023-03-08 21:33:29 -08:00
Tuan Dang
5860136494 Patch serviceTokenData workspace string comparison 2023-03-09 12:00:30 +07:00
Tuan Dang
3f3516b7ba Checkpoint GitLab integration group support 2023-03-09 11:52:34 +07:00
mv-turtle
06e26da684 Update README.md 2023-03-08 20:41:16 -08:00
Maidul Islam
bb70ff96d2 Add docs for k8 Global configuration 2023-03-08 20:33:18 -08:00
Maidul Islam
c019d57fb6 allow global defaults for secrets operator 2023-03-08 18:52:05 -08:00
Vladyslav Matsiiako
7854a5eea2 Fix stripe checks 2023-03-08 16:20:47 -08:00
Vladyslav Matsiiako
29636173ef Removed the add to project button for new people 2023-03-08 08:22:31 -08:00
BlackMagiq
4edfc1e0be Merge pull request #411 from Infisical/revised-service-token-docs
Add read/write support for service tokens and update CRUD examples in docs to use service tokens
2023-03-07 15:42:07 +07:00
Tuan Dang
61d4da49aa Merge remote-tracking branch 'origin' into revised-service-token-docs 2023-03-07 15:34:22 +07:00
Tuan Dang
56187ec43e Fix lint errors 2023-03-07 15:33:45 +07:00
Tuan Dang
971ac26033 Fix lint errors 2023-03-07 15:23:10 +07:00
mv-turtle
1f316a0b65 Add SveteKit to the docs sidebar 2023-03-06 21:17:33 -08:00
mv-turtle
23d09c37b5 Merge pull request #407 from jerriclynsjohn/patch-2
Adding sveltekit into the index of Integrations
2023-03-06 21:12:50 -08:00
mv-turtle
fc7c3022be Merge pull request #410 from jerriclynsjohn/add-sveltekit
Adding SvelteKit in the frontend app
2023-03-06 21:12:09 -08:00
Tuan Dang
5b65adedbb Resolve merge conflicts 2023-03-07 11:22:56 +07:00
Maidul Islam
6faf9bf4bf bug fix for https://github.com/Infisical/infisical/issues/403 2023-03-06 11:13:35 -05:00
Jerric Lyns John
b5998d7f22 Adding SvelteKit in the frontend app 2023-03-06 20:30:40 +05:30
Tuan Dang
6abbc1c54d Revise docs for working with CRUD secrets 2023-03-06 18:52:30 +07:00
Maidul Islam
85e5319981 Merge pull request #401 from jon4hz/update-check
Update check
2023-03-05 22:38:06 -05:00
Maidul Islam
50da0a753a Add cli docs for supported environment variables 2023-03-05 22:36:00 -05:00
Jerric Lyns John
6a5f2d0566 Adding sveltekit into the index 2023-03-06 06:44:17 +05:30
mv-turtle
d93277155f Merge pull request #405 from jerriclynsjohn/patch-1
Adding documentation for SvelteKit
2023-03-05 15:56:32 -08:00
Jerric Lyns John
cb905e5ee6 Create sveltekit.mdx 2023-03-06 05:18:53 +05:30
BlackMagiq
71261e7594 Merge pull request #394 from ha-sante/patch-1
Update create-secrets.mdx
2023-03-05 12:34:26 +07:00
BlackMagiq
27e4f490d3 Merge pull request #387 from MatthewJohn/main
Correct port in self-host documentation and simplify downloading nginx config
2023-03-05 12:32:20 +07:00
mv-turtle
298c8705d7 Merge pull request #370 from Neeraj138/login-after-delete-all-projects
Fix: Unable to login after deleting all projects
2023-03-04 21:22:54 -08:00
mv-turtle
edc4382a48 Merge pull request #378 from caioluis/fix/update-pt-br-copies
feat(webui-localization): update and fix pt-BR
2023-03-04 20:59:05 -08:00
mv-turtle
5baab76f2e Merge branch 'main' into fix/update-pt-br-copies 2023-03-04 20:54:32 -08:00
Maidul Islam
f9f30efe03 add integrations to main nav in docs 2023-03-04 19:33:11 -05:00
BlackMagiq
12701bdf98 Merge pull request #402 from Aqib-Rime/update_overview_docs
update AWS and AZURE integrations
2023-03-04 15:01:57 +07:00
Aqib-Rime
70967ac7b0 update AWS and AZURE integrations 2023-03-04 13:52:14 +06:00
Maidul Islam
98b443da82 Merge pull request #380 from jon4hz/tf
docs: add terraform
2023-03-03 21:07:51 -05:00
Maidul Islam
10f75c8e55 add terraform docs 2023-03-03 21:07:18 -05:00
jon4hz
b226642853 fix: add debug log 2023-03-03 23:56:14 +01:00
jon4hz
933f837f64 feat: option to disable update check 2023-03-03 23:52:01 +01:00
jon4hz
7327698305 fix: dont use ioutils and handle error 2023-03-03 23:47:54 +01:00
mv-turtle
1dc59d0d41 Merge pull request #399 from eltociear/add-ja
Add Japanese README.md
2023-03-03 14:43:05 -08:00
snyk-bot
13e067dc4f fix: frontend/package.json & frontend/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-STYLEDCOMPONENTS-3149924
2023-03-03 22:37:03 +00:00
Ikko Eltociear Ashimine
885d348f96 Add Ja link 2023-03-03 21:35:05 +09:00
Ikko Eltociear Ashimine
c71ee77503 Add Japanese README.md 2023-03-03 21:28:59 +09:00
BlackMagiq
14206de926 Merge pull request #398 from Infisical/patch-azure
Add secret recovery for setting deleted secrets for Azure integration
2023-03-03 15:05:41 +07:00
Tuan Dang
c73d64d784 Add secret recovery for setting deleted secrets for Azure integration 2023-03-03 14:59:59 +07:00
mv-turtle
913067f014 Merge pull request #390 from jorgeteixe/main
Feat: add spanish (es) locale
2023-03-02 14:33:58 -08:00
Tuan Dang
ff2ee989d6 Add GitLab and Azure Key Vault docs 2023-03-02 23:44:57 +07:00
Tuan Dang
200cefc1b2 Complete Azure integration 2023-03-02 22:12:56 +07:00
Tuan Dang
721af0f26d Paginate Netlify sites 2023-03-02 14:36:52 +07:00
Maidul Islam
aca6269920 add support link for all major errors 2023-03-02 00:00:31 -05:00
Maidul Islam
a4074c9687 add update Instructions 2023-03-01 23:44:40 -05:00
Maidul Islam
205ec61549 improve export and run command docs 2023-03-01 18:01:09 -05:00
Maidul Islam
0d16f707c2 update export command docs with proejctId flag 2023-03-01 17:40:39 -05:00
Maidul Islam
d3d5ead6ed allow export by explicit projectId 2023-03-01 17:36:02 -05:00
Maidul Islam
1f05d6ea4d update file permissions to be r/w only for owner 2023-03-01 17:22:59 -05:00
Maidul Islam
ff82af8358 remove unused GetAllWorkSpaceConfigsStartingFromCurrentPath method 2023-03-01 17:22:59 -05:00
Maidul Islam
a7da858694 reset cmd also delete secret backups 2023-03-01 17:22:59 -05:00
Maidul Islam
b5c2f6e551 no login override popup when invalid private key 2023-03-01 17:22:59 -05:00
Maidul Islam
77226e0924 check public and private keys before DecryptAsymmetric call 2023-03-01 17:22:59 -05:00
Vladyslav Matsiiako
0cc4286f5f Added notifications for wrong file types when dropping 2023-03-01 11:08:22 -08:00
Vladyslav Matsiiako
99144143ff Added Kubernetes to the integrations list 2023-02-28 20:42:34 -08:00
Vladyslav Matsiiako
efff841121 Updated slack link 2023-02-28 09:44:47 -08:00
BlackMagiq
2f8d914ecb Merge pull request #391 from Aashish-Upadhyay-101/Aashish-Upadhyay-101/GitLab-integration
Feat: GitLab Integration
2023-03-01 00:16:21 +07:00
ha-sante
7dd28a5941 Update create-secrets.mdx
I am making this change to draw your attention to this as it seems that some variables are used wrong.

- Specifically this section:


		util.decodeBase64(encryptedProjectKey),
		util.decodeBase64(encryptedProjectKey.nonce),
		util.decodeBase64(encryptedProjectKey.sender.publicKey),
		util.decodeBase64(PSWD)



- Imported tweetnacl as well so it's easier to understand and for the code to make sense from the get go.
2023-02-28 13:03:32 +00:00
Tuan Dang
a89fccdc1f Add support for Zoho email 2023-02-28 19:02:17 +07:00
Aashish-Upadhyay-101
40ddd3b2a5 remove console.log() i.e used for testing 2023-02-28 10:19:31 +05:45
Aashish-Upadhyay-101
74d17a20a4 axios changes to request 2023-02-28 10:15:09 +05:45
Aashish-Upadhyay-101
d537bd2f58 merge conflict resolve 2023-02-28 09:52:49 +05:45
Aashish-Upadhyay-101
2f045be8a4 missing break statement 2023-02-28 09:37:47 +05:45
Jorge Teixeira
c5ee4810ad add dropdown option for spanish locale 2023-02-28 00:24:12 +01:00
Jorge Teixeira
1dbda5876f add spanish locale files 2023-02-28 00:23:47 +01:00
Maidul Islam
d948923d95 add typescript types to secret versions 2023-02-27 16:32:13 -05:00
Maidul Islam
fb1085744a Merge pull request #389 from Infisical/revert-374-shell
Revert "fix: always execute cmd in subshell"
2023-02-27 14:26:41 -05:00
Maidul Islam
ec22291aca Revert "fix: always execute cmd in subshell" 2023-02-27 14:24:47 -05:00
Matt John
00a07fd27c Correct port in linux selfhost setup 2023-02-27 18:47:59 +00:00
Matt John
ec0e77cc5a Remove unecessary 'cd' during download of nginx config in linux selfhost setup 2023-02-27 18:47:23 +00:00
Maidul Islam
16c49a9626 update slack link in welcome message 2023-02-26 23:21:36 -05:00
Maidul Islam
06ea809d60 change color of bold welcome text 2023-02-26 23:19:28 -05:00
Maidul Islam
12364005c1 improve login welcome message 2023-02-26 22:49:31 -05:00
Maidul Islam
98573e9e05 Dependabot alerts #13 2023-02-26 21:55:34 -05:00
Maidul Islam
c1a4ca6203 Dependabot alerts #19 2023-02-26 21:43:01 -05:00
Maidul Islam
21c2fd8542 address Dependabot alerts #17 2023-02-26 21:40:55 -05:00
Maidul Islam
b27bc8fc1b address dependabot alerts #18 2023-02-26 21:38:44 -05:00
Vladyslav Matsiiako
091115e6ba Merge branch 'main' of https://github.com/Infisical/infisical 2023-02-26 17:51:45 -08:00
Vladyslav Matsiiako
d9c055872d Fixed minor bugs everywhere 2023-02-26 17:51:26 -08:00
Maidul Islam
f73d18ddc7 merge PR 287 2023-02-26 20:03:40 -05:00
Maidul Islam
eb47126f68 merge PR 288 2023-02-26 20:00:37 -05:00
Maidul Islam
4750767268 merge PR 289 2023-02-26 19:53:03 -05:00
Maidul Islam
b0ed772885 merge PR 290 2023-02-26 19:50:22 -05:00
Maidul Islam
7fdab81b5f merge pr 291 2023-02-26 19:46:40 -05:00
Maidul Islam
c17bf13f8c remove sudo for alpine 2023-02-26 19:32:33 -05:00
jon4hz
515e010065 docs: add terraform 2023-02-25 18:09:06 +01:00
Caio Gomes
2c46e8a2dc feat(webui-localization): fix and update pt-br copies 2023-02-25 08:24:27 +00:00
Neeraj138
eebe3c164a Fix: Unable to login after deleting all projects 2023-02-25 01:16:10 +05:30
snyk-bot
d7acd7aef6 fix: upgrade i18next from 22.4.6 to 22.4.9
Snyk has created this PR to upgrade i18next from 22.4.6 to 22.4.9.

See this package in npm:
https://www.npmjs.com/package/i18next

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-02-02 22:37:54 +00:00
snyk-bot
860b8efd7d fix: upgrade axios-auth-refresh from 3.3.3 to 3.3.6
Snyk has created this PR to upgrade axios-auth-refresh from 3.3.3 to 3.3.6.

See this package in npm:
https://www.npmjs.com/package/axios-auth-refresh

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-02-02 22:37:50 +00:00
snyk-bot
6ca3fc5ad2 fix: upgrade @headlessui/react from 1.6.6 to 1.7.7
Snyk has created this PR to upgrade @headlessui/react from 1.6.6 to 1.7.7.

See this package in npm:
https://www.npmjs.com/package/@headlessui/react

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-02-02 22:37:45 +00:00
snyk-bot
189af07ff5 fix: upgrade @stripe/react-stripe-js from 1.10.0 to 1.16.3
Snyk has created this PR to upgrade @stripe/react-stripe-js from 1.10.0 to 1.16.3.

See this package in npm:
https://www.npmjs.com/package/@stripe/react-stripe-js

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-02-02 22:37:38 +00:00
snyk-bot
caf7426f86 fix: upgrade posthog-js from 1.34.0 to 1.39.4
Snyk has created this PR to upgrade posthog-js from 1.34.0 to 1.39.4.

See this package in npm:
https://www.npmjs.com/package/posthog-js

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/53d4ecb6-6cc1-4918-aa73-bf9cae4ffd13?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-02-02 22:37:32 +00:00
206 changed files with 8673 additions and 8441 deletions

View File

@@ -48,10 +48,12 @@ CLIENT_ID_HEROKU=
CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB=
CLIENT_ID_GITLAB=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB=
CLIENT_SECRET_GITLAB=
CLIENT_SLUG_VERCEL=
# Sentry (optional) for monitoring errors

View File

@@ -25,7 +25,7 @@
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
</a>
<a href="https://cloudsmith.io/~infisical/repos/">
<img src="https://img.shields.io/badge/Downloads-36.7k-orange" alt="Cloudsmith downloads" />
<img src="https://img.shields.io/badge/Downloads-45.7k-orange" alt="Cloudsmith downloads" />
</a>
<a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g">
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
@@ -44,6 +44,7 @@
<kbd>[<img title="Turkish" alt="Turkish language" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/tr.svg" width="22">](i18n/README.tr.md)</kbd>
<kbd>[<img title="Bahasa Indonesia" alt="Bahasa Indonesia language" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/id.svg" width="22">](i18n/README.id.md)</kbd>
<kbd>[<img title="Portuguese - Brazil" alt="Portuguese - Brazil" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/br.svg" width="22">](i18n/README.pt-br.md)</kbd>
<kbd>[<img title="Japanese" alt="Japanese language" src="https://cdn.staticaly.com/gh/hjnilsson/country-flags/master/svg/jp.svg" width="22">](i18n/README.ja.md)</kbd>
**[Infisical](https://infisical.com)** is an open source, end-to-end encrypted secret manager which you can use to centralize your API keys and configs. From Infisical, you can then distribute these secrets across your whole development lifecycle - from development to production . It's designed to be simple and take minutes to get going.
@@ -55,14 +56,13 @@
- **[Integrations](https://infisical.com/docs/integrations/overview)** with CI/CD and production infrastructure
- **[Infisical API](https://infisical.com/docs/api-reference/overview/introduction)** - manage secrets via HTTPS requests to the platform
- **[Secret Versioning](https://infisical.com/docs/getting-started/dashboard/versioning)** to view the change history for any secret
- **[Activity Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** to record every action taken in a project
- **[Audit Logs](https://infisical.com/docs/getting-started/dashboard/audit-logs)** to record every action taken in a project
- **[Point-in-time Secrets Recovery](https://infisical.com/docs/getting-started/dashboard/pit-recovery)** for rolling back to any snapshot of your secrets
- **Role-based Access Controls** per environment
- **2FA** (more options coming soon)
- **Smart Security Alerts**
- 🔜 **1-Click Deploy** to AWS
- 🔜 **Automatic Secret Rotation**
- 🔜 **Smart Security Alerts**
- 🔜 **Secrets Rotation**
- 🔜 **Slack & MS Teams** integrations
And more.
@@ -157,7 +157,9 @@ We're currently setting the foundation and building [integrations](https://infis
</a>
</td>
<td align="left" valign="middle">
🔜 CircleCI (https://github.com/Infisical/infisical/issues/91)
<a href="https://infisical.com/docs/integrations/cicd/circleci?ref=github.com">
✔️ CircleCI
</a>
</td>
</tr>
<tr>
@@ -168,7 +170,9 @@ We're currently setting the foundation and building [integrations](https://infis
🔜 Digital Ocean
</td>
<td align="left" valign="middle">
🔜 Azure
<a href="https://infisical.com/docs/integrations/cloud/azure-key-vault?ref=github.com">
✔️ Azure Key Vault
</a>
</td>
</tr>
<tr>

View File

@@ -22,10 +22,12 @@ declare global {
CLIENT_ID_VERCEL: string;
CLIENT_ID_NETLIFY: string;
CLIENT_ID_GITHUB: string;
CLIENT_ID_GITLAB: string;
CLIENT_SECRET_HEROKU: string;
CLIENT_SECRET_VERCEL: string;
CLIENT_SECRET_NETLIFY: string;
CLIENT_SECRET_GITHUB: string;
CLIENT_SECRET_GITLAB: string;
CLIENT_SLUG_VERCEL: string;
POSTHOG_HOST: string;
POSTHOG_PROJECT_API_KEY: string;

9570
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -87,7 +87,7 @@
"@types/supertest": "^2.0.12",
"@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.40.1",
"cross-env": "^7.0.3",
"eslint": "^8.26.0",

File diff suppressed because it is too large Load Diff

View File

@@ -39,8 +39,7 @@ import {
password as v1PasswordRouter,
stripe as v1StripeRouter,
integration as v1IntegrationRouter,
integrationAuth as v1IntegrationAuthRouter,
secretApprovalRequest as v1SecretApprovalRequest
integrationAuth as v1IntegrationAuthRouter
} from './routes/v1';
import {
signup as v2SignupRouter,
@@ -60,7 +59,7 @@ import { healthCheck } from './routes/status';
import { getLogger } from './utils/logger';
import { RouteNotFoundError } from './utils/errors';
import { handleMongoInvalidDataError, requestErrorHandler } from './middleware/requestErrorHandler';
import { requestErrorHandler } from './middleware/requestErrorHandler';
// patch async route params to handle Promise Rejections
patchRouterParam();
@@ -111,7 +110,6 @@ app.use('/api/v1/password', v1PasswordRouter);
app.use('/api/v1/stripe', v1StripeRouter);
app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
app.use('/api/v1/secrets-approval-request', v1SecretApprovalRequest)
// v2 routes
app.use('/api/v2/signup', v2SignupRouter);
@@ -138,9 +136,6 @@ app.use((req, res, next) => {
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
})
// handle mongo validation errors
app.use(handleMongoInvalidDataError);
//* Error Handling Middleware (must be after all routing logic)
app.use(requestErrorHandler)

View File

@@ -17,16 +17,17 @@ const NODE_ENV = process.env.NODE_ENV! || 'production';
const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true;
const LOKI_HOST = process.env.LOKI_HOST || undefined;
const CLIENT_ID_AZURE = process.env.CLIENT_ID_AZURE!;
const TENANT_ID_AZURE = process.env.TENANT_ID_AZURE!;
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!;
const CLIENT_ID_GITLAB = process.env.CLIENT_ID_GITLAB!;
const CLIENT_SECRET_AZURE = process.env.CLIENT_SECRET_AZURE!;
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!;
const CLIENT_SECRET_GITLAB = process.env.CLIENT_SECRET_GITLAB;
const CLIENT_SLUG_VERCEL = process.env.CLIENT_SLUG_VERCEL!;
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
const POSTHOG_PROJECT_API_KEY =
@@ -70,16 +71,17 @@ export {
VERBOSE_ERROR_OUTPUT,
LOKI_HOST,
CLIENT_ID_AZURE,
TENANT_ID_AZURE,
CLIENT_ID_HEROKU,
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_ID_GITLAB,
CLIENT_SECRET_AZURE,
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
CLIENT_SECRET_GITHUB,
CLIENT_SECRET_GITLAB,
CLIENT_SLUG_VERCEL,
POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY,

View File

@@ -14,7 +14,6 @@ import * as stripeController from './stripeController';
import * as userActionController from './userActionController';
import * as userController from './userController';
import * as workspaceController from './workspaceController';
import * as secretApprovalController from './secretApprovalController';
export {
authController,
@@ -32,6 +31,5 @@ export {
stripeController,
userActionController,
userController,
workspaceController,
secretApprovalController
workspaceController
};

View File

@@ -2,13 +2,16 @@ import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Integration,
IntegrationAuth,
Bot
} from '../../models';
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
import { IntegrationService } from '../../services';
import { getApps, revokeAccess } from '../../integrations';
import {
getApps,
getTeams,
revokeAccess
} from '../../integrations';
/***
* Return integration authorization with id [integrationAuthId]
@@ -154,25 +157,54 @@ export const saveIntegrationAccessToken = async (
* @returns
*/
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
let apps;
try {
apps = await getApps({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get integration authorization applications",
});
}
let apps;
try {
const teamId = req.query.teamId as string;
apps = await getApps({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
...teamId && { teamId }
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get integration authorization applications",
});
}
return res.status(200).send({
apps,
});
return res.status(200).send({
apps
});
};
/**
* Return list of teams allowed for integration with integration authorization id [integrationAuthId]
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
let teams;
try {
teams = await getTeams({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get integration authorization teams"
});
}
return res.status(200).send({
teams
});
}
/**
* Delete integration authorization with id [integrationAuthId]
* @param req

View File

@@ -2,10 +2,7 @@ import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Integration,
Workspace,
Bot,
BotKey
Integration
} from '../../models';
import { EventService } from '../../services';
import { eventPushSecrets } from '../../events';
@@ -18,6 +15,7 @@ import { eventPushSecrets } from '../../events';
*/
export const createIntegration = async (req: Request, res: Response) => {
let integration;
try {
const {
integrationAuthId,
@@ -34,19 +32,19 @@ export const createIntegration = async (req: Request, res: Response) => {
// TODO: validate [sourceEnvironment] and [targetEnvironment]
// initialize new integration after saving integration access token
integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment,
isActive,
app,
integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment,
isActive,
app,
appId,
targetEnvironment,
owner,
path,
region,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();
if (integration) {
// trigger event - push secrets

View File

@@ -1,320 +0,0 @@
import { Request, Response } from 'express';
import SecretApprovalRequest, { ApprovalStatus, ChangeType, IApprover, IRequestedChange } from '../../models/secretApprovalRequest';
import { Builder, IBuilder } from "builder-pattern"
import { secretObjectHasRequiredFields, validateSecrets } from '../../helpers/secret';
import _ from 'lodash';
import { SECRET_PERSONAL, SECRET_SHARED } from '../../variables';
import { BadRequestError, ResourceNotFound, UnauthorizedRequestError } from '../../utils/errors';
import { ISecret, Membership, Secret, Workspace } from '../../models';
import mongoose from 'mongoose';
export const createApprovalRequest = async (req: Request, res: Response) => {
const { workspaceId, environment, requestedChanges } = req.body;
// validate workspace
const workspaceFromDB = await Workspace.findById(workspaceId)
if (!workspaceFromDB) {
throw ResourceNotFound()
}
const environmentBelongsToWorkspace = _.some(workspaceFromDB.environments, { slug: environment })
if (!environmentBelongsToWorkspace) {
throw ResourceNotFound()
}
// check for secret duplicates
const hasSecretIdDuplicates = requestedChanges.length !== _.uniqBy(requestedChanges, 'modifiedSecretParentId').length;
if (hasSecretIdDuplicates) {
throw BadRequestError({ message: "Request cannot contain changes for duplicate secrets" })
}
// ensure the workspace has approvers set
if (!workspaceFromDB.approvers.length) {
throw BadRequestError({ message: "There are no designated approvers for this project, you must set approvers first before making a request" })
}
const approverIds = _.compact(_.map(workspaceFromDB.approvers, "userId"))
const approversFormatted: IApprover[] = approverIds.map(id => {
return { "userId": id, status: ApprovalStatus.PENDING }
})
const listOfSecretIdsToModify = _.compact(_.map(requestedChanges, "modifiedSecretParentId"))
// Ensure that the user requesting changes for the set of secrets can indeed interact with said secrets
if (listOfSecretIdsToModify.length > 0) {
await validateSecrets({
userId: req.user._id.toString(),
secretIds: listOfSecretIdsToModify
});
}
const sanitizedRequestedChangesList: IRequestedChange[] = []
requestedChanges.forEach((requestedChange: IRequestedChange) => {
const secretDetailsIsValid = secretObjectHasRequiredFields(requestedChange.modifiedSecretDetails)
if (!secretDetailsIsValid) {
throw BadRequestError({ message: "One or more required fields are missing from your modified secret" })
}
if (!requestedChange.modifiedSecretParentId && (requestedChange.type != ChangeType.DELETE.toString() && requestedChange.type != ChangeType.CREATE.toString())) {
throw BadRequestError({ message: "modifiedSecretParentId can only be empty when secret change type is DELETE or CREATE" })
}
sanitizedRequestedChangesList.push(Builder<IRequestedChange>()
.modifiedSecretParentId(requestedChange.modifiedSecretParentId)
.modifiedSecretDetails(requestedChange.modifiedSecretDetails)
.approvers(approversFormatted)
.type(requestedChange.type).build())
});
const newApprovalRequest = await SecretApprovalRequest.create({
workspace: workspaceId,
requestedByUserId: req.user._id.toString(),
environment: environment,
requestedChanges: sanitizedRequestedChangesList
})
const populatedNewApprovalRequest = await newApprovalRequest.populate(["requestedChanges.modifiedSecretParentId", { path: 'requestedChanges.approvers.userId', select: 'firstName lastName _id' }])
return res.send({ approvalRequest: populatedNewApprovalRequest });
};
export const getAllApprovalRequestsForUser = async (req: Request, res: Response) => {
const approvalRequests = await SecretApprovalRequest.find({
requestedByUserId: req.user._id.toString()
}).populate(["requestedChanges.modifiedSecretParentId", { path: 'requestedChanges.approvers.userId', select: 'firstName lastName _id' }])
.sort({ updatedAt: -1 })
res.send({ approvalRequests: approvalRequests })
}
export const getAllApprovalRequestsThatRequireUserApproval = async (req: Request, res: Response) => {
const approvalRequests = await SecretApprovalRequest.find({
'requestedChanges.approvers.userId': req.user._id.toString()
}).populate(["requestedChanges.modifiedSecretParentId", { path: 'requestedChanges.approvers.userId', select: 'firstName lastName _id' }])
.sort({ updatedAt: -1 })
res.send({ approvalRequests: approvalRequests })
}
export const approveApprovalRequest = async (req: Request, res: Response) => {
const { requestedChangeIds } = req.body;
const { reviewId } = req.params
const approvalRequestFromDB = await SecretApprovalRequest.findById(reviewId)
if (!approvalRequestFromDB) {
throw ResourceNotFound()
}
const requestedChangesFromDB: IRequestedChange[] = approvalRequestFromDB.requestedChanges
const filteredChangesByIds = requestedChangesFromDB.filter(change => requestedChangeIds.includes(change._id.toString()))
if (filteredChangesByIds.length != requestedChangeIds.length) {
throw BadRequestError({ message: "All requestedChangeIds should exist in this approval request" })
}
const changesThatRequireUserApproval = _.filter(filteredChangesByIds, change => {
return _.some(change.approvers, approver => {
return approver.userId.toString() == req.user._id.toString();
});
});
if (!changesThatRequireUserApproval.length) {
throw UnauthorizedRequestError({ message: "Your approval is not required for this review" })
}
if (changesThatRequireUserApproval.length != filteredChangesByIds.length) {
throw BadRequestError({ message: "You may only request to approve changes that require your approval" })
}
changesThatRequireUserApproval.forEach((requestedChange) => {
const overallChangeStatus = requestedChange.status
const currentLoggedInUserId = req.user._id.toString()
if (overallChangeStatus == ApprovalStatus.PENDING.toString()) {
requestedChange.approvers.forEach((approver) => {
if (approver.userId.toString() == currentLoggedInUserId && approver.status == ApprovalStatus.PENDING.toString()) {
approver.status = ApprovalStatus.APPROVED
}
})
let updateOverallStatusToApproved = true
requestedChange.approvers.forEach((approver) => {
if (approver.status != ApprovalStatus.APPROVED.toString()) {
updateOverallStatusToApproved = false
}
})
if (updateOverallStatusToApproved) {
requestedChange.status = ApprovalStatus.APPROVED
}
}
})
const updatedApprovalRequest = await SecretApprovalRequest.findByIdAndUpdate(reviewId, {
requestedChanges: requestedChangesFromDB
}, { new: true }).populate(["requestedChanges.modifiedSecretParentId", { path: 'requestedChanges.approvers.userId', select: 'firstName lastName _id' }])
res.send({ approvalRequest: updatedApprovalRequest })
}
export const rejectApprovalRequest = async (req: Request, res: Response) => {
const { requestedChangeIds } = req.body;
const { reviewId } = req.params
const approvalRequestFromDB = await SecretApprovalRequest.findById(reviewId)
if (!approvalRequestFromDB) {
throw ResourceNotFound()
}
const requestedChangesFromDB: IRequestedChange[] = approvalRequestFromDB.requestedChanges
const filteredChangesByIds = requestedChangesFromDB.filter(change => requestedChangeIds.includes(change._id.toString()))
if (filteredChangesByIds.length != requestedChangeIds.length) {
throw BadRequestError({ message: "All requestedChangeIds should exist in this approval request" })
}
const changesThatRequireUserApproval = _.filter(filteredChangesByIds, change => {
return _.some(change.approvers, approver => {
return approver.userId.toString() == req.user._id.toString();
});
});
if (!changesThatRequireUserApproval.length) {
throw UnauthorizedRequestError({ message: "Your approval is not required for this review" })
}
if (changesThatRequireUserApproval.length != filteredChangesByIds.length) {
throw BadRequestError({ message: "You may only request to reject changes that require your approval" })
}
changesThatRequireUserApproval.forEach((requestedChange) => {
const overallChangeStatus = requestedChange.status
const currentLoggedInUserId = req.user._id.toString()
if (overallChangeStatus == ApprovalStatus.PENDING.toString()) {
requestedChange.approvers.forEach((approver) => {
if (approver.userId.toString() == currentLoggedInUserId && approver.status == ApprovalStatus.PENDING.toString()) {
approver.status = ApprovalStatus.REJECTED
requestedChange.status = ApprovalStatus.REJECTED
}
})
}
})
const updatedApprovalRequest = await SecretApprovalRequest.findByIdAndUpdate(reviewId, {
requestedChanges: requestedChangesFromDB
}, { new: true }).populate(["requestedChanges.modifiedSecretParentId", { path: 'requestedChanges.approvers.userId', select: 'firstName lastName _id' }])
res.send({ approvalRequest: updatedApprovalRequest })
};
export const mergeApprovalRequestSecrets = async (req: Request, res: Response) => {
const { requestedChangeIds } = req.body;
const { reviewId } = req.params
// only the user who requested the set of changes can merge it
const approvalRequestFromDB = await SecretApprovalRequest.findOne({ _id: reviewId, requestedByUserId: req.user._id })
if (!approvalRequestFromDB) {
throw ResourceNotFound()
}
// ensure that this user is a member of this workspace
const membershipDetails = await Membership.find({ user: req.user._id, workspace: approvalRequestFromDB.workspace })
if (!membershipDetails) {
throw UnauthorizedRequestError()
}
// filter not merged, approved, and change ids specified in this request
const filteredChangesToMerge: IRequestedChange[] = approvalRequestFromDB.requestedChanges.filter(change => change.merged == false && change.status == ApprovalStatus.APPROVED && requestedChangeIds.includes(change._id.toString()))
if (filteredChangesToMerge.length != requestedChangeIds.length) {
throw BadRequestError({ message: "One or more changes in this approval is either already merged/not approved or do not exist" })
}
const secretsToCreate: ISecret[] = []
const secretsToUpdate: any[] = []
const secretsIdsToDelete: any[] = []
const secretIdsToModify: any[] = []
filteredChangesToMerge.forEach((requestedChange: any) => {
const overallChangeStatus = requestedChange.status
const currentLoggedInUserId = req.user._id.toString()
if (overallChangeStatus == ApprovalStatus.APPROVED.toString()) {
if (ChangeType.CREATE.toString() == requestedChange.type) {
const modifiedSecret = requestedChange.modifiedSecretDetails.toObject()
secretsToCreate.push({
...modifiedSecret,
user: requestedChange.modifiedSecretDetails.type === SECRET_PERSONAL ? currentLoggedInUserId : undefined,
})
}
if (ChangeType.UPDATE.toString() == requestedChange.type) {
const modifiedSecret = requestedChange.modifiedSecretDetails.toObject()
secretIdsToModify.push(requestedChange.modifiedSecretParentId)
secretsToUpdate.push({
filter: { _id: requestedChange.modifiedSecretParentId },
update: {
$set: {
...modifiedSecret,
user: requestedChange.modifiedSecretDetails.type === SECRET_PERSONAL ? currentLoggedInUserId : undefined,
},
$inc: {
version: 1
}
}
})
}
if (ChangeType.DELETE.toString() == requestedChange.type) {
secretsIdsToDelete.push({
_id: requestedChange.modifiedSecretParentId.toString()
})
}
requestedChange.merged = true
}
})
// ensure all secrets that are to be updated exist
const numSecretsFromDBThatRequireUpdate = await Secret.countDocuments({ _id: { $in: secretIdsToModify } });
const numSecretsFromDBThatRequireDelete = await Secret.countDocuments({ _id: { $in: secretsIdsToDelete } });
if (numSecretsFromDBThatRequireUpdate != secretIdsToModify.length || numSecretsFromDBThatRequireDelete != secretsIdsToDelete.length) {
throw BadRequestError({ message: "You cannot merge changes for secrets that no longer exist" })
}
// Add add CRUD operations into a single list of operations
const allOperationsForBulkWrite: any[] = [];
for (const updateStatement of secretsToUpdate) {
allOperationsForBulkWrite.push({ updateOne: updateStatement });
}
for (const secretId of secretsIdsToDelete) {
allOperationsForBulkWrite.push({ deleteOne: { filter: { _id: secretId } } });
}
for (const createStatement of secretsToCreate) {
allOperationsForBulkWrite.push({ insertOne: { document: createStatement } });
}
// start transaction
const session = await mongoose.startSession();
session.startTransaction();
try {
await Secret.bulkWrite(allOperationsForBulkWrite);
await SecretApprovalRequest.updateOne({ _id: reviewId, 'requestedChanges._id': { $in: requestedChangeIds } },
{ $set: { 'requestedChanges.$.merged': true } })
const updatedApproval = await SecretApprovalRequest.findById(reviewId).populate(["requestedChanges.modifiedSecretParentId", { path: 'requestedChanges.approvers.userId', select: 'firstName lastName _id' }])
res.send(updatedApproval)
} catch (error) {
await session.abortTransaction();
throw error
} finally {
session.endSession();
}
};

View File

@@ -16,8 +16,7 @@ import {
} from "../../helpers/workspace";
import { addMemberships } from "../../helpers/membership";
import { ADMIN } from "../../variables";
import { BadRequestError, ResourceNotFound, UnauthorizedRequestError } from "../../utils/errors";
import _ from "lodash";
/**
* Return public keys of members of workspace with id [workspaceId]
* @param req
@@ -304,112 +303,6 @@ export const getWorkspaceIntegrationAuthorizations = async (
});
};
export const addApproverForWorkspaceAndEnvironment = async (
req: Request,
res: Response
) => {
interface Approver {
environment: string;
userId: string;
}
const { workspaceId } = req.params;
const { approvers }: { approvers: Approver[] } = req.body;
const workspaceFromDB = await Workspace.findById(workspaceId)
if (!workspaceFromDB) {
throw ResourceNotFound()
}
const allAvailableWorkspaceEnvironments = _.map(workspaceFromDB.environments, 'slug');
const environmentsFromApprovers = _.map(approvers, "environment")
const filteredApprovers = environmentsFromApprovers.map(environment => allAvailableWorkspaceEnvironments.includes(environment))
// validate environments
if (filteredApprovers.length != environmentsFromApprovers.length) {
const err = `One or more environments set for approver(s) is invalid`
throw BadRequestError({ message: err })
}
const approverIds = _.map(approvers, "userId")
// validate approvers membership
const approversMemberships = await Membership.find({
workspace: workspaceId,
user: { $in: approverIds }
})
if (!approversMemberships) {
throw ResourceNotFound()
}
if (approversMemberships.length != approverIds.length) {
throw UnauthorizedRequestError({ message: "Approvers must be apart of the workspace they are being added to" })
}
const updatedWorkspace = await Workspace.findByIdAndUpdate(workspaceId,
{
$addToSet: {
approvers: {
$each: approvers,
}
}
}, { new: true })
return res.json(updatedWorkspace)
};
export const removeApproverForWorkspaceAndEnvironment = async (
req: Request,
res: Response
) => {
interface Approver {
environment: string;
userId: string;
}
const { workspaceId } = req.params;
const { approvers }: { approvers: Approver[] } = req.body;
const workspaceFromDB = await Workspace.findById(workspaceId)
if (!workspaceFromDB) {
throw ResourceNotFound()
}
const allAvailableWorkspaceEnvironments = _.map(workspaceFromDB.environments, 'slug');
const environmentsFromApprovers = _.map(approvers, "environment")
const filteredApprovers = environmentsFromApprovers.map(environment => allAvailableWorkspaceEnvironments.includes(environment))
// validate environments
if (filteredApprovers.length != environmentsFromApprovers.length) {
const err = `One or more environments set for approver(s) is invalid`
throw BadRequestError({ message: err })
}
const approverIds = _.map(approvers, "userId")
// validate approvers membership
const approversMemberships = await Membership.find({
workspace: workspaceId,
user: { $in: approverIds }
})
if (!approversMemberships) {
throw ResourceNotFound()
}
if (approversMemberships.length != approverIds.length) {
throw UnauthorizedRequestError({ message: "Approvers must be apart of the workspace they are being added to" })
}
const updatedWorkspace = await Workspace.findByIdAndUpdate(workspaceId, { $pullAll: { approvers: approvers } }, { new: true })
return res.json(updatedWorkspace)
};
/**
* Return service service tokens for workspace [workspaceId] belonging to user
* @param req

View File

@@ -22,7 +22,7 @@ import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } fro
import Tag from '../../models/tag';
import _ from 'lodash';
import {
BatchSecretRequest,
BatchSecretRequest,
BatchSecret
} from '../../types/secret';
@@ -41,13 +41,13 @@ export const batchSecrets = async (req: Request, res: Response) => {
workspaceId: string;
environment: string;
requests: BatchSecretRequest[];
}= req.body;
} = req.body;
const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = [];
const deleteSecrets: Types.ObjectId[] = [];
const actions: IAction[] = [];
requests.forEach((request) => {
switch (request.method) {
case 'POST':
@@ -70,7 +70,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
break;
}
});
// handle create secrets
let createdSecrets: ISecret[] = [];
if (createSecrets.length > 0) {
@@ -109,18 +109,18 @@ export const batchSecrets = async (req: Request, res: Response) => {
});
}
}
// handle update secrets
let updatedSecrets: ISecret[] = [];
if (updateSecrets.length > 0 && req.secrets) {
// construct object containing all secrets
let listedSecretsObj: {
[key: string]: {
[key: string]: {
version: number;
type: string;
}
} = {};
listedSecretsObj = req.secrets.reduce((obj: any, secret: ISecret) => ({
...obj,
[secret._id.toString()]: secret
@@ -140,7 +140,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
}));
await Secret.bulkWrite(updateOperations);
const secretVersions = updateSecrets.map((u) => ({
secret: new Types.ObjectId(u._id),
version: listedSecretsObj[u._id.toString()].version,
@@ -227,7 +227,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
});
}
}
if (actions.length > 0) {
// (EE) create (audit) log
await EELogService.createLog({
@@ -250,7 +250,7 @@ export const batchSecrets = async (req: Request, res: Response) => {
await EESecretService.takeSecretSnapshot({
workspaceId
});
const resObj: { [key: string]: ISecret[] | string[] } = {}
if (createSecrets.length > 0) {
@@ -260,11 +260,11 @@ export const batchSecrets = async (req: Request, res: Response) => {
if (updateSecrets.length > 0) {
resObj['updatedSecrets'] = updatedSecrets;
}
if (deleteSecrets.length > 0) {
resObj['deletedSecrets'] = deleteSecrets.map((d) => d.toString());
}
return res.status(200).send(resObj);
}
@@ -358,9 +358,25 @@ export const createSecrets = async (req: Request, res: Response) => {
tags: string[]
}
const newlyCreatedSecrets = await Secret.insertMany(
listOfSecretsToCreate.map(({
const secretsToInsert: ISecret[] = listOfSecretsToCreate.map(({
type,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
tags
}: secretsToCreateType) => {
return ({
version: 1,
workspace: new Types.ObjectId(workspaceId),
type,
user: type === SECRET_PERSONAL ? req.user : undefined,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
@@ -371,26 +387,10 @@ export const createSecrets = async (req: Request, res: Response) => {
secretCommentIV,
secretCommentTag,
tags
}: secretsToCreateType) => {
return ({
version: 1,
workspace: new Types.ObjectId(workspaceId),
type,
user: type === SECRET_PERSONAL ? req.user : undefined,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
tags
});
})
);
});
})
const newlyCreatedSecrets: ISecret[] = (await Secret.insertMany(secretsToInsert)).map((insertedSecret) => insertedSecret.toObject());
setTimeout(async () => {
// trigger event - push secrets
@@ -953,6 +953,7 @@ export const deleteSecrets = async (req: Request, res: Response) => {
}
}
*/
const channel = getChannelFromUserAgent(req.headers['user-agent'])
const toDelete = req.secrets.map((s: any) => s._id);

View File

@@ -17,7 +17,35 @@ import { ABILITY_READ } from '../../variables/organization';
* @param res
* @returns
*/
export const getServiceTokenData = async (req: Request, res: Response) => res.status(200).json(req.serviceTokenData);
export const getServiceTokenData = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return Infisical Token data'
#swagger.description = 'Return Infisical Token data'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"serviceTokenData": {
"type": "object",
$ref: "#/components/schemas/ServiceTokenData",
"description": "Details of service token"
}
}
}
}
}
}
*/
return res.status(200).json(req.serviceTokenData);
}
/**
* Create new service token data for workspace with id [workspaceId] and
@@ -28,6 +56,7 @@ export const getServiceTokenData = async (req: Request, res: Response) => res.st
*/
export const createServiceTokenData = async (req: Request, res: Response) => {
let serviceToken, serviceTokenData;
try {
const {
name,
@@ -36,7 +65,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
encryptedKey,
iv,
tag,
expiresIn
expiresIn,
permissions
} = req.body;
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_READ)
@@ -59,7 +89,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
secretHash,
encryptedKey,
iv,
tag
tag,
permissions
}).save();
// return service token data without sensitive data

View File

@@ -41,7 +41,7 @@ export const getMe = async (req: Request, res: Response) => {
try {
user = await User
.findById(req.user._id)
.select('+publicKey +encryptedPrivateKey +iv +tag');
.select('+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);

View File

@@ -2,6 +2,7 @@ import jwt from 'jsonwebtoken';
import * as Sentry from '@sentry/node';
import bcrypt from 'bcrypt';
import {
IUser,
User,
ServiceTokenData,
APIKeyData
@@ -148,7 +149,10 @@ const getAuthSTDPayload = async ({
serviceTokenData = await ServiceTokenData
.findById(TOKEN_IDENTIFIER)
.select('+encryptedKey +iv +tag').populate('user');
.select('+encryptedKey +iv +tag')
.populate<{user: IUser}>('user');
if (!serviceTokenData) throw ServiceTokenDataNotFoundError({ message: 'Failed to find service token data' });
} catch (err) {
throw UnauthorizedRequestError({

View File

@@ -229,7 +229,7 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
// access token is expired
const refreshToken = await getIntegrationAuthRefreshHelper({ integrationAuthId });
accessToken = await exchangeRefresh({
integration: integrationAuth.integration,
integrationAuth,
refreshToken
});
}

View File

@@ -267,7 +267,7 @@ const v1PushSecrets = async ({
if (toAdd.length > 0) {
// add secrets
const newSecrets = await Secret.insertMany(
const newSecrets: ISecret[] = (await Secret.insertMany(
toAdd.map((s, idx) => {
const obj: any = {
version: 1,
@@ -294,7 +294,7 @@ const v1PushSecrets = async ({
return obj;
})
);
)).map((insertedSecret) => insertedSecret.toObject());
// (EE) add secret versions for new secrets
EESecretService.addSecretVersions({
@@ -713,27 +713,10 @@ const reformatPullSecrets = ({ secrets }: { secrets: ISecret[] }) => {
return reformatedSecrets;
};
const secretObjectHasRequiredFields = (secretObject: ISecret) => {
if (!secretObject.type ||
!(secretObject.type === SECRET_PERSONAL || secretObject.type === SECRET_SHARED) ||
!secretObject.secretKeyCiphertext ||
!secretObject.secretKeyIV ||
!secretObject.secretKeyTag ||
(typeof secretObject.secretValueCiphertext !== 'string') ||
!secretObject.secretValueIV ||
!secretObject.secretValueTag) {
return false
}
return true
}
export {
validateSecrets,
v1PushSecrets,
v2PushSecrets,
pullSecrets,
reformatPullSecrets,
secretObjectHasRequiredFields
reformatPullSecrets
};

View File

@@ -10,11 +10,13 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_GITLAB_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
@@ -23,26 +25,30 @@ import {
INTEGRATION_TRAVISCI_API_URL,
} from "../variables";
interface App {
name: string;
appId?: string;
owner?: string;
}
/**
* Return list of names of apps for integration named [integration]
* @param {Object} obj
* @param {String} obj.integration - name of integration
* @param {String} obj.accessToken - access token for integration
* @param {String} obj.teamId - (optional) id of team for getting integration apps (used for integrations like GitLab)
* @returns {Object[]} apps - names of integration apps
* @returns {String} apps.name - name of integration app
*/
const getApps = async ({
integrationAuth,
accessToken,
teamId
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
teamId?: string;
}) => {
interface App {
name: string;
appId?: string;
owner?: string;
}
let apps: App[] = [];
try {
@@ -77,6 +83,12 @@ const getApps = async ({
accessToken,
});
break;
case INTEGRATION_GITLAB:
apps = await getAppsGitlab({
accessToken,
teamId
});
break;
case INTEGRATION_RENDER:
apps = await getAppsRender({
accessToken,
@@ -190,21 +202,40 @@ const getAppsVercel = async ({
* @returns {String} apps.name - name of Netlify site
*/
const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
let apps;
const apps: any = [];
try {
const res = (
await request.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
let page = 1;
const perPage = 10;
let hasMorePages = true;
// paginate through all sites
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});
const { data } = await request.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
})
).data;
});
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.site_id
});
});
if (data.length < perPage) {
hasMorePages = false;
}
apps = res.map((a: any) => ({
name: a.name,
appId: a.site_id,
}));
page++;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@@ -217,9 +248,9 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
/**
* Return list of repositories for Github integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for Netlify API
* @returns {Object[]} apps - names of Netlify sites
* @returns {String} apps.name - name of Netlify site
* @param {String} obj.accessToken - access token for Github API
* @returns {Object[]} apps - names of Github sites
* @returns {String} apps.name - name of Github site
*/
const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
let apps;
@@ -401,4 +432,117 @@ const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
return apps;
}
/**
* Return list of repositories for GitLab integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for GitLab API
* @returns {Object[]} apps - names of GitLab sites
* @returns {String} apps.name - name of GitLab site
*/
const getAppsGitlab = async ({
accessToken,
teamId
}: {
accessToken: string;
teamId?: string;
}) => {
const apps: App[] = [];
let page = 1;
const perPage = 10;
let hasMorePages = true;
try {
if (teamId) {
// case: fetch projects for group with id [teamId] in GitLab
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});
const { data } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
{
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
);
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id
});
});
if (data.length < perPage) {
hasMorePages = false;
}
page++;
}
} else {
// case: fetch projects for individual in GitLab
const { id } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/user`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
).data;
while (hasMorePages) {
const params = new URLSearchParams({
page: String(page),
per_page: String(perPage)
});
const { data } = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
{
params,
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
);
data.map((a: any) => {
apps.push({
name: a.name,
appId: a.id
});
});
if (data.length < perPage) {
hasMorePages = false;
}
page++;
}
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get GitLab projects");
}
return apps;
}
export { getApps };

View File

@@ -6,11 +6,13 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL
} from '../variables';
import {
SITE_URL,
@@ -18,11 +20,13 @@ import {
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_ID_GITLAB,
CLIENT_SECRET_AZURE,
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
CLIENT_SECRET_GITHUB
CLIENT_SECRET_GITHUB,
CLIENT_SECRET_GITLAB,
} from '../config';
interface ExchangeCodeAzureResponse {
@@ -66,6 +70,15 @@ interface ExchangeCodeGithubResponse {
token_type: string;
}
interface ExchangeCodeGitlabResponse {
access_token: string;
token_type: string;
expires_in: number;
refresh_token: string;
scope: string;
created_at: number;
}
/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for OAuth2
* code-token exchange for integration named [integration]
@@ -114,6 +127,10 @@ const exchangeCode = async ({
code
});
break;
case INTEGRATION_GITLAB:
obj = await exchangeCodeGitlab({
code
});
}
} catch (err) {
Sentry.setUser(null);
@@ -151,7 +168,7 @@ const exchangeCodeAzure = async ({
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err: any) {
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Azure');
@@ -341,4 +358,53 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
};
};
/**
* Return [accessToken], [accessExpiresAt], and [refreshToken] for Gitlab
* 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 Gitlab API
* @returns {String} obj2.refreshToken - refresh token for Gitlab API
* @returns {Date} obj2.accessExpiresAt - date of expiration for access token
*/
const exchangeCodeGitlab = async ({ code }: { code: string }) => {
let res: ExchangeCodeGitlabResponse;
const accessExpiresAt = new Date();
try {
res = (
await request.post(
INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: CLIENT_ID_GITLAB,
client_secret: CLIENT_SECRET_GITLAB,
redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback`
} as any),
{
headers: {
"Accept-Encoding": "application/json",
}
}
)
).data;
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + res.expires_in
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed OAuth2 code-token exchange with Gitlab');
}
return {
accessToken: res.access_token,
refreshToken: res.refresh_token,
accessExpiresAt
};
}
export { exchangeCode };

View File

@@ -1,6 +1,7 @@
import { exchangeCode } from './exchange';
import { exchangeRefresh } from './refresh';
import { getApps } from './apps';
import { getTeams } from './teams';
import { syncSecrets } from './sync';
import { revokeAccess } from './revoke';
@@ -8,6 +9,7 @@ export {
exchangeCode,
exchangeRefresh,
getApps,
getTeams,
syncSecrets,
revokeAccess
}

View File

@@ -1,16 +1,29 @@
import request from '../config/request';
import * as Sentry from '@sentry/node';
import { INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU } from '../variables';
import {
IIntegrationAuth
} from '../models';
import {
INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU,
INTEGRATION_GITLAB,
} from '../variables';
import {
SITE_URL,
CLIENT_ID_AZURE,
CLIENT_ID_GITLAB,
CLIENT_SECRET_AZURE,
CLIENT_SECRET_HEROKU
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_GITLAB
} from '../config';
import {
INTEGRATION_AZURE_TOKEN_URL,
INTEGRATION_HEROKU_TOKEN_URL
INTEGRATION_HEROKU_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL
} from '../variables';
import {
IntegrationService
} from '../services';
interface RefreshTokenAzureResponse {
token_type: string;
@@ -21,6 +34,23 @@ interface RefreshTokenAzureResponse {
refresh_token: string;
}
interface RefreshTokenHerokuResponse {
access_token: string;
expires_in: number;
refresh_token: string;
token_type: string;
user_id: string;
}
interface RefreshTokenGitLabResponse {
token_type: string;
scope: string;
expires_in: number;
access_token: string;
refresh_token: string;
created_at: number;
}
/**
* Return new access token by exchanging refresh token [refreshToken] for integration
* named [integration]
@@ -29,33 +59,61 @@ interface RefreshTokenAzureResponse {
* @param {String} obj.refreshToken - refresh token to use to get new access token for Heroku
*/
const exchangeRefresh = async ({
integration,
integrationAuth,
refreshToken
}: {
integration: string;
integrationAuth: IIntegrationAuth;
refreshToken: string;
}) => {
let accessToken;
interface TokenDetails {
accessToken: string;
refreshToken: string;
accessExpiresAt: Date;
}
let tokenDetails: TokenDetails;
try {
switch (integration) {
switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT:
accessToken = await exchangeRefreshAzure({
tokenDetails = await exchangeRefreshAzure({
refreshToken
});
break;
case INTEGRATION_HEROKU:
accessToken = await exchangeRefreshHeroku({
tokenDetails = await exchangeRefreshHeroku({
refreshToken
});
break;
case INTEGRATION_GITLAB:
tokenDetails = await exchangeRefreshGitLab({
refreshToken
});
break;
default:
throw new Error('Failed to exchange token for incompatible integration');
}
if (tokenDetails?.accessToken && tokenDetails?.refreshToken && tokenDetails?.accessExpiresAt) {
await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId: null,
accessToken: tokenDetails.accessToken,
accessExpiresAt: tokenDetails.accessExpiresAt
});
await IntegrationService.setIntegrationAuthRefresh({
integrationAuthId: integrationAuth._id.toString(),
refreshToken: tokenDetails.refreshToken
});
}
return tokenDetails.accessToken;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get new OAuth2 access token');
}
return accessToken;
};
/**
@@ -71,7 +129,8 @@ const exchangeRefreshAzure = async ({
refreshToken: string;
}) => {
try {
const res: RefreshTokenAzureResponse = (await request.post(
const accessExpiresAt = new Date();
const { data }: { data: RefreshTokenAzureResponse } = await request.post(
INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({
client_id: CLIENT_ID_AZURE,
@@ -80,9 +139,17 @@ const exchangeRefreshAzure = async ({
grant_type: 'refresh_token',
client_secret: CLIENT_SECRET_AZURE
} as any)
)).data;
);
return res.access_token;
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + data.expires_in
);
return ({
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
@@ -102,10 +169,13 @@ const exchangeRefreshHeroku = async ({
}: {
refreshToken: string;
}) => {
let accessToken;
try {
const res = await request.post(
const accessExpiresAt = new Date();
const {
data
}: {
data: RefreshTokenHerokuResponse
} = await request.post(
INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({
grant_type: 'refresh_token',
@@ -114,14 +184,69 @@ const exchangeRefreshHeroku = async ({
} as any)
);
accessToken = res.data.access_token;
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + data.expires_in
);
return ({
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to refresh OAuth2 access token for Heroku');
}
};
return accessToken;
/**
* Return new access token by exchanging refresh token [refreshToken] for the
* GitLab integration
* @param {Object} obj
* @param {String} obj.refreshToken - refresh token to use to get new access token for GitLab
* @returns
*/
const exchangeRefreshGitLab = async ({
refreshToken
}: {
refreshToken: string;
}) => {
try {
const accessExpiresAt = new Date();
const {
data
}: {
data: RefreshTokenGitLabResponse
} = await request.post(
INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: CLIENT_ID_GITLAB,
client_secret: CLIENT_SECRET_GITLAB,
redirect_uri: `${SITE_URL}/integrations/gitlab/oauth2/callback`
} as any),
{
headers: {
"Accept-Encoding": "application/json",
}
});
accessExpiresAt.setSeconds(
accessExpiresAt.getSeconds() + data.expires_in
);
return ({
accessToken: data.access_token,
refreshToken: data.refresh_token,
accessExpiresAt
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to refresh OAuth2 access token for GitLab');
}
};
export { exchangeRefresh };

View File

@@ -10,7 +10,8 @@ import {
INTEGRATION_HEROKU,
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
} from '../variables';
const revokeAccess = async ({
@@ -32,6 +33,8 @@ const revokeAccess = async ({
break;
case INTEGRATION_GITHUB:
break;
case INTEGRATION_GITLAB:
break;
}
deletedIntegrationAuth = await IntegrationAuth.findOneAndDelete({

View File

@@ -19,11 +19,13 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
INTEGRATION_TRAVISCI,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_GITLAB_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
@@ -110,6 +112,13 @@ const syncSecrets = async ({
accessToken,
});
break;
case INTEGRATION_GITLAB:
await syncSecretsGitLab({
integration,
secrets,
accessToken,
});
break;
case INTEGRATION_RENDER:
await syncSecretsRender({
integration,
@@ -163,7 +172,6 @@ const syncSecretsAzureKeyVault = async ({
accessToken: string;
}) => {
try {
interface GetAzureKeyVaultSecret {
id: string; // secret URI
attributes: {
@@ -186,17 +194,22 @@ const syncSecretsAzureKeyVault = async ({
*/
const paginateAzureKeyVaultSecrets = async (url: string) => {
let result: GetAzureKeyVaultSecret[] = [];
while (url) {
const res = await request.get(url, {
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
});
try {
while (url) {
const res = await request.get(url, {
headers: {
Authorization: `Bearer ${accessToken}`
}
});
result = result.concat(res.data.value);
url = res.data.nextLink;
}
result = result.concat(res.data.value);
url = res.data.nextLink;
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
}
return result;
@@ -212,8 +225,7 @@ const syncSecretsAzureKeyVault = async ({
const azureKeyVaultSecret = await request.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
'Authorization': `Bearer ${accessToken}`
}
});
@@ -259,33 +271,75 @@ const syncSecretsAzureKeyVault = async ({
deleteSecrets.push(res[key]);
}
});
const setSecretAzureKeyVault = async ({
key,
value,
integration,
accessToken
}: {
key: string;
value: string;
integration: IIntegration;
accessToken: string;
}) => {
let isSecretSet = false;
let maxTries = 6;
while (!isSecretSet && maxTries > 0) {
// try to set secret
try {
await request.put(
`${integration.app}/secrets/${key}?api-version=7.3`,
{
value
},
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
isSecretSet = true;
} catch (err) {
const error: any = err;
if (error?.response?.data?.error?.innererror?.code === 'ObjectIsDeletedButRecoverable') {
await request.post(
`${integration.app}/deletedsecrets/${key}/recover?api-version=7.3`, {},
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
await new Promise(resolve => setTimeout(resolve, 10000));
} else {
await new Promise(resolve => setTimeout(resolve, 10000));
maxTries--;
}
}
}
}
// Sync/push set secrets
if (setSecrets.length > 0) {
setSecrets.forEach(async ({ key, value }) => {
await request.put(
`${integration.app}/secrets/${key}?api-version=7.3`,
{
value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
}
);
for await (const setSecret of setSecrets) {
const { key, value } = setSecret;
setSecretAzureKeyVault({
key,
value,
integration,
accessToken
});
}
if (deleteSecrets.length > 0) {
deleteSecrets.forEach(async (secret) => {
await request.delete(`${integration.app}/secrets/${secret.key}?api-version=7.3`, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}
});
for await (const deleteSecret of deleteSecrets) {
const { key } = deleteSecret;
await request.delete(`${integration.app}/secrets/${key}?api-version=7.3`, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
}
} catch (err) {
@@ -1422,7 +1476,98 @@ const syncSecretsTravisCI = async ({
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to sync secrets to TravisCI");
throw new Error("Failed to sync secrets to GitLab");
}
}
/**
* Sync/push [secrets] to GitLab repo with name [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for GitLab integration
*/
const syncSecretsGitLab = async ({
integration,
secrets,
accessToken,
}: {
integration: IIntegration;
secrets: any;
accessToken: string;
}) => {
try {
// get secrets from gitlab
const getSecretsRes = (
await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
},
}
)
).data;
for await (const key of Object.keys(secrets)) {
const existingSecret = getSecretsRes.find((s: any) => s.key == key);
if (!existingSecret) {
await request.post(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
{
key: key,
value: secrets[key],
protected: false,
masked: false,
raw: false,
environment_scope:'*'
},
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept-Encoding": "application/json",
},
}
)
} else {
// udpate secret
await request.put(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}`,
{
...existingSecret,
value: secrets[existingSecret.key]
},
{
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
"Accept-Encoding": "application/json",
},
}
)
}
}
// delete secrets
for await (const sec of getSecretsRes) {
if (!(sec.key in secrets)) {
await request.delete(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}`,
{
headers: {
"Authorization": `Bearer ${accessToken}`,
},
}
);
}
}
}catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to sync secrets to GitLab");
}
}

View File

@@ -0,0 +1,92 @@
import * as Sentry from "@sentry/node";
import {
IIntegrationAuth
} from '../models';
import {
INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL
} from '../variables';
import request from '../config/request';
interface Team {
name: string;
teamId: string;
}
/**
* Return list of teams for integration authorization [integrationAuth]
* @param {Object} obj
* @param {String} obj.integrationAuth - integration authorization to get teams for
* @param {String} obj.accessToken - access token for integration authorization
* @returns {Object[]} teams - teams of integration authorization
* @returns {String} teams.name - name of team
* @returns {String} teams.teamId - id of team
*/
const getTeams = async ({
integrationAuth,
accessToken
}: {
integrationAuth: IIntegrationAuth;
accessToken: string;
}) => {
let teams: Team[] = [];
try {
switch (integrationAuth.integration) {
case INTEGRATION_GITLAB:
teams = await getTeamsGitLab({
accessToken
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get integration teams');
}
return teams;
}
/**
* Return list of teams for GitLab integration
* @param {Object} obj
* @param {String} obj.accessToken - access token for GitLab API
* @returns {Object[]} teams - teams that user is part of in GitLab
* @returns {String} teams.name - name of team
* @returns {String} teams.teamId - id of team
*/
const getTeamsGitLab = async ({
accessToken
}: {
accessToken: string;
}) => {
let teams: Team[] = [];
try {
const res = (await request.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
)).data;
teams = res.map((t: any) => ({
name: t.name,
teamId: t.id
}));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error("Failed to get GitLab integration teams");
}
return teams;
}
export {
getTeams
}

View File

@@ -1,12 +1,12 @@
import { ErrorRequestHandler } from "express";
import * as Sentry from '@sentry/node';
import { InternalServerError, UnauthorizedRequestError, UnprocessableEntityError } from "../utils/errors";
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
import { getLogger } from "../utils/logger";
import RequestError, { LogLevel } from "../utils/requestError";
import { NODE_ENV } from "../config";
import mongoose from "mongoose";
import { TokenExpiredError } from 'jsonwebtoken';
export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | Error, req, res, next) => {
if (res.headersSent) return next();
@@ -34,17 +34,4 @@ export const requestErrorHandler: ErrorRequestHandler = (error: RequestError | E
res.status((<RequestError>error).statusCode).json((<RequestError>error).format(req))
next()
}
export const handleMongoInvalidDataError = (err: any, req: any, res: any, next: any) => {
if (err instanceof mongoose.Error.ValidationError) {
const errors: any = {};
for (const field in err.errors) {
errors[field] = err.errors[field].message;
}
throw UnprocessableEntityError({ message: JSON.stringify(errors) })
} else {
next(err);
}
}

View File

@@ -1,12 +1,14 @@
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { User, ServiceTokenData } from '../models';
import {
validateAuthMode,
getAuthUserPayload,
getAuthSTDPayload,
getAuthAPIKeyPayload
} from '../helpers/auth';
import {
UnauthorizedRequestError
} from '../utils/errors';
declare module 'jsonwebtoken' {
export interface UserIDJwtPayload extends jwt.JwtPayload {
@@ -25,9 +27,11 @@ declare module 'jsonwebtoken' {
* @returns
*/
const requireAuth = ({
acceptedAuthModes = ['jwt']
acceptedAuthModes = ['jwt'],
requiredServiceTokenPermissions = []
}: {
acceptedAuthModes: string[];
requiredServiceTokenPermissions?: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
// validate auth token against accepted auth modes [acceptedAuthModes]
@@ -38,11 +42,22 @@ const requireAuth = ({
});
// attach auth payloads
let serviceTokenData: any;
switch (authTokenType) {
case 'serviceToken':
req.serviceTokenData = await getAuthSTDPayload({
serviceTokenData = await getAuthSTDPayload({
authTokenValue
});
requiredServiceTokenPermissions.forEach((requiredServiceTokenPermission) => {
if (!serviceTokenData.permissions.includes(requiredServiceTokenPermission)) {
return next(UnauthorizedRequestError({ message: 'Failed to authorize service token for endpoint' }));
}
});
req.serviceTokenData = serviceTokenData;
req.user = serviceTokenData?.user;
break;
case 'apiKey':
req.user = await getAuthAPIKeyPayload({

View File

@@ -23,7 +23,7 @@ const requireSecretsAuth = ({
// case: validate 1 secret
secrets = await validateSecrets({
userId: req.user._id.toString(),
secretIds: req.body.secrets.id
secretIds: [req.body.secrets.id]
});
} else if (Array.isArray(req.body.secretIds)) {
secrets = await validateSecrets({

View File

@@ -21,7 +21,7 @@ const requireWorkspaceAuth = ({
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { workspaceId } = req[location];
if (req.user) {
// case: jwt auth
const membership = await validateMembership({
@@ -32,11 +32,11 @@ const requireWorkspaceAuth = ({
req.membership = membership;
}
if (
req.serviceTokenData
&& req.serviceTokenData.workspace !== workspaceId
&& req.serviceTokenData.environment !== req.query.environment
&& req.serviceTokenData.workspace.toString() !== workspaceId
&& req.serviceTokenData.environment !== req.body.environment
) {
next(UnauthorizedRequestError({message: 'Unable to authenticate workspace'}))
}

View File

@@ -7,6 +7,7 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
@@ -31,7 +32,8 @@ export interface IIntegration {
| 'heroku'
| 'vercel'
| 'netlify'
| 'github'
| 'github'
| 'gitlab'
| 'render'
| 'flyio'
| 'circleci'
@@ -60,13 +62,11 @@ const integrationSchema = new Schema<IIntegration>(
default: null,
},
appId: {
// (new)
// id of app in provider
type: String,
default: null,
},
targetEnvironment: {
// (new)
// target environment
type: String,
default: null,
@@ -96,6 +96,7 @@ const integrationSchema = new Schema<IIntegration>(
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,

View File

@@ -7,6 +7,7 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
@@ -16,15 +17,15 @@ import {
export interface IIntegrationAuth {
_id: Types.ObjectId;
workspace: Types.ObjectId;
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'render' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'aws-parameter-store' | 'aws-secret-manager';
integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'aws-parameter-store' | 'aws-secret-manager';
teamId: string;
accountId: string;
refreshCiphertext?: string;
refreshIV?: string;
refreshTag?: string;
accessIdCiphertext?: string; // new
accessIdIV?: string; // new
accessIdTag?: string; // new
accessIdCiphertext?: string;
accessIdIV?: string;
accessIdTag?: string;
accessCiphertext?: string;
accessIV?: string;
accessTag?: string;
@@ -48,6 +49,7 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,

View File

@@ -26,7 +26,7 @@ export interface ISecret {
tags?: string[];
}
export const secretSchema = new Schema<ISecret>(
const secretSchema = new Schema<ISecret>(
{
version: {
type: Number,

View File

@@ -1,28 +1,18 @@
import mongoose, { Schema, model } from 'mongoose';
import Secret, { ISecret, secretSchema } from './secret';
export interface IRequestedChange {
_id: string
userId: mongoose.Types.ObjectId;
status: ApprovalStatus;
modifiedSecretDetails: ISecret,
modifiedSecretParentId: mongoose.Types.ObjectId,
type: string,
approvers: IApprover[]
merged: boolean
}
import Secret, { ISecret } from './secret';
interface ISecretApprovalRequest {
environment: string;
workspace: mongoose.Types.ObjectId;
requestedChanges: IRequestedChange[];
requestedByUserId: mongoose.Types.ObjectId;
secret: mongoose.Types.ObjectId;
requestedChanges: ISecret;
requestedBy: mongoose.Types.ObjectId;
approvers: IApprover[];
status: ApprovalStatus;
timestamp: Date;
requestType: ChangeType;
requestType: RequestType;
requestId: string;
}
export interface IApprover {
interface IApprover {
userId: mongoose.Types.ObjectId;
status: ApprovalStatus;
}
@@ -33,80 +23,54 @@ export enum ApprovalStatus {
REJECTED = 'rejected'
}
export enum ChangeType {
export enum RequestType {
UPDATE = 'update',
DELETE = 'delete',
CREATE = 'create'
}
const approverSchema = new mongoose.Schema({
userId: {
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: false,
required: true
},
status: {
type: String,
enum: [ApprovalStatus],
default: ApprovalStatus.PENDING
}
}, { timestamps: true });
// extend the Secret Schema by taking all but removing _id and version fields
const SecretModificationSchema = new Schema({
...secretSchema.obj,
}, {
_id: false,
});
SecretModificationSchema.remove("version")
const requestedChangeSchema = new mongoose.Schema(
{
_id: { type: mongoose.Schema.Types.ObjectId, auto: true },
modifiedSecretDetails: SecretModificationSchema,
modifiedSecretParentId: { // used to fetch the current version of this secret for comparing
type: mongoose.Schema.Types.ObjectId,
ref: 'Secret'
},
type: {
type: String,
enum: ChangeType,
required: true
},
status: {
type: String,
enum: ApprovalStatus,
default: ApprovalStatus.PENDING // the overall status of the requested change
},
approvers: [approverSchema],
merged: {
type: Boolean,
default: false,
}
},
{ timestamps: true }
);
const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
{
environment: {
type: String, // The secret changes were requested for
secret: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Secret'
},
workspace: {
type: mongoose.Schema.Types.ObjectId, // workspace id of the secret
ref: 'Workspace'
},
requestedChanges: [requestedChangeSchema], // the changes that the requested user wants to make to the existing secret
requestedByUserId: {
requestedChanges: Secret,
requestedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
approvers: [approverSchema],
status: {
type: String,
enum: ApprovalStatus,
default: ApprovalStatus.PENDING
},
timestamp: {
type: Date,
default: Date.now
},
requestType: {
type: String,
enum: RequestType,
required: true
},
requestId: {
type: String,
required: false
}
},
{
@@ -114,8 +78,6 @@ const secretApprovalRequestSchema = new Schema<ISecretApprovalRequest>(
}
);
secretApprovalRequestSchema.index({ 'requestedChanges.approvers.userId': 1 });
const SecretApprovalRequest = model<ISecretApprovalRequest>('secret_approval_request', secretApprovalRequestSchema);
const SecretApprovalRequest = model<ISecretApprovalRequest>('SecretApprovalRequest', secretApprovalRequestSchema);
export default SecretApprovalRequest;

View File

@@ -3,13 +3,14 @@ import { Schema, model, Types } from 'mongoose';
export interface IServiceTokenData {
name: string;
workspace: Types.ObjectId;
environment: string; // TODO: adapt to upcoming environment id
environment: string;
user: Types.ObjectId;
expiresAt: Date;
secretHash: string;
encryptedKey: string;
iv: string;
tag: string;
permissions: string[];
}
const serviceTokenDataSchema = new Schema<IServiceTokenData>(
@@ -51,6 +52,11 @@ const serviceTokenDataSchema = new Schema<IServiceTokenData>(
tag: {
type: String,
select: false
},
permissions: {
type: [String],
enum: ['read', 'write'],
default: ['read']
}
},
{

View File

@@ -1,16 +1,9 @@
import mongoose, { Schema, model, Types } from 'mongoose';
export interface DesignatedApprovers {
environment: string,
approvers: [mongoose.Schema.Types.ObjectId]
}
import { Schema, model, Types } from 'mongoose';
export interface IWorkspace {
_id: Types.ObjectId;
name: string;
organization: Types.ObjectId;
approvers: [DesignatedApprovers];
environments: Array<{
name: string;
slug: string;
@@ -18,16 +11,6 @@ export interface IWorkspace {
autoCapitalization: boolean;
}
const approverSchema = new mongoose.Schema({
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
},
environment: {
type: String
}
}, { _id: false });
const workspaceSchema = new Schema<IWorkspace>({
name: {
type: String,
@@ -37,7 +20,6 @@ const workspaceSchema = new Schema<IWorkspace>({
type: Boolean,
default: true,
},
approvers: [approverSchema],
organization: {
type: Schema.Types.ObjectId,
ref: 'Organization',

View File

@@ -15,7 +15,6 @@ import password from './password';
import stripe from './stripe';
import integration from './integration';
import integrationAuth from './integrationAuth';
import secretApprovalRequest from './secretApprovalsRequest'
export {
signup,
@@ -34,6 +33,5 @@ export {
password,
stripe,
integration,
integrationAuth,
secretApprovalRequest
integrationAuth
};

View File

@@ -1,6 +1,6 @@
import express from 'express';
const router = express.Router();
import { body, param } from 'express-validator';
import { body, param, query } from 'express-validator';
import {
requireAuth,
requireWorkspaceAuth,
@@ -73,10 +73,24 @@ router.get(
acceptedRoles: [ADMIN, MEMBER]
}),
param('integrationAuthId'),
query('teamId'),
validateRequest,
integrationAuthController.getIntegrationAuthApps
);
router.get(
'/:integrationAuthId/teams',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireIntegrationAuthorizationAuth({
acceptedRoles: [ADMIN, MEMBER]
}),
param('integrationAuthId'),
validateRequest,
integrationAuthController.getIntegrationAuthTeams
);
router.delete(
'/:integrationAuthId',
requireAuth({

View File

@@ -1,65 +0,0 @@
import express from 'express';
const router = express.Router();
import { requireAuth, validateRequest } from '../../middleware';
import { secretApprovalController } from '../../controllers/v1';
import { body, param } from 'express-validator';
router.post(
'/',
requireAuth({
acceptedAuthModes: ['jwt']
}),
body('workspaceId').exists(),
body('environment').exists(),
body('requestedChanges').isArray(),
validateRequest,
secretApprovalController.createApprovalRequest
);
router.get(
'/sent',
requireAuth({
acceptedAuthModes: ['jwt']
}),
secretApprovalController.getAllApprovalRequestsForUser
);
router.get(
'/approvals-needed',
requireAuth({
acceptedAuthModes: ['jwt']
}),
secretApprovalController.getAllApprovalRequestsThatRequireUserApproval
);
router.post(
'/:reviewId/approve',
requireAuth({
acceptedAuthModes: ['jwt']
}),
body('requestedChangeIds').isArray(),
validateRequest,
secretApprovalController.approveApprovalRequest
);
router.post(
'/:reviewId/reject',
requireAuth({
acceptedAuthModes: ['jwt']
}),
body('requestedChangeIds').isArray(),
validateRequest,
secretApprovalController.rejectApprovalRequest
);
router.post(
'/:reviewId/merge',
body('requestedChangeIds').isArray(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt']
}),
secretApprovalController.mergeApprovalRequestSecrets
);
export default router;

View File

@@ -36,10 +36,10 @@ router.get(
);
router.get(
'/',
'/',
requireAuth({
acceptedAuthModes: ['jwt']
}),
}),
workspaceController.getWorkspaces
);
@@ -134,34 +134,6 @@ router.get(
workspaceController.getWorkspaceIntegrationAuthorizations
);
router.post(
'/:workspaceId/approvers',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN]
}),
param('workspaceId').exists().trim(),
body("approvers").isArray(),
validateRequest,
workspaceController.addApproverForWorkspaceAndEnvironment
);
router.delete(
'/:workspaceId/approvers',
requireAuth({
acceptedAuthModes: ['jwt']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN]
}),
param('workspaceId').exists().trim(),
body("approvers").isArray(),
validateRequest,
workspaceController.removeApproverForWorkspaceAndEnvironment
);
router.get(
'/:workspaceId/service-tokens', // deprecate
requireAuth({

View File

@@ -22,7 +22,8 @@ import {
router.post(
'/batch',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
requiredServiceTokenPermissions: ['read', 'write']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -99,7 +100,8 @@ router.post(
}),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
requiredServiceTokenPermissions: ['write']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -115,7 +117,8 @@ router.get(
query('tagSlugs'),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken']
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
requiredServiceTokenPermissions: ['read']
}),
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
@@ -154,7 +157,8 @@ router.patch(
}),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
requiredServiceTokenPermissions: ['write']
}),
requireSecretsAuth({
acceptedRoles: [ADMIN, MEMBER]
@@ -182,7 +186,8 @@ router.delete(
.isEmpty(),
validateRequest,
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
acceptedAuthModes: ['jwt', 'apiKey', 'serviceToken'],
requiredServiceTokenPermissions: ['write']
}),
requireSecretsAuth({
acceptedRoles: [ADMIN, MEMBER]
@@ -192,5 +197,3 @@ router.delete(
export default router;

View File

@@ -30,13 +30,22 @@ router.post(
acceptedRoles: [ADMIN, MEMBER],
location: 'body'
}),
body('name').exists().trim(),
body('workspaceId'),
body('environment'),
body('encryptedKey'),
body('iv'),
body('tag'),
body('expiresIn'), // measured in ms
body('name').exists().isString().trim(),
body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(),
body('encryptedKey').exists().isString().trim(),
body('iv').exists().isString().trim(),
body('tag').exists().isString().trim(),
body('expiresIn').exists().isNumeric(), // measured in ms
body('permissions').isArray({ min: 1 }).custom((value: string[]) => {
const allowedPermissions = ['read', 'write'];
const invalidValues = value.filter((v) => !allowedPermissions.includes(v));
if (invalidValues.length > 0) {
throw new Error(`permissions contains invalid values: ${invalidValues.join(', ')}`);
}
return true
}),
validateRequest,
serviceTokenDataController.createServiceTokenData
);

View File

@@ -7,9 +7,6 @@ import {
setIntegrationAuthAccessHelper,
} from '../helpers/integration';
// should sync stuff be here too? Probably.
// TODO: move bot functions to IntegrationService.
/**
* Class to handle integrations
*/

View File

@@ -1,9 +1,16 @@
import nodemailer from 'nodemailer';
import { SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_SECURE } from '../config';
import {
SMTP_HOST,
SMTP_PORT,
SMTP_USERNAME,
SMTP_PASSWORD,
SMTP_SECURE
} from '../config';
import {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL
} from '../variables';
import SMTPConnection from 'nodemailer/lib/smtp-connection';
import * as Sentry from '@sentry/node';
@@ -37,6 +44,12 @@ if (SMTP_SECURE) {
ciphers: 'TLSv1.2'
}
break;
case SMTP_HOST_ZOHOMAIL:
mailOpts.requireTLS = true;
mailOpts.tls = {
ciphers: 'TLSv1.2'
}
break;
default:
if (SMTP_HOST.includes('amazonaws.com')) {
mailOpts.tls = {

View File

@@ -19,15 +19,6 @@ export const MethodNotAllowedError = (error?: Partial<RequestErrorContext>) => n
stack: error?.stack
});
export const UnprocessableEntityError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 422,
type: error?.type ?? 'unprocessable_entity',
message: error?.message ?? 'The server understands the content of the request, but it was unable to process it because it contains invalid data',
context: error?.context,
stack: error?.stack
});
export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 401,
@@ -36,7 +27,7 @@ export const UnauthorizedRequestError = (error?: Partial<RequestErrorContext>) =
context: error?.context,
stack: error?.stack
});
export const ForbiddenRequestError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 403,
@@ -55,15 +46,6 @@ export const BadRequestError = (error?: Partial<RequestErrorContext>) => new Req
stack: error?.stack
});
export const ResourceNotFound = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.INFO,
statusCode: error?.statusCode ?? 404,
type: error?.type ?? 'resource_not_found',
message: error?.message ?? 'The requested resource was not found',
context: error?.context,
stack: error?.stack
});
export const InternalServerError = (error?: Partial<RequestErrorContext>) => new RequestError({
logLevel: error?.logLevel ?? LogLevel.ERROR,
statusCode: error?.statusCode ?? 500,

View File

@@ -13,6 +13,7 @@ import {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
@@ -24,7 +25,9 @@ import {
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_GITLAB_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
@@ -47,7 +50,8 @@ import {
import {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL
} from './smtp';
import { PLAN_STARTER, PLAN_PRO } from './stripe';
import {
@@ -80,6 +84,7 @@ export {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
@@ -91,7 +96,9 @@ export {
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB_TOKEN_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_GITLAB_API_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,
@@ -110,6 +117,7 @@ export {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL,
PLAN_STARTER,
PLAN_PRO,
MFA_METHOD_EMAIL,

View File

@@ -1,12 +1,12 @@
import {
CLIENT_ID_AZURE,
TENANT_ID_AZURE
CLIENT_ID_GITLAB
} from '../config';
import {
CLIENT_ID_HEROKU,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_SLUG_VERCEL,
CLIENT_SLUG_VERCEL
} from "../config";
// integrations
@@ -17,6 +17,7 @@ const INTEGRATION_HEROKU = "heroku";
const INTEGRATION_VERCEL = "vercel";
const INTEGRATION_NETLIFY = "netlify";
const INTEGRATION_GITHUB = "github";
const INTEGRATION_GITLAB = "gitlab";
const INTEGRATION_RENDER = "render";
const INTEGRATION_FLYIO = "flyio";
const INTEGRATION_CIRCLECI = "circleci";
@@ -27,6 +28,7 @@ const INTEGRATION_SET = new Set([
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
@@ -37,16 +39,19 @@ const INTEGRATION_SET = new Set([
const INTEGRATION_OAUTH2 = "oauth2";
// integration oauth endpoints
const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/${TENANT_ID_AZURE}/oauth2/v2.0/token`;
const INTEGRATION_AZURE_TOKEN_URL = `https://login.microsoftonline.com/common/oauth2/v2.0/token`;
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://api.netlify.com/oauth/token";
const INTEGRATION_GITHUB_TOKEN_URL =
"https://github.com/login/oauth/access_token";
const INTEGRATION_GITLAB_TOKEN_URL = "https://gitlab.com/oauth/token";
// integration apps endpoints
const INTEGRATION_HEROKU_API_URL = "https://api.heroku.com";
const INTEGRATION_GITLAB_API_URL = "https://gitlab.com/api";
const INTEGRATION_VERCEL_API_URL = "https://api.vercel.com";
const INTEGRATION_NETLIFY_API_URL = "https://api.netlify.com";
const INTEGRATION_RENDER_API_URL = "https://api.render.com";
@@ -54,6 +59,7 @@ const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com";
// TODO: deprecate types?
const INTEGRATION_OPTIONS = [
{
name: 'Heroku',
@@ -128,6 +134,15 @@ const INTEGRATION_OPTIONS = [
clientId: '',
docsLink: ''
},
{
name: 'Azure Key Vault',
slug: 'azure-key-vault',
image: 'Microsoft Azure.png',
isAvailable: true,
type: 'oauth',
clientId: CLIENT_ID_AZURE,
docsLink: ''
},
{
name: 'Circle CI',
slug: 'circleci',
@@ -137,6 +152,15 @@ const INTEGRATION_OPTIONS = [
clientId: '',
docsLink: ''
},
{
name: 'GitLab',
slug: 'gitlab',
image: 'GitLab.png',
isAvailable: true,
type: 'custom',
clientId: CLIENT_ID_GITLAB,
docsLink: ''
},
{
name: 'Travis CI',
slug: 'travisci',
@@ -146,16 +170,6 @@ const INTEGRATION_OPTIONS = [
clientId: '',
docsLink: ''
},
{
name: 'Azure Key Vault',
slug: 'azure-key-vault',
image: 'Microsoft Azure.png',
isAvailable: false,
type: 'oauth',
clientId: CLIENT_ID_AZURE,
tenantId: TENANT_ID_AZURE,
docsLink: ''
},
{
name: 'Google Cloud Platform',
slug: 'gcp',
@@ -175,6 +189,7 @@ export {
INTEGRATION_VERCEL,
INTEGRATION_NETLIFY,
INTEGRATION_GITHUB,
INTEGRATION_GITLAB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI,
@@ -186,7 +201,9 @@ export {
INTEGRATION_VERCEL_TOKEN_URL,
INTEGRATION_NETLIFY_TOKEN_URL,
INTEGRATION_GITHUB_TOKEN_URL,
INTEGRATION_GITLAB_API_URL,
INTEGRATION_HEROKU_API_URL,
INTEGRATION_GITLAB_TOKEN_URL,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_NETLIFY_API_URL,
INTEGRATION_RENDER_API_URL,

View File

@@ -1,9 +1,11 @@
const SMTP_HOST_SENDGRID = 'smtp.sendgrid.net';
const SMTP_HOST_MAILGUN = 'smtp.mailgun.org';
const SMTP_HOST_SOCKETLABS = 'smtp.socketlabs.com';
const SMTP_HOST_ZOHOMAIL = 'smtp.zoho.com';
export {
SMTP_HOST_SENDGRID,
SMTP_HOST_MAILGUN,
SMTP_HOST_SOCKETLABS
SMTP_HOST_SOCKETLABS,
SMTP_HOST_ZOHOMAIL
}

View File

@@ -197,6 +197,23 @@ const generateOpenAPISpec = async () => {
secretValueCiphertext: '',
secretValueIV: '',
secretValueTag: '',
},
ServiceTokenData: {
_id: '',
name: '',
workspace: '',
environment: '',
user: {
_id: '',
firstName: '',
lastName: ''
},
expiresAt: '2023-01-13T14:16:12.210Z',
encryptedKey: '',
iv: '',
tag: '',
updatedAt: '2023-01-13T14:16:12.210Z',
createdAt: '2023-01-13T14:16:12.210Z'
}
}
};

View File

@@ -7,7 +7,7 @@ require (
github.com/muesli/mango-cobra v1.2.0
github.com/muesli/roff v0.1.0
github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.6.0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
golang.org/x/term v0.5.0
)
@@ -31,7 +31,7 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
go.mongodb.org/mongo-driver v1.10.0 // indirect
golang.org/x/net v0.6.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
)

View File

@@ -103,17 +103,13 @@ github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgk
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -125,13 +121,9 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@@ -49,6 +49,11 @@ var exportCmd = &cobra.Command{
util.HandleError(err)
}
projectId, err := cmd.Flags().GetString("projectId")
if err != nil {
util.HandleError(err)
}
format, err := cmd.Flags().GetString("format")
if err != nil {
util.HandleError(err)
@@ -69,7 +74,7 @@ var exportCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs})
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken, TagSlugs: tagSlugs, WorkspaceId: projectId})
if err != nil {
util.HandleError(err, "Unable to fetch secrets")
}
@@ -106,6 +111,7 @@ func init() {
exportCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
exportCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs")
exportCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets from")
}
// Format according to the format flag

View File

@@ -6,7 +6,6 @@ package cmd
import (
"encoding/json"
"fmt"
"os"
"github.com/Infisical/infisical-merge/packages/api"
"github.com/Infisical/infisical-merge/packages/models"
@@ -96,7 +95,7 @@ func writeWorkspaceFile(selectedWorkspace models.Workspace) error {
return err
}
err = util.WriteToFile(util.INFISICAL_WORKSPACE_CONFIG_FILE_NAME, marshalledWorkspaceFile, os.ModePerm)
err = util.WriteToFile(util.INFISICAL_WORKSPACE_CONFIG_FILE_NAME, marshalledWorkspaceFile, 0600)
if err != nil {
return err
}

View File

@@ -41,13 +41,14 @@ var loginCmd = &cobra.Command{
PreRun: toggleDebug,
Run: func(cmd *cobra.Command, args []string) {
currentLoggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
if err != nil && (strings.Contains(err.Error(), "The specified item could not be found in the keyring") || strings.Contains(err.Error(), "unable to get key from Keyring")) { // if the key can't be found allow them to override
// if the key can't be found or there is an error getting current credentials from key ring, allow them to override
if err != nil && (strings.Contains(err.Error(), "The specified item could not be found in the keyring") || strings.Contains(err.Error(), "unable to get key from Keyring") || strings.Contains(err.Error(), "GetUserCredsFromKeyRing")) {
log.Debug(err)
} else if err != nil {
util.HandleError(err)
}
if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired { // if you are logged in but not expired
if currentLoggedInUserDetails.IsUserLoggedIn && !currentLoggedInUserDetails.LoginExpired && len(currentLoggedInUserDetails.UserCredentials.PrivateKey) != 0 {
shouldOverride, err := shouldOverrideLoginPrompt(currentLoggedInUserDetails.UserCredentials.Email)
if err != nil {
util.HandleError(err)
@@ -234,8 +235,16 @@ var loginCmd = &cobra.Command{
// clear backed up secrets from prev account
util.DeleteBackupSecrets()
color.Green("Nice! You are logged in as: %v", email)
whilte := color.New(color.FgGreen)
boldWhite := whilte.Add(color.Bold)
boldWhite.Printf(">>>> Welcome to Infisical!")
boldWhite.Printf(" You are now logged in as %v <<<< \n", email)
plainBold := color.New(color.Bold)
plainBold.Println("\nQuick links")
fmt.Println("- Learn to inject secrets into your application at https://infisical.com/docs/cli/usage")
fmt.Println("- Stuck? Join our slack for quick support https://infisical.com/slack")
},
}

View File

@@ -36,6 +36,9 @@ var resetCmd = &cobra.Command{
keyringInstance.Remove(util.KEYRING_SERVICE_NAME)
// delete secrets backup
util.DeleteBackupSecrets()
util.PrintSuccessMessage("Reset successful")
},
}

View File

@@ -186,14 +186,11 @@ func init() {
// Will execute a single command and pass in the given secrets into the process
func executeSingleCommandWithEnvs(args []string, secretsCount int, env []string) error {
shell := subShellCmd()
command := args[0]
argsForCommand := args[1:]
color.Green("Injecting %v Infisical secrets into your application process", secretsCount)
args = append(args[:1], args[0:]...) // shift args to the right
args[0] = shell[1]
cmd := exec.Command(shell[0], args...)
cmd := exec.Command(command, argsForCommand...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -203,7 +200,15 @@ func executeSingleCommandWithEnvs(args []string, secretsCount int, env []string)
}
func executeMultipleCommandWithEnvs(fullCommand string, secretsCount int, env []string) error {
shell := subShellCmd()
shell := [2]string{"sh", "-c"}
if runtime.GOOS == "windows" {
shell = [2]string{"cmd", "/C"}
} else {
currentShell := os.Getenv("SHELL")
if currentShell != "" {
shell[0] = currentShell
}
}
cmd := exec.Command(shell[0], shell[1], fullCommand)
cmd.Stdin = os.Stdin
@@ -217,23 +222,6 @@ func executeMultipleCommandWithEnvs(fullCommand string, secretsCount int, env []
return execCmd(cmd)
}
func subShellCmd() [2]string {
// default to sh -c
shell := [...]string{"sh", "-c"}
currentShell := os.Getenv("SHELL")
if currentShell != "" {
shell[0] = currentShell
} else if runtime.GOOS == "windows" {
// if the SHELL env var is not set and we're on Windows, use cmd.exe
// The SHELL var should always be checked first, in case the user executes
// infisical from something like Git Bash.
return [...]string{"cmd", "/C"}
}
return shell
}
// Credit: inspired by AWS Valut
func execCmd(cmd *exec.Cmd) error {
sigChannel := make(chan os.Signal, 1)

View File

@@ -19,6 +19,7 @@ import (
"github.com/Infisical/infisical-merge/packages/util"
"github.com/Infisical/infisical-merge/packages/visualize"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
@@ -132,6 +133,11 @@ var secretsSetCmd = &cobra.Command{
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
log.Debugf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
util.PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
}
// decrypt workspace key
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)

View File

@@ -55,4 +55,5 @@ type GetAllSecretsParameters struct {
EnvironmentPassedViaFlag bool
InfisicalToken string
TagSlugs string
WorkspaceId string
}

View File

@@ -4,18 +4,47 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
log "github.com/sirupsen/logrus"
"io"
"net/http"
"os"
"os/exec"
"runtime"
"github.com/fatih/color"
)
func CheckForUpdate() {
if checkEnv := os.Getenv("INFISICAL_DISABLE_UPDATE_CHECK"); checkEnv != "" {
return
}
latestVersion, err := getLatestTag("Infisical", "infisical")
if err != nil {
log.Debug(err)
// do nothing and continue
return
}
if latestVersion != CLI_VERSION {
PrintWarning(fmt.Sprintf("Please update your CLI. You are running version %s but the latest version is %s", CLI_VERSION, latestVersion))
yellow := color.New(color.FgYellow).SprintFunc()
blue := color.New(color.FgCyan).SprintFunc()
black := color.New(color.FgBlack).SprintFunc()
msg := fmt.Sprintf("%s %s %s %s",
yellow("A new release of infisical is available:"),
blue(CLI_VERSION),
black("->"),
blue(latestVersion),
)
fmt.Fprintln(os.Stderr, msg)
updateInstructions := GetUpdateInstructions()
if updateInstructions != "" {
msg = fmt.Sprintf("\n%s\n", GetUpdateInstructions())
fmt.Fprintln(os.Stderr, msg)
}
}
}
@@ -26,12 +55,12 @@ func getLatestTag(repoOwner string, repoName string) (string, error) {
return "", err
}
if resp.StatusCode != 200 {
return "", errors.New(fmt.Sprintf("GitHub API returned status code %d", resp.StatusCode))
return "", errors.New(fmt.Sprintf("gitHub API returned status code %d", resp.StatusCode))
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
@@ -40,7 +69,59 @@ func getLatestTag(repoOwner string, repoName string) (string, error) {
Name string `json:"name"`
}
json.Unmarshal(body, &tags)
if err := json.Unmarshal(body, &tags); err != nil {
return "", fmt.Errorf("failed to unmarshal github response: %w", err)
}
return tags[0].Name[1:], nil
}
func GetUpdateInstructions() string {
os := runtime.GOOS
switch os {
case "darwin":
return "To update, run: brew update && brew upgrade infisical"
case "windows":
return "To update, run: scoop update infisical"
case "linux":
pkgManager := getLinuxPackageManager()
switch pkgManager {
case "apt-get":
return "To update, run: sudo apt-get update && sudo apt-get install infisical"
case "yum":
return "To update, run: sudo yum update infisical"
case "apk":
return "To update, run: sudo apk update && sudo apk upgrade infisical"
case "yay":
return "To update, run: yay -Syu infisical"
default:
return ""
}
default:
return ""
}
}
func getLinuxPackageManager() string {
cmd := exec.Command("apt-get", "--version")
if err := cmd.Run(); err == nil {
return "apt-get"
}
cmd = exec.Command("yum", "--version")
if err := cmd.Run(); err == nil {
return "yum"
}
cmd = exec.Command("yay", "--version")
if err := cmd.Run(); err == nil {
return "yay"
}
cmd = exec.Command("apk", "--version")
if err := cmd.Run(); err == nil {
return "apk"
}
return ""
}

View File

@@ -42,7 +42,7 @@ func WriteInitalConfig(userCredentials *models.UserCredentials) error {
}
// Create file in directory
err = WriteToFile(fullConfigFilePath, configFileMarshalled, os.ModePerm)
err = WriteToFile(fullConfigFilePath, configFileMarshalled, 0600)
if err != nil {
return err
}
@@ -151,52 +151,6 @@ func GetWorkspaceConfigByPath(path string) (workspaceConfig models.WorkspaceConf
return workspaceConfigFile, nil
}
// Will get the list of .infisical.json files that are located
// within the root of each sub folder from where the CLI is ran from
func GetAllWorkSpaceConfigsStartingFromCurrentPath() (workspaces []models.WorkspaceConfigFile, err error) {
currentDir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("GetAllProjectConfigs: unable to get the current directory because [%s]", err)
}
files, err := os.ReadDir(currentDir)
if err != nil {
return nil, fmt.Errorf("GetAllProjectConfigs: unable to read the contents of the current directory because [%s]", err)
}
listOfWorkSpaceConfigs := []models.WorkspaceConfigFile{}
for _, file := range files {
if !file.IsDir() && file.Name() == INFISICAL_WORKSPACE_CONFIG_FILE_NAME {
pathToWorkspaceConfigFile := currentDir + "/" + INFISICAL_WORKSPACE_CONFIG_FILE_NAME
workspaceConfig, err := GetWorkspaceConfigByPath(pathToWorkspaceConfigFile)
if err != nil {
return nil, fmt.Errorf("GetAllProjectConfigs: Unable to get config file because [%s]", err)
}
listOfWorkSpaceConfigs = append(listOfWorkSpaceConfigs, workspaceConfig)
} else if file.IsDir() {
pathToSubFolder := currentDir + "/" + file.Name()
pathToMaybeWorkspaceConfigFile := pathToSubFolder + "/" + INFISICAL_WORKSPACE_CONFIG_FILE_NAME
_, err := os.Stat(pathToMaybeWorkspaceConfigFile)
if err != nil {
continue // workspace config file doesn't exist
}
workspaceConfig, err := GetWorkspaceConfigByPath(pathToMaybeWorkspaceConfigFile)
if err != nil {
return nil, fmt.Errorf("GetAllProjectConfigs: Unable to get config file because [%s]", err)
}
listOfWorkSpaceConfigs = append(listOfWorkSpaceConfigs, workspaceConfig)
}
}
return listOfWorkSpaceConfigs, nil
}
// Get the infisical config file and if it doesn't exist, return empty config model, otherwise raise error
func GetConfigFile() (models.ConfigFile, error) {
fullConfigFilePath, _, err := GetFullConfigFilePath()
@@ -243,7 +197,7 @@ func WriteConfigFile(configFile *models.ConfigFile) error {
}
// Create file in directory
err = os.WriteFile(fullConfigFilePath, configFileMarshalled, os.ModePerm)
err = os.WriteFile(fullConfigFilePath, configFileMarshalled, 0600)
if err != nil {
return fmt.Errorf("writeConfigFile: Unable to write to file [err=%s]", err)
}

View File

@@ -20,6 +20,9 @@ func PrintErrorAndExit(exitCode int, err error, messages ...string) {
}
}
supportMsg := fmt.Sprintf("\n\nIf this issue continues, get support at https://infisical.com/slack")
fmt.Fprintln(os.Stderr, supportMsg)
os.Exit(exitCode)
}

View File

@@ -76,10 +76,31 @@ func GetPlainTextSecretsViaJTW(JTWToken string, receiversPrivateKey string, work
return nil, fmt.Errorf("unable to get your encrypted workspace key. [err=%v]", err)
}
encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(receiversPrivateKey)
encryptedWorkspaceKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKey")
}
encryptedWorkspaceKeySenderPublicKey, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKeySenderPublicKey")
}
encryptedWorkspaceKeyNonce, err := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for encryptedWorkspaceKeyNonce")
}
currentUsersPrivateKey, err := base64.StdEncoding.DecodeString(receiversPrivateKey)
if err != nil {
HandleError(err, "Unable to get bytes represented by the base64 for currentUsersPrivateKey")
}
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
log.Debugf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
}
plainTextWorkspaceKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
encryptedSecrets, err := api.CallGetSecretsV2(httpClient, api.GetEncryptedSecretsV2Request{
@@ -131,6 +152,10 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
return nil, err
}
if params.WorkspaceId != "" {
workspaceFile.WorkspaceId = params.WorkspaceId
}
// Verify environment
err = ValidateEnvironmentName(params.Environment, workspaceFile.WorkspaceId, loggedInUserDetails.UserCredentials)
if err != nil {
@@ -415,7 +440,7 @@ func WriteBackupSecrets(workspace string, environment string, encryptionKey []by
}
listOfSecretsMarshalled, _ := json.Marshal(encryptedSecrets)
err = os.WriteFile(fmt.Sprintf("%s/%s", fullPathToSecretsBackupFolder, fileName), listOfSecretsMarshalled, os.ModePerm)
err = os.WriteFile(fmt.Sprintf("%s/%s", fullPathToSecretsBackupFolder, fileName), listOfSecretsMarshalled, 0600)
if err != nil {
return fmt.Errorf("WriteBackupSecrets: Unable to write backup secrets to file [err=%s]", err)
}

View File

@@ -0,0 +1,4 @@
---
title: "Get"
openapi: "GET /api/v2/service-token/"
---

View File

@@ -2,14 +2,24 @@
title: "Authentication"
---
To authenticate requests with Infisical, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform. You can obtain an API key in User Settings > API Keys
To authenticate requests with Infisical, you can either use an API Key or [Infisical Token](../../../getting-started/dashboard/token); certain endpoints will accept either one or both.
- API Key: This general-purpose authentication token provides user access to most endpoints in this reference.
- [Infisical Token](../../../getting-started/dashboard/token): This authentication token (also referred to as the service token) is scoped to a specific project and environment and used for CRUD secret operations.
<AccordionGroup>
<Accordion title="API Key">
To authenticate requests with Infisical using the API Key, you must include an API key in the `X-API-KEY` header of HTTP requests made to the platform.
You can obtain an API key in User Settings > API Keys
![API key dashboard](../../images/api-key-dashboard.png)
![API key in personal settings](../../images/api-key-settings.png)
![Adding an API key](../../images/api-key-add.png)
</Accordion>
<Accordion title="Infisical Token">
To authenticate requests with Infisical using the Infisical Token, you must include your Infisical Token in the `Authorization` header of HTTP requests made to the platform with the value `Bearer st.<rest_of_your_infisical_token>`.
<Info>
It's important to keep your API key secure, as it grants access to your
secrets in Infisical. For added security, set a reasonable expiration time and
rotate your API key on a regular basis.
</Info>
You can obtain an Infisical Token in Project Settings > Service Tokens.
![token add](../../images/project-token-add.png)
</Accordion>
</AccordionGroup>

View File

@@ -2,48 +2,48 @@
title: "Create secrets"
---
In this example, we demonstrate how to add secrets to a project and environment.
In this example, we demonstrate how to add secrets to a project and environment using an Infisical Token.
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
## Flow
1. [Get your (encrypted) private key](/api-reference/endpoints/users/me).
2. Decrypt your (encrypted) private key with your password.
3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
4. Decrypt the (encrypted) project key with your private key.
5. Encrypt your secret(s) with the project key.
6. [Send (encrypted) secret(s) to the Infical API](/api-reference/endpoints/secrets/create)
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
2. Decrypt the (encrypted) project key with the key from your Infisical Token.
3. Encrypt your secret(s) with the project key
4. [Send (encrypted) secret(s) to Infical](/api-reference/endpoints/secrets/create)
## Example
<Tabs>
<Tab title="Javascript">
```js
const crypto = require('crypto');
const axios = require('axios');
const nacl = require('tweetnacl');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const encrypt = (
text,
secret
) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
const encrypt = ({ text, secret }) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
const decrypt = (ciphertext, iv, tag, secret) => {
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
@@ -58,95 +58,190 @@ const decrypt = (ciphertext, iv, tag, secret) => {
}
const createSecrets = async () => {
const API_KEY = 'your_api_key';
const PSWD = 'your_pswd';
const WORKSPACE_ID = 'your_workspace_id';
const serviceToken = '';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretType = 'shared'; // 'shared' or 'personal'
const secretKey = 'some_key';
const secretValue = 'some_value';
const secretComment = 'some_comment';
const SECRET_KEY = 'SOME_KEY';
const SECRET_VALUE = 'SOME_VALUE';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 1. get (encrypted) private key
const user = await axios.get(
'https://api.infisical.com/api/v2/users/me', {
headers: {
'X-API-KEY': API_KEY
}
}
);
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 3. Encrypt your secret(s) with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt({
text: secretKey,
secret: projectKey
});
// 2. decrypt your (encrypted) private key with your password
const privateKey = decrypt({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
secret: PSWD.slice(0, 32).padStart(32, '0');
});
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt({
text: secretValue,
secret: projectKey
});
// 3. get the (encrypted) project key for the project
const encryptedProjectKey = await axios.get(
`https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, {
headers: {
'X-API-KEY': API_KEY
}
}
);
const {
ciphertext: secretCommentCiphertext,
iv: secretCommentIV,
tag: secretCommentTag
} = encrypt({
text: secretComment,
secret: projectKey
});
const secret = {
type: secretType,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
}
// 4. decrypt the project key with your private key
const projectKey = nacl.box.open(
util.decodeBase64(encryptedProjectKey),
util.decodeBase64(projectKey.nonce),
util.decodeBase64(projectKey.sender.publicKey),
util.decodeBase64(privateKey)
);
// 5. encrypt your secret(s) with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt(SECRET_KEY, projectKey);
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt(SECRET_VALUE, projectKey);
const secret = {
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}
// 6. Send (encrypted) secret(s) to the Infisical API
await axios.post(
`https://api.infisical.com/api/v2/secrets`,
{
workspaceId: WORKSPACE_ID,
environment: 'dev',
secrets: secret
},
{
headers: {
'X-API-KEY': API_KEY
}
}
);
// 4. Send (encrypted) secret(s) to Infisical
await axios.post(
`${BASE_URL}/api/v2/secrets`,
{
workspaceId: serviceTokenData.workspace,
environment: serviceTokenData.environment,
secrets: [secret]
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
}
createSecrets();
```
</Tab>
<Tab title="Python">
```Python
import base64
import requests
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
BASE_URL = "https://app.infisical.com"
BLOCK_SIZE_BYTES = 16
def encrypt(text, secret):
iv = get_random_bytes(BLOCK_SIZE_BYTES)
secret = bytes(secret, "utf-8")
cipher = AES.new(secret, AES.MODE_GCM, iv)
ciphertext, tag = cipher.encrypt_and_digest(text.encode("utf-8"))
return {
"ciphertext": base64.standard_b64encode(ciphertext).decode("utf-8"),
"tag": base64.standard_b64encode(tag).decode("utf-8"),
"iv": base64.standard_b64encode(iv).decode("utf-8"),
}
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def create_secrets():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_type = "shared" # "shared or "personal"
secret_key = "some_key"
secret_value = "some_value"
secret_comment = "some_comment"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 3. Encrypt your secret(s) with the project key
encrypted_key_data = encrypt(text=secret_key, secret=project_key)
encrypted_value_data = encrypt(text=secret_value, secret=project_key)
encrypted_comment_data = encrypt(text=secret_comment, secret=project_key)
secret = {
"type": secret_type,
"secretKeyCiphertext": encrypted_key_data["ciphertext"],
"secretKeyIV": encrypted_key_data["iv"],
"secretKeyTag": encrypted_key_data["tag"],
"secretValueCiphertext": encrypted_value_data["ciphertext"],
"secretValueIV": encrypted_value_data["iv"],
"secretValueTag": encrypted_value_data["tag"],
"secretCommentCiphertext": encrypted_comment_data["ciphertext"],
"secretCommentIV": encrypted_comment_data["iv"],
"secretCommentTag": encrypted_comment_data["tag"],
}
# 4. Send (encrypted) secret (s) to Infisical
requests.post(
f"{BASE_URL}/api/v2/secrets",
json={
"workspaceId": service_token_data["workspace"],
"environment": service_token_data["environment"],
"secrets": [secret],
},
headers={"Authorization": f"Bearer {service_token}"},
)
create_secrets()
```
</Tab>
</Tabs>
<Info>
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
are ports of NaCl available in every major language.
</Info>
<Tip>
It can be useful to perform steps 1-4 ahead of time and store away your
private key (and even project key) for later use. The Infisical CLI works by
securely storing your private key via your OS keyring.
</Tip>
</Info>

View File

@@ -7,24 +7,30 @@ In this example, we demonstrate how to delete secrets
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Create either an [API Key](/api-reference/overview/authentication) or [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
## Example
<Tabs>
<Tab title="Javascript">
```js
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const deleteSecrets = async () => {
const API_KEY = "your_api_key";
const SECRET_ID = "ID"; // ID of secret to delete
const serviceToken = 'your_service_token';
const secretId = 'id_of_secret_to_delete';
// 6. Send ID(s) of secret(s) to delete to the Infisical API
await axios.delete(
`https://api.infisical.com/api/v2/secrets`,
`${BASE_URL}/api/v2/secrets`,
{
secretIds: SECRET_ID,
secretIds: [secretId],
},
{
headers: {
"X-API-KEY": API_KEY,
Authorization: `Bearer ${serviceToken}`
},
}
);
@@ -32,3 +38,33 @@ const deleteSecrets = async () => {
deleteSecrets();
```
</Tab>
<Tab title="Python">
```Python
import requests
BASE_URL = "https://app.infisical.com"
def delete_secrets():
service_token = "<your_service_token>"
secret_id = "id_of_secret_to_delete"
# Send ID(s) of secret(s) to delete to the Infisical API
requests.delete(
f"{BASE_URL}/api/v2/secrets",
json={"secretIds": [secret_id]},
headers={"Authorization": f"Bearer {service_token}"},
)
delete_secrets()
```
</Tab>
</Tabs>
<Info>
If using an `API_KEY` to authenticate with the Infisical API, then you should include it in the `X_API_KEY` header.
</Info>

View File

@@ -2,48 +2,33 @@
title: "Retrieve secrets"
---
In this example, we demonstrate how to retrieve secrets from a project and environment.
In this example, we demonstrate how to retrieve secrets from a project and environment using an Infisical Token.
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Set up and add envars to [Infisical Cloud](https://app.infisical.com).
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
## Flow
1. [Get your (encrypted) private key.](/api-reference/endpoints/users/me)
2. Decrypt your (encrypted) private key with your password.
3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
4. Decrypt the (encrypted) project key with your private key.
5. [Get secrets for a project and environment.](/api-reference/endpoints/secrets/read)
6. Decrypt the (encrypted) secrets
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
2. [Get secrets for your project and environment](/api-reference/endpoints/secrets/read).
3. Decrypt the (encrypted) project key with the key from your Infisical Token.
4. Decrypt the (encrypted) secrets
## Example
<Tabs>
<Tab title="Javascript">
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const encrypt = (
text,
secret
) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
const decrypt = (ciphertext, iv, tag, secret) => {
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
@@ -51,73 +36,63 @@ const decrypt = (ciphertext, iv, tag, secret) => {
);
decipher.setAuthTag(Buffer.from(tag, 'base64'));
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
let cleartext = decipher.update(ciphertext, 'base64', 'utf8');
cleartext += decipher.final('utf8');
return cleartext;
return cleartext;
}
const retrieveSecrets = async () => {
const API_KEY = 'your_api_key';
const PSWD = 'your_pswd';
const WORKSPACE_ID = 'your_workspace_id';
const getSecrets = async () => {
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
// 1. get (encrypted) private key
const user = await axios.get(
'https://api.infisical.com/api/v2/users/me', {
headers: {
'X-API-KEY': API_KEY
}
}
);
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 2. decrypt your (encrypted) private key with your password
const privateKey = decrypt({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
secret: PSWD.slice(0, 32).padStart(32, '0');
});
// 2. Get secrets for your project and environment
const { data } = await axios.get(
`${BASE_URL}/api/v2/secrets?${new URLSearchParams({
environment: serviceTokenData.environment,
workspaceId: serviceTokenData.workspace
})}`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 3. get the (encrypted) project key for the project
const encryptedProjectKey = await axios.get(
`https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, {
headers: {
'X-API-KEY': API_KEY
}
}
);
const encryptedSecrets = data.secrets;
// 4. decrypt the project key with your private key
const projectKey = nacl.box.open(
util.decodeBase64(encryptedProjectKey),
util.decodeBase64(projectKey.nonce),
util.decodeBase64(projectKey.sender.publicKey),
util.decodeBase64(privateKey)
);
// 3. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 5. get (encrypted) secrets for a project and environment.
const encryptedSecrets = await axios.get(
'https://api.infisical.com/api/v2/secrets', {
headers: {
'X-API-KEY': API_KEY
}
}
);
// 6. decrypt the (encrypted) secrets
const secrets = encryptedSecrets.map((encryptedSecret) => {
// 4. Decrypt the (encrypted) secrets
const secrets = encryptedSecrets.map((secret) => {
const secretKey = decrypt({
ciphertext: encryptedSecret.secretKeyCiphertext,
iv: encryptedSecret.secretKeyIV,
tag: encryptedSecret.secretKeyTag
secret: projectKey
ciphertext: secret.secretKeyCiphertext,
iv: secret.secretKeyIV,
tag: secret.secretKeyTag,
secret: projectKey
});
const secretValue = decrypt({
ciphertext: encryptedSecret.secretValueCiphertext,
iv: encryptedSecret.secretValueIV,
tag: encryptedSecret.secretValueTag
secret: projectKey
ciphertext: secret.secretValueCiphertext,
iv: secret.secretValueIV,
tag: secret.secretValueTag,
secret: projectKey
});
return ({
@@ -125,18 +100,102 @@ const retrieveSecrets = async () => {
secretValue
});
});
console.log('secrets: ', secrets);
}
retrieveSecrets();
getSecrets();
```
</Tab>
<Tab title="Python">
```Python
import requests
import base64
from Cryptodome.Cipher import AES
BASE_URL = "http://app.infisical.com"
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def get_secrets():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Get secrets for your project and environment
data = requests.get(
f"{BASE_URL}/api/v2/secrets",
params={
"environment": service_token_data["environment"],
"workspaceId": service_token_data["workspace"],
},
headers={"Authorization": f"Bearer {service_token}"},
).json()
encrypted_secrets = data["secrets"]
# 3. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 4. Decrypt the (encrypted) secrets
secrets = []
for secret in encrypted_secrets:
secret_key = decrypt(
ciphertext=secret["secretKeyCiphertext"],
iv=secret["secretKeyIV"],
tag=secret["secretKeyTag"],
secret=project_key,
)
secret_value = decrypt(
ciphertext=secret["secretValueCiphertext"],
iv=secret["secretValueIV"],
tag=secret["secretValueTag"],
secret=project_key,
)
secrets.append(
{
"secret_key": secret_key,
"secret_value": secret_value,
}
)
print("secrets:", secrets)
get_secrets()
```
</Tab>
</Tabs>
<Info>
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
are ports of NaCl available in every major language.
</Info>
<Tip>
It can be useful to perform steps 1-4 ahead of time and store away your
private key (and even project key) for later use. The Infisical CLI works by
securely storing your private key via your OS keyring.
</Tip>
</Info>

View File

@@ -2,48 +2,47 @@
title: "Update secrets"
---
In this example, we demonstrate how to update secrets
In this example, we demonstrate how to update secrets using an Infisical Token.
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Create an [Infisical Token](../../../getting-started/dashboard/token) for your project and environment.
- Grasp a basic understanding of the system and its underlying cryptography [here](/api-reference/overview/introduction).
## Flow
1. [Get your (encrypted) private key.](/api-reference/endpoints/users/me)
2. Decrypt your (encrypted) private key with your password.
3. [Get the (encrypted) project key for the project.](/api-reference/endpoints/workspaces/workspace-key)
4. Decrypt the (encrypted) project key with your private key.
5. Encrypt your secret(s) with the project key.
6. [Send (encrypted) updated secret(s) to the Infical API.](/api-reference/endpoints/secrets/update)
1. [Get your Infisical Token data](/api-reference/endpoints/service-tokens/get) including a (encrypted) project key.
2. Decrypt the (encrypted) project key with the key from your Infisical Token.
3. Encrypt your updated secret(s) with the project key
4. [Send (encrypted) updated secret(s) to Infical](/api-reference/endpoints/secrets/update)
## Example
<Tabs>
<Tab title="Javascript">
```js
const crypto = require('crypto');
const axios = require('axios');
const BASE_URL = 'https://app.infisical.com';
const ALGORITHM = 'aes-256-gcm';
const BLOCK_SIZE_BYTES = 16;
const encrypt = (
text,
secret
) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
const encrypt = ({ text, secret }) => {
const iv = crypto.randomBytes(BLOCK_SIZE_BYTES);
const cipher = crypto.createCipheriv(ALGORITHM, secret, iv);
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
let ciphertext = cipher.update(text, 'utf8', 'base64');
ciphertext += cipher.final('base64');
return {
ciphertext,
iv: iv.toString('base64'),
tag: cipher.getAuthTag().toString('base64')
};
}
const decrypt = (ciphertext, iv, tag, secret) => {
const decrypt = ({ ciphertext, iv, tag, secret}) => {
const decipher = crypto.createDecipheriv(
ALGORITHM,
secret,
@@ -58,95 +57,188 @@ const decrypt = (ciphertext, iv, tag, secret) => {
}
const updateSecrets = async () => {
const API_KEY = 'your_api_key';
const PSWD = 'your_pswd';
const WORKSPACE_ID = 'your_workspace_id';
const serviceToken = 'your_service_token';
const serviceTokenSecret = serviceToken.substring(serviceToken.lastIndexOf('.') + 1);
const secretId = 'id_of_secret_to_update';
const secretKey = 'some_key';
const secretValue = 'updated_value';
const secretComment = 'updated_comment';
const SECRET_ID = 'ID' // ID of secret to update
const SECRET_KEY = 'SOME_KEY';
const SECRET_VALUE = 'SOME_VALUE';
// 1. Get your Infisical Token data
const { data: serviceTokenData } = await axios.get(
`${BASE_URL}/api/v2/service-token`,
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
// 1. get (encrypted) private key
const user = await axios.get(
'https://api.infisical.com/api/v2/users/me', {
headers: {
'X-API-KEY': API_KEY
}
}
);
// 2. Decrypt the (encrypted) project key with the key from your Infisical Token
const projectKey = decrypt({
ciphertext: serviceTokenData.encryptedKey,
iv: serviceTokenData.iv,
tag: serviceTokenData.tag,
secret: serviceTokenSecret
});
// 3. Encrypt your updated secret(s) with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt({
text: secretKey,
secret: projectKey
});
// 2. decrypt your (encrypted) private key with your password
const privateKey = decrypt({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
secret: PSWD.slice(0, 32).padStart(32, '0');
});
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt({
text: secretValue,
secret: projectKey
});
// 3. get the (encrypted) project key for the project
const encryptedProjectKey = await axios.get(
`https://api.infisical.com/api/v2/workspace/${WORKSPACE_ID}`, {
headers: {
'X-API-KEY': API_KEY
}
}
);
// 4. decrypt the project key with your private key
const projectKey = nacl.box.open(
util.decodeBase64(encryptedProjectKey),
util.decodeBase64(projectKey.nonce),
util.decodeBase64(projectKey.sender.publicKey),
util.decodeBase64(privateKey)
);
// 5. encrypt your secret(s) with the project key
const {
ciphertext: secretKeyCiphertext,
iv: secretKeyIV,
tag: secretKeyTag
} = encrypt(SECRET_KEY, projectKey);
const {
ciphertext: secretValueCiphertext,
iv: secretValueIV,
tag: secretValueTag
} = encrypt(SECRET_VALUE, projectKey);
const secret = {
id: SECRET_ID,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}
// 6. Send (encrypted) secret(s) to the Infisical API
await axios.patch(
`https://api.infisical.com/api/v2/secrets`,
{
secrets: secret
},
{
headers: {
'X-API-KEY': API_KEY
}
}
);
const {
ciphertext: secretCommentCiphertext,
iv: secretCommentIV,
tag: secretCommentTag
} = encrypt({
text: secretComment,
secret: projectKey
});
const secret = {
id: secretId,
workspace: serviceTokenData.workspace,
environment: serviceTokenData.environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
}
// 4. Send (encrypted) updated secret(s) to Infisical
await axios.patch(
`${BASE_URL}/api/v2/secrets`,
{
secrets: [secret]
},
{
headers: {
Authorization: `Bearer ${serviceToken}`
}
}
);
}
updateSecrets();
```
</Tab>
<Tab title="Python">
```Python
import base64
import requests
from Cryptodome.Cipher import AES
from Cryptodome.Random import get_random_bytes
BASE_URL = "https://app.infisical.com"
BLOCK_SIZE_BYTES = 16
def encrypt(text, secret):
iv = get_random_bytes(BLOCK_SIZE_BYTES)
secret = bytes(secret, "utf-8")
cipher = AES.new(secret, AES.MODE_GCM, iv)
ciphertext, tag = cipher.encrypt_and_digest(text.encode("utf-8"))
return {
"ciphertext": base64.standard_b64encode(ciphertext).decode("utf-8"),
"tag": base64.standard_b64encode(tag).decode("utf-8"),
"iv": base64.standard_b64encode(iv).decode("utf-8"),
}
def decrypt(ciphertext, iv, tag, secret):
secret = bytes(secret, "utf-8")
iv = base64.standard_b64decode(iv)
tag = base64.standard_b64decode(tag)
ciphertext = base64.standard_b64decode(ciphertext)
cipher = AES.new(secret, AES.MODE_GCM, iv)
cipher.update(tag)
cleartext = cipher.decrypt(ciphertext).decode("utf-8")
return cleartext
def update_secret():
service_token = "your_service_token"
service_token_secret = service_token[service_token.rindex(".") + 1 :]
secret_id = "id_of_secret_to_update"
secret_key = "some_key"
secret_value = "updated_value"
secret_comment = "updated_comment"
# 1. Get your Infisical Token data
service_token_data = requests.get(
f"{BASE_URL}/api/v2/service-token",
headers={"Authorization": f"Bearer {service_token}"},
).json()
# 2. Decrypt the (encrypted) project key with the key from your Infisical Token
project_key = decrypt(
ciphertext=service_token_data["encryptedKey"],
iv=service_token_data["iv"],
tag=service_token_data["tag"],
secret=service_token_secret,
)
# 3. Encrypt your updated secret(s) with the project key
encrypted_key_data = encrypt(text=secret_key, secret=project_key)
encrypted_value_data = encrypt(text=secret_value, secret=project_key)
encrypted_comment_data = encrypt(text=secret_comment, secret=project_key)
secret = {
"id": secret_id,
"workspace": service_token_data["workspace"],
"environment": service_token_data["environment"],
"secretKeyCiphertext": encrypted_key_data["ciphertext"],
"secretKeyIV": encrypted_key_data["iv"],
"secretKeyTag": encrypted_key_data["tag"],
"secretValueCiphertext": encrypted_value_data["ciphertext"],
"secretValueIV": encrypted_value_data["iv"],
"secretValueTag": encrypted_value_data["tag"],
"secretCommentCiphertext": encrypted_comment_data["ciphertext"],
"secretCommentIV": encrypted_comment_data["iv"],
"secretCommentTag": encrypted_comment_data["tag"],
}
# 4. Send (encrypted) updated secret(s) to Infisical
requests.patch(
f"{BASE_URL}/api/v2/secrets",
json={"secrets": [secret]},
headers={"Authorization": f"Bearer {service_token}"},
)
update_secret()
```
</Tab>
</Tabs>
<Info>
This example uses [TweetNaCl.js](https://tweetnacl.js.org/#/), a port of
TweetNacl/Nacl, to perform asymmeric decryption of the project key but there
are ports of NaCl available in every major language.
</Info>
<Tip>
It can be useful to perform steps 1-4 ahead of time and store away your
private key (and even project key) for later use. The Infisical CLI works by
securely storing your private key via your OS keyring.
</Tip>
</Info>

View File

@@ -12,10 +12,11 @@ With the REST API, users can create, read, update, and delete secrets, as well a
Using Infisical's API to manage secrets requires a basic understanding of the system and its underlying cryptography detailed [here](/security/overview).
- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by the user's password before being sent off to the server during the account signup process.
- Each user has a public/private key pair that is stored with the platform; private keys are encrypted locally by protected keys that are encrypted by keys derived from Argon2id applied to the user's password before being sent off to the server during the account signup process.
- Each (encrypted) secret belongs to a project and environment.
- Each project has an (encrypted) project key used to encrypt the secrets within that project; Infisical stores copies of the project key, for each member of that project, encrypted under each member's public key.
- Secrets are encrypted symmetrically by your copy of the project key belonging to the project containing.
- Infisical Tokens contain a symmetric key that can be used to decrypt a copy of a project key from the [call to get the Infisical Token data](/api-reference/endpoints/service-tokens/get).
- Infisical uses AES256-GCM and [TweetNaCl.js](https://tweetnacl.js.org/#/) for symmetric and asymmetric encryption/decryption operations.
<Info>

View File

@@ -11,31 +11,105 @@ infisical export [options]
Export environment variables from the platform into a file format.
## Options
## Subcommands & flags
| Option | Description | Default value |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `--env` | Used to set the environment that secrets are pulled from. Accepted values: `dev`, `staging`, `test`, `prod` | `dev` |
| `--projectId` | Only required if injecting via the [service token method](../token). If you are not using service token, the project id will be automatically retrieved from the `.infisical.json` located at the root of your local project. | `None` |
| `--expand` | Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`) | `true` |
| `--format` | Format of the output file. Accepted values: `dotenv`, `dotenv-export`, `csv` and `json` | `dotenv` |
<Accordion title="infisical export" defaultOpen="true">
Use this command to export environment variables from the platform into a raw file formats
## Examples
```bash
$ infisical export
```bash
# Export variables to a .env file
infisical export > .env
# Export variables to a .env file
infisical export > .env
# Export variables to a .env file (with export keyword)
infisical export --format=dotenv-export > .env
# Export variables to a .env file (with export keyword)
infisical export --format=dotenv-export > .env
# Export variables to a CSV file
infisical export --format=csv > secrets.csv
# Export variables to a CSV file
infisical export --format=csv > secrets.csv
# Export variables to a JSON file
infisical export --format=json > secrets.json
# Export variables to a JSON file
infisical export --format=json > secrets.json
# Export variables to a YAML file
infisical export --format=yaml > secrets.yaml
# Export variables to a YAML file
infisical export --format=yaml > secrets.yaml
```
```
### Environment variables
<Accordion title="INFISICAL_TOKEN">
Used to fetch secrets via a [service token](/getting-started/dashboard/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
```bash
# Example
export INFISICAL_TOKEN=st.63e03c4a97cb4a747186c71e.ed5b46a34c078a8f94e8228f4ab0ff97.4f7f38034811995997d72badf44b42ec
```
</Accordion>
<Accordion title="INFISICAL_DISABLE_UPDATE_CHECK">
Used to disable the check for new CLI versions. This can improve the time it takes to run this command. Recommended for production environments.
To use, simply export this variable in the terminal before running this command.
```bash
# Example
export INFISICAL_DISABLE_UPDATE_CHECK=true
```
</Accordion>
### flags
<Accordion title="--env">
Used to set the environment that secrets are pulled from.
```bash
# Example
infisical export --env=prod
```
Note: this flag only accepts environment slug names not the fully qualified name. To view the slug name of an environment, visit the project settings page.
default value: `dev`
</Accordion>
<Accordion title="--projectId">
By default the project id is retrieved from the `.infisical.json` located at the root of your local project.
This flag allows you to override this behavior by explicitly defining the project to fetch your secrets from.
```bash
# Example
infisical export --projectId=XXXXXXXXXXXXXX
```
</Accordion>
<Accordion title="--expand">
Parse shell parameter expansions in your secrets (e.g., `${DOMAIN}`)
Default value: `true`
</Accordion>
<Accordion title="--format">
Format of the output file. Accepted values: `dotenv`, `dotenv-export`, `csv` and `json`
Default value: `dotenv`
</Accordion>
<Accordion title="--secret-overriding">
Prioritizes personal secrets with the same name over shared secrets
Default value: `true`
</Accordion>
<Accordion title="--tags">
When working with tags, you can use this flag to filter and retrieve only secrets that are associated with a specific tag(s).
```bash
# Example
infisical run --tags=tag1,tag2,tag3 -- npm run dev
```
Note: you must reference the tag by its slug name not its fully qualified name. Go to project settings to view all tag slugs.
By default, all secrets are fetched
</Accordion>
</Accordion>

View File

@@ -40,7 +40,28 @@ Inject secrets from Infisical into your application process.
$ infisical run -- npm run dev
```
### flags
### Environment variables
<Accordion title="INFISICAL_TOKEN">
Used to fetch secrets via a [service token](/getting-started/dashboard/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
```bash
# Example
export INFISICAL_TOKEN=st.63e03c4a97cb4a747186c71e.ed5b46a34c078a8f94e8228f4ab0ff97.4f7f38034811995997d72badf44b42ec
```
</Accordion>
<Accordion title="INFISICAL_DISABLE_UPDATE_CHECK">
Used to disable the check for new CLI versions. This can improve the time it takes to run this command. Recommended for production environments.
To use, simply export this variable in the terminal before running this command.
```bash
# Example
export INFISICAL_DISABLE_UPDATE_CHECK=true
```
</Accordion>
### Flags
<Accordion title="--command">
Pass secrets into multiple commands at once
@@ -79,4 +100,17 @@ Inject secrets from Infisical into your application process.
Default value: `true`
</Accordion>
<Accordion title="--tags">
When working with tags, you can use this flag to filter and retrieve only secrets that are associated with a specific tag(s).
```bash
# Example
infisical run --tags=tag1,tag2,tag3 -- npm run dev
```
Note: you must reference the tag by its slug name not its fully qualified name. Go to project settings to view all tag slugs.
By default, all secrets are fetched
</Accordion>
</Accordion>

View File

@@ -18,7 +18,28 @@ This command enables you to perform CRUD (create, read, update, delete) operatio
$ infisical secrets
```
### flags
### Environment variables
<Accordion title="INFISICAL_TOKEN">
Used to fetch secrets via a [service token](/getting-started/dashboard/token) apposed to logged in credentials. Simply, export this variable in the terminal before running this command.
```bash
# Example
export INFISICAL_TOKEN=st.63e03c4a97cb4a747186c71e.ed5b46a34c078a8f94e8228f4ab0ff97.4f7f38034811995997d72badf44b42ec
```
</Accordion>
<Accordion title="INFISICAL_DISABLE_UPDATE_CHECK">
Used to disable the check for new CLI versions. This can improve the time it takes to run this command. Recommended for production environments.
To use, simply export this variable in the terminal before running this command.
```bash
# Example
export INFISICAL_DISABLE_UPDATE_CHECK=true
```
</Accordion>
### Flags
<Accordion title="--expand">
Parse shell parameter expansions in your secrets

View File

@@ -45,19 +45,19 @@ The Infisical CLI provides a way to inject environment variables from the platfo
<Tab title="Alpine">
Install prerequisite
```bash
sudo apk add --no-cache bash sudo
apk add --no-cache bash sudo
```
Add Infisical repository
```bash
curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' \
| sudo -E bash
| bash
```
Then install CLI
```bash
sudo apk update && sudo apk add infisical
apk update && sudo apk add infisical
```
</Tab>

View File

@@ -1,12 +1,12 @@
---
title: "Activity Logs"
title: "Audit Logs"
description: "See which events are triggered within your Infisical project."
---
Activity logs record all actions going through Infisical including who performed which CRUD operations on environment variables and from what IP address. They help answer questions like:
Audit logs record all actions going through Infisical including who performed which CRUD operations on environment variables and from what IP address. They help answer questions like:
- Who added or updated environment variables recently?
- Did Bob read environment variables last week (if at all)?
- What IP address was used for that action?
![Activity logs](../../images/activity-logs.png)
![Audit logs](../../images/activity-logs.png)

View File

@@ -12,7 +12,7 @@ This is a non-exhaustive list of features that Infisical offers:
- Sync secrets to platforms via integrations to platforms like GitHub, Vercel, and Netlify.
- Rollback secrets to any point in time.
- Rollback each secrets to any version.
- Track actions through activity logs.
- Track actions through audit logs.
## CLI
@@ -27,18 +27,18 @@ We're building the future of secret management, one that's comprehensive and acc
| Feature | Status |
| ------------------------------------- | ----------- |
| Account recovery: Backup key | Done |
| 2FA | Done |
| Read/write access controls | Done |
| Comparing secrets across environments | Done |
| Integrations | Ongoing |
| More hosting options | Ongoing |
| 1-Click Deploys | Ongoing |
| Account recovery: Backup key | Ongoing |
| Access logs | Ongoing |
| Account recovery: Member-assisted | Coming soon |
| Slack & MS teams integrations | Coming soon |
| Version control for secrets | Coming soon |
| 2FA | Coming soon |
| Restricted IPs | Coming soon |
| Read/write access controls | Coming soon |
| Secret rotation | Coming soon |
| Comparing secrets across environments | Coming soon |
Interested in contributing? Check out the [guide](/contributing/overview).

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 424 KiB

View File

@@ -1,34 +1,70 @@
---
title: "Gitlab Pipeline"
title: "GitLab"
description: "How to automatically sync secrets from Infisical into GitLab."
---
To integrate Infisical secrets into your Gitlab CI/CD setup, three steps are required.
Prerequisites:
## Generate service token
To expose Infisical secrets in Gitlab CI/CD, you must generate a service token for the specific project and environment in Infisical. For instructions on how to generate a service token, refer to [this page](../../getting-started/dashboard/token)
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
## Set Infisical service token in Gitlab
To provide Infisical CLI with the service token generated in the previous step, go to **Settings > CI/CD > Variables** in Gitlab and create a new **INFISICAL_TOKEN** variable. Enter the generated service token as its value.
<Tabs>
<Tab title="Standard">
## Navigate to your project's integrations tab
## Configure Infisical in your pipeline
Edit your .gitlab-ci.yml to include the installation of the Infisical CLI. This will allow you to use the CLI for fetching and injecting secrets into any script or command within your Gitlab CI/CD process.
![integrations](../../images/integrations.png)
## Authorize Infisical for GitLab
Press on the GitLab tile and grant Infisical access to your GitLab account.
![integrations gitlab authorization](../../images/integrations-gitlab-auth.png)
<Info>
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.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which GitLab repository and press create integration to start syncing secrets to GitLab.
![integrations gitlab](../../images/integrations-gitlab-create.png)
![integrations gitlab](../../images/integrations-gitlab.png)
</Tab>
<Tab title="Pipeline">
## Generate service token
Generate an [Infisical Token](../../getting-started/dashboard/token) for the specific project and environment in Infisical.
## Set the Infisical Token in Gitlab
Create a new variable called `INFISICAL_TOKEN` with the value set to the token from the previous step in Settings > CI/CD > Variables of your GitLab repository.
## Configure Infisical in your pipeline
Edit your `.gitlab-ci.yml` to include the Infisical CLI installation. This will allow you to use the CLI for fetching and injecting secrets into any script or command within your Gitlab CI/CD process.
#### Example
```yaml
```yaml
image: ubuntu
stages:
stages:
- build
- test
- deploy
build-job:
build-job:
stage: build
script:
- apt update && apt install -y curl
- curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash
- apt-get update && apt-get install -y infisical
- infisical run -- npm run build
```
...
```
</Tab>
</Tabs>

View File

@@ -0,0 +1,36 @@
---
title: "Azure Key Vault"
description: "How to automatically sync secrets from Infisical into your Azure Key Vault."
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Set up Azure and have an existing key vault
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for Azure Key Vault
Press on the Azure Key Vault tile and grant Infisical access to Azure Key Vault.
## Start Integration
Obtain the Vault URI of your key vault in the Overview tab.
![integrations](../../images/integrations-azure-key-vault-vault-uri.png)
Select which Infisical environment secrets you want to sync to your key vault. Then, input your Vault URI from the previous step. Finally, press create integration to start syncing secrets to Azure Key Vault.
![integrations](../../images/integrations-azure-key-vault-create.png)
![integrations](../../images/integrations-azure-key-vault.png)
<Info>
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.
</Info>

View File

@@ -0,0 +1,34 @@
---
title: "SvelteKit"
description: "How to use Infisical to inject environment variables and secrets into a SvelteKit app."
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- [Install the CLI](/cli/overview)
## Initialize Infisical for your [SvelteKit](https://kit.svelte.dev) app
```bash
# navigate to the root of your of your project
cd /path/to/project
# then initialize infisical
infisical init
```
## Start your application as usual but with Infisical
```bash
infisical run -- <your application start command>
# Example
infisical run -- npm run dev
```
<Note>
Note that for environment variables to be exposed to the client, you'll have
to prefix them with `PUBLIC_`. Read more about that
[here](https://kit.svelte.dev/docs/modules#$env-static-public).
</Note>

View File

@@ -0,0 +1,34 @@
---
title: "Terraform"
description: "How to use Infisical to inject environment variables and secrets into terraform."
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- [Install the CLI](/cli/overview)
## Initialize Infisical for your [Terraform](https://www.terraform.io/) project
```bash
# navigate to the root of your of your project
cd /path/to/project
# then initialize Infisical
infisical init
```
## Run terraform as usual but with Infisical
```bash
infisical run -- <your application start command>
# Example
infisical run -- terraform plan
```
<Note>
To inject any arbitrary variable to terraform, you have
to prefix them with `TF_VAR`. Read more about that
[here](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_var_name).
</Note>

View File

@@ -12,6 +12,7 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [Docker](/integrations/platforms/docker) | Platform | Available |
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
| [Terraform](/integrations/frameworks/terraform) | Infrastructure as code | Available |
| [PM2](/integrations/platforms/pm2) | Platform | Available |
| [Heroku](/integrations/cloud/heroku) | Cloud | Available |
| [Vercel](/integrations/cloud/vercel) | Cloud | Available |
@@ -20,8 +21,9 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [Fly.io](/integrations/cloud/flyio) | Cloud | Available |
| [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
| [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
| [Azure Key Vault](/integrations/cloud/azure-key-vault) | Cloud | Available |
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
| [GitLab](/integrations/cicd/gitlab) | CI/CD | Available |
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Available |
| [Travis CI](/integrations/cicd/travisci) | CI/CD | Available |
| [React](/integrations/frameworks/react) | Framework | Available |
@@ -29,6 +31,7 @@ Missing an integration? Throw in a [request](https://github.com/Infisical/infisi
| [Express](/integrations/frameworks/express) | Framework | Available |
| [Next.js](/integrations/frameworks/nextjs) | Framework | Available |
| [NestJS](/integrations/frameworks/nestjs) | Framework | Available |
| [SvelteKit](/integrations/frameworks/sveltekit) | Framework | Available |
| [Nuxt](/integrations/frameworks/nuxt) | Framework | Available |
| [Gatsby](/integrations/frameworks/gatsby) | Framework | Available |
| [Remix](/integrations/frameworks/remix) | Framework | Available |
@@ -38,9 +41,7 @@ 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 |
| AWS | Cloud | Coming soon |
| GCP | Cloud | Coming soon |
| Azure | Cloud | Coming soon |
| DigitalOcean | Cloud | Coming soon |
| GitHub Actions | CI/CD | Coming soon |
| Jenkins | CI/CD | Coming soon |

View File

@@ -42,7 +42,7 @@ Starting your service with the Infisical CLI pulls your secrets from Infisical a
CMD ["infisical", "run", "---", "[your service start command]"]
# example with single single command
CMD ["infisical", "run", "---", "npm run start"]
CMD ["infisical", "run", "---", "npm", "run", "start"]
# example with multiple commands
CMD ["infisical", "run", "--command", "npm run start && ..."]

View File

@@ -50,7 +50,7 @@ metadata:
# Name of of this InfisicalSecret resource
name: infisicalsecret-sample
spec:
# The host that should be used to pull secrets from. The default value is https://app.infisical.com/api.
# The host that should be used to pull secrets from. If left empty, the value specified in Global configuration will be used
hostAPI: https://app.infisical.com/api
# The Kubernetes secret the stores the Infisical token
@@ -288,8 +288,43 @@ spec:
```
</Accordion>
## Global configuration
To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap.
For example, you can configure all `InfisicalSecret` instances to fetch secrets from a single backend API without specifying the `hostAPI` parameter for each instance.
## Troubleshoot
### Available global properties
| Property | Description | Default value
| -------- | ------------------------------------- |------------------------
| hostAPI | If `hostAPI` in `InfisicalSecret` instance is left empty, this value will be used | https://app.infisical.com/api
### Applying global configurations
All global configurations must reside in a Kubernetes ConfigMap named `infisical-config` in the namespace `infisical-operator-system`.
To apply global configuration to the operator, copy the following yaml into `infisical-config.yaml` file.
```yaml infisical-config.yaml
apiVersion: v1
kind: Namespace
metadata:
name: infisical-operator-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: infisical-config
namespace: infisical-operator-system
data:
hostAPI: https://example.com/api # <-- global hostAPI
```
Then apply this change via kubectl by running the following
```bash
kubectl apply -f infisical-config.yaml
```
## Troubleshoot operator
If the operator is unable to fetch secrets from the API, it will not affect the managed Kubernetes secret.
It will continue attempting to reconnect to the API indefinitely.

View File

@@ -54,11 +54,6 @@
"icon": "cloud",
"url": "api-reference"
},
{
"name": "Integrations",
"icon": "plug",
"url": "integrations"
},
{
"name": "Contributing",
"icon": "code",
@@ -94,7 +89,6 @@
"pages": [
"getting-started/dashboard/organization",
"getting-started/dashboard/project",
"getting-started/dashboard/integrations",
"getting-started/dashboard/pit-recovery",
"getting-started/dashboard/secret-versioning",
"getting-started/dashboard/audit-logs",
@@ -103,7 +97,7 @@
]
},
{
"group": "CLI",
"group": "Command line",
"pages": [
"cli/overview",
"cli/usage",
@@ -123,6 +117,61 @@
"cli/faq"
]
},
{
"group": "Integrations",
"pages": [
"integrations/overview",
{
"group": "Docker",
"pages": [
"integrations/platforms/docker",
"integrations/platforms/docker-compose"
]
},
"integrations/platforms/kubernetes",
"integrations/frameworks/terraform",
{
"group": "AWS",
"pages": [
"integrations/cloud/aws-parameter-store",
"integrations/cloud/aws-secret-manager"
]
},
"integrations/cloud/heroku",
"integrations/cloud/vercel",
"integrations/cloud/netlify",
"integrations/cloud/render",
"integrations/cloud/flyio",
"integrations/cloud/azure-key-vault",
"integrations/cicd/githubactions",
"integrations/cicd/gitlab",
"integrations/cicd/circleci",
"integrations/cicd/travisci",
"integrations/frameworks/react",
"integrations/frameworks/vue",
"integrations/frameworks/express",
"integrations/frameworks/nextjs",
"integrations/frameworks/nestjs",
"integrations/frameworks/sveltekit",
"integrations/frameworks/nuxt",
"integrations/frameworks/gatsby",
"integrations/frameworks/remix",
"integrations/frameworks/vite",
"integrations/frameworks/fiber",
"integrations/frameworks/django",
"integrations/frameworks/flask",
"integrations/frameworks/laravel",
"integrations/frameworks/rails",
"integrations/frameworks/dotnet",
"integrations/platforms/pm2"
]
},
{
"group": "Self-hosting",
"pages": [
@@ -200,63 +249,15 @@
"api-reference/endpoints/secrets/versions",
"api-reference/endpoints/secrets/rollback-version"
]
},
{
"group": "Service Tokens",
"pages": [
"api-reference/endpoints/service-tokens/get"
]
}
]
},
{
"group": "Integrations",
"pages": ["integrations/overview"]
},
{
"group": "Platforms",
"pages": [
"integrations/platforms/docker",
"integrations/platforms/docker-compose",
"integrations/platforms/kubernetes",
"integrations/platforms/pm2"
]
},
{
"group": "Cloud",
"pages": [
"integrations/cloud/heroku",
"integrations/cloud/vercel",
"integrations/cloud/netlify",
"integrations/cloud/render",
"integrations/cloud/flyio",
"integrations/cloud/aws-parameter-store",
"integrations/cloud/aws-secret-manager"
]
},
{
"group": "CI/CD",
"pages": [
"integrations/cicd/githubactions",
"integrations/cicd/gitlab",
"integrations/cicd/circleci",
"integrations/cicd/travisci"
]
},
{
"group": "Frameworks",
"pages": [
"integrations/frameworks/react",
"integrations/frameworks/vue",
"integrations/frameworks/express",
"integrations/frameworks/nextjs",
"integrations/frameworks/nestjs",
"integrations/frameworks/nuxt",
"integrations/frameworks/gatsby",
"integrations/frameworks/remix",
"integrations/frameworks/vite",
"integrations/frameworks/fiber",
"integrations/frameworks/django",
"integrations/frameworks/flask",
"integrations/frameworks/laravel",
"integrations/frameworks/rails",
"integrations/frameworks/dotnet"
]
},
{
"group": "Security",
"pages": [

View File

@@ -134,4 +134,33 @@ SMTP_FROM_NAME=Infisical
Remember that you will need to restart Infisical for this to work properly.
</Info>
</Accordion>
<Accordion title="Zoho Mail">
1. Create an account and configure [Zoho Mail](https://www.zoho.com/mail/) to send emails.
2. With your email credentials, you can now set up your SMTP environment variables:
```
SMTP_HOST=smtp.zoho.com
SMTP_USERNAME=username # your email
SMTP_PASSWORD=password # your password
SMTP_PORT=587
SMTP_SECURE=true
SMTP_FROM_ADDRESS=hey@example.com # your personal Zoho email or domain-based email linked to Zoho Mail
SMTP_FROM_NAME=Infisical
```
<Note>
You can use either your personal Zoho email address like `you@zohomail.com` or
a domain-based email address like `you@yourdomain.com`. If using a
domain-based email address, then please make sure that you've configured and
verified it with Zoho Mail.
</Note>
<Info>
Remember that you will need to restart Infisical for this to work properly.
</Info>
</Accordion>
</AccordionGroup>

View File

@@ -33,8 +33,7 @@ wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.exa
wget -O docker-compose.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.yml
# Download nginx config
mkdir nginx && cd nginx && wget -O default.conf https://raw.githubusercontent.com/Infisical/infisical/main/nginx/default.dev.conf
cd ..
mkdir nginx && wget -O ./nginx/default.conf https://raw.githubusercontent.com/Infisical/infisical/main/nginx/default.dev.conf
```
3. Tweak the `.env` according to your preferences. Refer to the available [environment variables](../../self-hosting/configuration/envars)
@@ -51,4 +50,4 @@ nano .env
docker-compose -f docker-compose.yml up -d
```
5. Your Infisical installation is complete and should be running on [http://localhost:8080](http://localhost:8080). Please note that the containers are not exposed to the internet and only bind to the localhost. It's up to you to configure a firewall, SSL certificates, and implement any additional security measures.
5. Your Infisical installation is complete and should be running on [http://localhost:80](http://localhost:80). Please note that the containers are not exposed to the internet and only bind to the localhost. It's up to you to configure a firewall, SSL certificates, and implement any additional security measures.

View File

@@ -309,78 +309,6 @@ paths:
example: any
code:
example: any
/api/v1/signup/complete-account/signup:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
'403':
description: Forbidden
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
firstName:
example: any
lastName:
example: any
publicKey:
example: any
encryptedPrivateKey:
example: any
iv:
example: any
tag:
example: any
salt:
example: any
verifier:
example: any
organizationName:
example: any
/api/v1/signup/complete-account/invite:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
'403':
description: Forbidden
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
firstName:
example: any
lastName:
example: any
publicKey:
example: any
encryptedPrivateKey:
example: any
iv:
example: any
tag:
example: any
salt:
example: any
verifier:
example: any
/api/v1/auth/token:
post:
description: ''
@@ -412,7 +340,11 @@ paths:
/api/v1/auth/login2:
post:
description: ''
parameters: []
parameters:
- name: user-agent
in: header
schema:
type: string
responses:
'200':
description: OK
@@ -431,7 +363,11 @@ paths:
/api/v1/auth/logout:
post:
description: ''
parameters: []
parameters:
- name: user-agent
in: header
schema:
type: string
responses:
'200':
description: OK
@@ -691,6 +627,18 @@ paths:
description: OK
'400':
description: Bad Request
/api/v1/organization/{organizationId}/workspace-memberships:
get:
description: ''
parameters:
- name: organizationId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
/api/v1/workspace/{workspaceId}/keys:
get:
description: ''
@@ -933,6 +881,26 @@ paths:
properties:
role:
example: any
/api/v1/membership/{membershipId}/deny-permissions:
post:
description: ''
parameters:
- name: membershipId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
requestBody:
content:
application/json:
schema:
type: object
properties:
permissions:
example: any
/api/v1/key/{workspaceId}:
post:
description: ''
@@ -1147,11 +1115,17 @@ paths:
properties:
clientProof:
example: any
protectedKey:
example: any
protectedKeyIV:
example: any
protectedKeyTag:
example: any
encryptedPrivateKey:
example: any
iv:
encryptedPrivateKeyIV:
example: any
tag:
encryptedPrivateKeyTag:
example: any
salt:
example: any
@@ -1247,11 +1221,17 @@ paths:
schema:
type: object
properties:
protectedKey:
example: any
protectedKeyIV:
example: any
protectedKeyTag:
example: any
encryptedPrivateKey:
example: any
iv:
encryptedPrivateKeyIV:
example: any
tag:
encryptedPrivateKeyTag:
example: any
salt:
example: any
@@ -1270,6 +1250,39 @@ paths:
description: OK
'400':
description: Bad Request
/api/v1/integration/:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
integrationAuthId:
example: any
app:
example: any
appId:
example: any
isActive:
example: any
sourceEnvironment:
example: any
targetEnvironment:
example: any
owner:
example: any
path:
example: any
region:
example: any
/api/v1/integration/{integrationId}:
patch:
description: ''
@@ -1290,17 +1303,17 @@ paths:
schema:
type: object
properties:
app:
example: any
environment:
example: any
isActive:
example: any
target:
app:
example: any
context:
appId:
example: any
siteId:
targetEnvironment:
example: any
owner:
example: any
delete:
description: ''
@@ -1322,6 +1335,33 @@ paths:
responses:
'200':
description: OK
/api/v1/integration-auth/{integrationAuthId}:
get:
description: ''
parameters:
- name: integrationAuthId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
'400':
description: Bad Request
delete:
description: ''
parameters:
- name: integrationAuthId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
'400':
description: Bad Request
/api/v1/integration-auth/oauth-token:
post:
description: ''
@@ -1343,6 +1383,29 @@ paths:
example: any
integration:
example: any
/api/v1/integration-auth/access-token:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
workspaceId:
example: any
accessId:
example: any
accessToken:
example: any
integration:
example: any
/api/v1/integration-auth/{integrationAuthId}/apps:
get:
description: ''
@@ -1357,13 +1420,115 @@ paths:
description: OK
'400':
description: Bad Request
/api/v1/integration-auth/{integrationAuthId}:
delete:
/api/v2/signup/complete-account/signup:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
'403':
description: Forbidden
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
firstName:
example: any
lastName:
example: any
protectedKey:
example: any
protectedKeyIV:
example: any
protectedKeyTag:
example: any
publicKey:
example: any
encryptedPrivateKey:
example: any
encryptedPrivateKeyIV:
example: any
encryptedPrivateKeyTag:
example: any
salt:
example: any
verifier:
example: any
organizationName:
example: any
/api/v2/signup/complete-account/invite:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
'403':
description: Forbidden
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
firstName:
example: any
lastName:
example: any
protectedKey:
example: any
protectedKeyIV:
example: any
protectedKeyTag:
example: any
publicKey:
example: any
encryptedPrivateKey:
example: any
encryptedPrivateKeyIV:
example: any
encryptedPrivateKeyTag:
example: any
salt:
example: any
verifier:
example: any
/api/v2/auth/login1:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
clientPublicKey:
example: any
/api/v2/auth/login2:
post:
description: ''
parameters:
- name: integrationAuthId
in: path
required: true
- name: user-agent
in: header
schema:
type: string
responses:
@@ -1371,6 +1536,54 @@ paths:
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
clientProof:
example: any
/api/v2/auth/mfa/send:
post:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
/api/v2/auth/mfa/verify:
post:
description: ''
parameters:
- name: user-agent
in: header
schema:
type: string
responses:
'200':
description: OK
requestBody:
content:
application/json:
schema:
type: object
properties:
email:
example: any
mfaToken:
example: any
/api/v2/users/me:
get:
summary: Retrieve the current user on the request
@@ -1392,6 +1605,23 @@ paths:
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/users/me/mfa:
patch:
description: ''
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
isMfaEnabled:
example: any
/api/v2/users/me/organizations:
get:
summary: Return organizations that current user is part of
@@ -1615,6 +1845,62 @@ paths:
properties:
environmentSlug:
example: any
get:
description: ''
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
/api/v2/workspace/{workspaceId}/tags:
get:
description: ''
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
post:
description: ''
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
example: any
slug:
example: any
/api/v2/workspace/tags/{tagId}:
delete:
description: ''
parameters:
- name: tagId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
/api/v2/workspace/{workspaceId}/secrets:
post:
description: ''
@@ -1804,6 +2090,28 @@ paths:
description: Bad Request
security:
- apiKeyAuth: []
/api/v2/workspace/{workspaceId}/auto-capitalization:
patch:
description: ''
parameters:
- name: workspaceId
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
'400':
description: Bad Request
requestBody:
content:
application/json:
schema:
type: object
properties:
autoCapitalization:
example: any
/api/v2/secret/batch-create/workspace/{workspaceId}/environment/{environment}:
post:
description: ''
@@ -1968,11 +2276,38 @@ paths:
properties:
secret:
example: any
/api/v2/secrets/batch:
post:
description: ''
parameters:
- name: user-agent
in: header
schema:
type: string
responses:
'200':
description: OK
requestBody:
content:
application/json:
schema:
type: object
properties:
workspaceId:
example: any
environment:
example: any
requests:
example: any
/api/v2/secrets/:
post:
summary: Create new secret(s)
description: Create one or many secrets for a given project and environment.
parameters: []
parameters:
- name: user-agent
in: header
schema:
type: string
responses:
'200':
description: OK
@@ -2022,6 +2357,10 @@ paths:
in: query
schema:
type: string
- name: user-agent
in: header
schema:
type: string
- name: content
in: query
schema:
@@ -2073,7 +2412,11 @@ paths:
delete:
summary: Delete secret(s)
description: Delete one or many secrets by their ID(s)
parameters: []
parameters:
- name: user-agent
in: header
schema:
type: string
responses:
'200':
description: OK
@@ -2101,13 +2444,23 @@ paths:
description: ID(s) of secrets - string or array of strings
/api/v2/service-token/:
get:
description: ''
summary: Return Infisical Token data
description: Return Infisical Token data
parameters: []
responses:
'200':
description: OK
'400':
description: Bad Request
content:
application/json:
schema:
type: object
properties:
serviceTokenData:
type: object
$ref: '#/components/schemas/ServiceTokenData'
description: Details of service token
security:
- bearerAuth: []
post:
description: ''
parameters: []
@@ -2136,6 +2489,8 @@ paths:
example: any
expiresIn:
example: any
permissions:
example: any
/api/v2/service-token/{serviceTokenDataId}:
delete:
description: ''
@@ -2317,9 +2672,6 @@ components:
Project:
type: object
properties:
_id:
type: string
example: ''
name:
type: string
example: My Project
@@ -2602,6 +2954,51 @@ components:
secretValueTag:
type: string
example: ''
ServiceTokenData:
type: object
properties:
_id:
type: string
example: ''
name:
type: string
example: ''
workspace:
type: string
example: ''
environment:
type: string
example: ''
user:
type: object
properties:
_id:
type: string
example: ''
firstName:
type: string
example: ''
lastName:
type: string
example: ''
expiresAt:
type: string
example: '2023-01-13T14:16:12.210Z'
encryptedKey:
type: string
example: ''
iv:
type: string
example: ''
tag:
type: string
example: ''
updatedAt:
type: string
example: '2023-01-13T14:16:12.210Z'
createdAt:
type: string
example: '2023-01-13T14:16:12.210Z'
securitySchemes:
bearerAuth:
type: http

View File

@@ -8,7 +8,7 @@ module.exports = {
debug: false,
i18n: {
defaultLocale: "en",
locales: ["en", "ko", "fr", "pt-BR", "pt-PT"],
locales: ["en", "ko", "fr", "pt-BR", "pt-PT", "es"],
},
fallbackLng: {
default: ["en"],
@@ -23,4 +23,4 @@ module.exports = {
// strictMode: true,
// serializeConfig: false,
// react: { useSuspense: false }
};
};

View File

@@ -1,5 +1,5 @@
{
"name": "frontend",
"name": "npm-proj-1677883018530-0.7603125731052582NtcmfK",
"lockfileVersion": 2,
"requires": true,
"packages": {
@@ -13,7 +13,7 @@
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.1.19",
"@headlessui/react": "^1.6.6",
"@headlessui/react": "^1.7.7",
"@hookform/resolvers": "^2.9.10",
"@octokit/rest": "^19.0.7",
"@radix-ui/react-accordion": "^1.1.0",
@@ -30,14 +30,14 @@
"@radix-ui/react-tabs": "^1.0.2",
"@radix-ui/react-toast": "^1.1.2",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/react-stripe-js": "^1.16.3",
"@stripe/stripe-js": "^1.46.0",
"@tanstack/react-query": "^4.23.0",
"@types/argon2-browser": "^1.18.1",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
@@ -45,13 +45,13 @@
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
"i18next": "^22.4.6",
"i18next": "^22.4.9",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.3.4",
"next-i18next": "^13.0.2",
"posthog-js": "^1.34.0",
"posthog-js": "^1.39.4",
"query-string": "^7.1.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
@@ -66,7 +66,7 @@
"react-table": "^7.8.0",
"set-cookie-parser": "^2.5.1",
"sharp": "^0.31.2",
"styled-components": "^5.3.5",
"styled-components": "^5.3.7",
"tailwind-merge": "^1.8.1",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
@@ -2544,9 +2544,12 @@
}
},
"node_modules/@headlessui/react": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.6.tgz",
"integrity": "sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q==",
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.7.tgz",
"integrity": "sha512-BqDOd/tB9u2tA0T3Z0fn18ktw+KbVwMnkxxsGPIH2hzssrQhKB5n/6StZOyvLYP/FsYtvuXfi9I0YowKPv2c1w==",
"dependencies": {
"client-only": "^0.0.1"
},
"engines": {
"node": ">=10"
},
@@ -3935,9 +3938,9 @@
"dev": true
},
"node_modules/@sentry/types": {
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.15.0.tgz",
"integrity": "sha512-MN9haDRh9ZOsTotoDTHu2BT3sT8Vs1F0alhizUpDyjN2YgBCqR6JV+AbAE1XNHwS2+5zbppch1PwJUVeE58URQ==",
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.22.0.tgz",
"integrity": "sha512-LhCL+wb1Jch+OesB2CIt6xpfO1Ab6CRvoNYRRzVumWPLns1T3ZJkarYfhbLaOEIb38EIbPgREdxn2AJT560U4Q==",
"engines": {
"node": ">=8"
}
@@ -6281,14 +6284,14 @@
}
},
"node_modules/@stripe/react-stripe-js": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.10.0.tgz",
"integrity": "sha512-vuIjJUZJ3nyiaGa5z5iyMCzZfGGsgzOOjWjqknbbhkNsewyyginfeky9EZLSz9+iSAsgC9K6MeNOTLKVGcMycQ==",
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.16.3.tgz",
"integrity": "sha512-gS6UDGEuM92K50pFB3o//EqqPxmaqpC8IrsBda4P4LxeULoO0pBFSHXJ5Ab6uA7G2lyO2bluvSLereh0OH9GrQ==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@stripe/stripe-js": "^1.34.0",
"@stripe/stripe-js": "^1.44.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
@@ -8157,9 +8160,9 @@
}
},
"node_modules/axios-auth-refresh": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.3.tgz",
"integrity": "sha512-2IbDhJ/h6ddNBBnnzn1VFK/qx17pE9aVqiafB8rx5LVHsJ1HtFpUGkbXY7PzTG+8P9HJWcyA3fNZl9BikSuilg==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.6.tgz",
"integrity": "sha512-2CeBUce/SxIfFxow5/n8vApJ97yYF6qoV4gh1UrswT7aEOnlOdBLxxyhOI4IaxGs6BY0l8YujU2jlc4aCmK17Q==",
"peerDependencies": {
"axios": ">= 0.18 < 0.19.0 || >= 0.19.1"
}
@@ -9205,6 +9208,11 @@
"@colors/colors": "1.5.0"
}
},
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -13224,9 +13232,9 @@
}
},
"node_modules/i18next": {
"version": "22.4.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.6.tgz",
"integrity": "sha512-9Tm1ezxWyzV+306CIDMBbYBitC1jedQyYuuLtIv7oxjp2ohh8eyxP9xytIf+2bbQfhH784IQKPSYp+Zq9+YSbw==",
"version": "22.4.9",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.9.tgz",
"integrity": "sha512-8gWMmUz460KJDQp/ob3MNUX84cVuDRY9PLFPnV8d+Qezz/6dkjxwOaH70xjrCNDO+JrUL25iXfAIN9wUkInNZw==",
"funding": [
{
"type": "individual",
@@ -17079,11 +17087,11 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/posthog-js": {
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.34.0.tgz",
"integrity": "sha512-HkRwwzdz31N5ykQIO3SIkSS8nwhdqqnuDZ/qltitX4FhxrV9/tSRavEXz0YLvioOXeNVmQWtsN3krKajErwkwg==",
"version": "1.39.4",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.39.4.tgz",
"integrity": "sha512-Elpf1gwyuObueXi89iH+9pP+WhpkiivP8Qwej4RzOLwSTa7Floaa4rgAw7rnCnX1PtRoJ3F0kqb6q9T+aZjRiA==",
"dependencies": {
"@sentry/types": "^7.2.0",
"@sentry/types": "7.22.0",
"fflate": "^0.4.1",
"rrweb-snapshot": "^1.1.14"
}
@@ -20237,10 +20245,9 @@
}
},
"node_modules/styled-components": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz",
"integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==",
"hasInstallScript": true,
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.7.tgz",
"integrity": "sha512-JL1b4A79OGqav4TxkrNsuuQfy6ZnrpyQx6hBDQ3Hd3JyuR2IQuVNBpF+FCEWFNZpN5hj+fhkaEVWteVJ18f0tw==",
"dependencies": {
"@babel/helper-module-imports": "^7.0.0",
"@babel/traverse": "^7.4.5",
@@ -23946,10 +23953,12 @@
}
},
"@headlessui/react": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.6.6.tgz",
"integrity": "sha512-MFJtmj9Xh/hhBMhLccGbBoSk+sk61BlP6sJe4uQcVMtXZhCgGqd2GyIQzzmsdPdTEWGSF434CBi8mnhR6um46Q==",
"requires": {}
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.7.tgz",
"integrity": "sha512-BqDOd/tB9u2tA0T3Z0fn18ktw+KbVwMnkxxsGPIH2hzssrQhKB5n/6StZOyvLYP/FsYtvuXfi9I0YowKPv2c1w==",
"requires": {
"client-only": "^0.0.1"
}
},
"@hookform/resolvers": {
"version": "2.9.10",
@@ -24987,9 +24996,9 @@
"dev": true
},
"@sentry/types": {
"version": "7.15.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.15.0.tgz",
"integrity": "sha512-MN9haDRh9ZOsTotoDTHu2BT3sT8Vs1F0alhizUpDyjN2YgBCqR6JV+AbAE1XNHwS2+5zbppch1PwJUVeE58URQ=="
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.22.0.tgz",
"integrity": "sha512-LhCL+wb1Jch+OesB2CIt6xpfO1Ab6CRvoNYRRzVumWPLns1T3ZJkarYfhbLaOEIb38EIbPgREdxn2AJT560U4Q=="
},
"@sinclair/typebox": {
"version": "0.24.51",
@@ -26659,9 +26668,9 @@
}
},
"@stripe/react-stripe-js": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.10.0.tgz",
"integrity": "sha512-vuIjJUZJ3nyiaGa5z5iyMCzZfGGsgzOOjWjqknbbhkNsewyyginfeky9EZLSz9+iSAsgC9K6MeNOTLKVGcMycQ==",
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.16.3.tgz",
"integrity": "sha512-gS6UDGEuM92K50pFB3o//EqqPxmaqpC8IrsBda4P4LxeULoO0pBFSHXJ5Ab6uA7G2lyO2bluvSLereh0OH9GrQ==",
"requires": {
"prop-types": "^15.7.2"
}
@@ -28115,9 +28124,9 @@
}
},
"axios-auth-refresh": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.3.tgz",
"integrity": "sha512-2IbDhJ/h6ddNBBnnzn1VFK/qx17pE9aVqiafB8rx5LVHsJ1HtFpUGkbXY7PzTG+8P9HJWcyA3fNZl9BikSuilg==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/axios-auth-refresh/-/axios-auth-refresh-3.3.6.tgz",
"integrity": "sha512-2CeBUce/SxIfFxow5/n8vApJ97yYF6qoV4gh1UrswT7aEOnlOdBLxxyhOI4IaxGs6BY0l8YujU2jlc4aCmK17Q==",
"requires": {}
},
"axobject-query": {
@@ -28888,6 +28897,11 @@
"string-width": "^4.2.0"
}
},
"client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -31990,9 +32004,9 @@
"dev": true
},
"i18next": {
"version": "22.4.6",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.6.tgz",
"integrity": "sha512-9Tm1ezxWyzV+306CIDMBbYBitC1jedQyYuuLtIv7oxjp2ohh8eyxP9xytIf+2bbQfhH784IQKPSYp+Zq9+YSbw==",
"version": "22.4.9",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.9.tgz",
"integrity": "sha512-8gWMmUz460KJDQp/ob3MNUX84cVuDRY9PLFPnV8d+Qezz/6dkjxwOaH70xjrCNDO+JrUL25iXfAIN9wUkInNZw==",
"requires": {
"@babel/runtime": "^7.20.6"
}
@@ -34690,11 +34704,11 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"posthog-js": {
"version": "1.34.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.34.0.tgz",
"integrity": "sha512-HkRwwzdz31N5ykQIO3SIkSS8nwhdqqnuDZ/qltitX4FhxrV9/tSRavEXz0YLvioOXeNVmQWtsN3krKajErwkwg==",
"version": "1.39.4",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.39.4.tgz",
"integrity": "sha512-Elpf1gwyuObueXi89iH+9pP+WhpkiivP8Qwej4RzOLwSTa7Floaa4rgAw7rnCnX1PtRoJ3F0kqb6q9T+aZjRiA==",
"requires": {
"@sentry/types": "^7.2.0",
"@sentry/types": "7.22.0",
"fflate": "^0.4.1",
"rrweb-snapshot": "^1.1.14"
}
@@ -36986,9 +37000,9 @@
}
},
"styled-components": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.5.tgz",
"integrity": "sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==",
"version": "5.3.7",
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.7.tgz",
"integrity": "sha512-JL1b4A79OGqav4TxkrNsuuQfy6ZnrpyQx6hBDQ3Hd3JyuR2IQuVNBpF+FCEWFNZpN5hj+fhkaEVWteVJ18f0tw==",
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"@babel/traverse": "^7.4.5",

View File

@@ -20,7 +20,7 @@
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@fortawesome/react-fontawesome": "^0.1.19",
"@headlessui/react": "^1.6.6",
"@headlessui/react": "^1.7.7",
"@hookform/resolvers": "^2.9.10",
"@octokit/rest": "^19.0.7",
"@radix-ui/react-accordion": "^1.1.0",
@@ -37,14 +37,14 @@
"@radix-ui/react-tabs": "^1.0.2",
"@radix-ui/react-toast": "^1.1.2",
"@reduxjs/toolkit": "^1.8.3",
"@stripe/react-stripe-js": "^1.10.0",
"@stripe/react-stripe-js": "^1.16.3",
"@stripe/stripe-js": "^1.46.0",
"@types/argon2-browser": "^1.18.1",
"@tanstack/react-query": "^4.23.0",
"add": "^2.0.6",
"argon2-browser": "^1.18.0",
"axios": "^0.27.2",
"axios-auth-refresh": "^3.3.3",
"axios-auth-refresh": "^3.3.6",
"base64-loader": "^1.0.0",
"classnames": "^2.3.1",
"cookies": "^0.8.0",
@@ -52,13 +52,13 @@
"fs": "^0.0.1-security",
"gray-matter": "^4.0.3",
"http-proxy": "^1.18.1",
"i18next": "^22.4.6",
"i18next": "^22.4.9",
"jspdf": "^2.5.1",
"jsrp": "^0.2.4",
"markdown-it": "^13.0.1",
"next": "^12.3.4",
"next-i18next": "^13.0.2",
"posthog-js": "^1.34.0",
"posthog-js": "^1.39.4",
"query-string": "^7.1.3",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.1",
@@ -73,7 +73,7 @@
"react-table": "^7.8.0",
"set-cookie-parser": "^2.5.1",
"sharp": "^0.31.2",
"styled-components": "^5.3.5",
"styled-components": "^5.3.7",
"tailwind-merge": "^1.8.1",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",

View File

@@ -10,6 +10,7 @@ const integrationSlugNameMapping: Mapping = {
'vercel': 'Vercel',
'netlify': 'Netlify',
'github': 'GitHub',
'gitlab': 'GitLab',
'render': 'Render',
'flyio': 'Fly.io',
'circleci': 'CircleCI',

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Some files were not shown because too many files have changed in this diff Show More