Compare commits

..

866 Commits

Author SHA1 Message Date
Tuan Dang
8dbc894ce9 Replace insertMany operation with upsert for backfilling trusted ips 2023-07-26 20:45:58 +07:00
BlackMagiq
511904605f Merge pull request #786 from Infisical/debug-integrations
Fix PATCH IP whitelist behavior and breaking integrations due to incorrect project id in local storage
2023-07-26 17:55:24 +07:00
Tuan Dang
7ae6d1610f Fix IP whitelist PATCH endpoint, update localStorage project id to reflect navigated to project 2023-07-26 17:46:33 +07:00
Tuan Dang
7da6d72f13 Remove save call from backfilling trusted ips 2023-07-26 16:18:13 +07:00
Tuan Dang
ad33356994 Remove required comment for trusted IP schema 2023-07-26 15:29:43 +07:00
BlackMagiq
cfa2461479 Merge pull request #785 from Infisical/network-access
Add support for IP allowlisting / trusted IPs
2023-07-26 15:11:04 +07:00
Tuan Dang
bf08bfacb5 Fix lint errors 2023-07-26 15:06:18 +07:00
Tuan Dang
cf77820059 Merge remote-tracking branch 'origin' into network-access 2023-07-26 14:55:12 +07:00
Tuan Dang
1ca90f56b8 Add docs for IP allowlisting 2023-07-26 14:51:25 +07:00
Tuan Dang
5899d7aee9 Complete trusted IPs feature 2023-07-26 13:34:56 +07:00
Maidul Islam
b565194c43 create versions for brew releases 2023-07-25 15:39:29 -04:00
Maidul Islam
86e04577c9 print exec error messages as is 2023-07-25 14:17:31 -04:00
Vladyslav Matsiiako
54c79012db fix the org-members link 2023-07-25 07:01:27 -07:00
Maidul Islam
4b720bf940 Update kubernetes.mdx 2023-07-24 18:13:35 -04:00
Maidul Islam
993866bb8b Update secret-reference.mdx 2023-07-24 17:18:07 -04:00
Maidul Islam
8c39fa2438 add conditional imports to raw api 2023-07-24 15:01:31 -04:00
Maidul Islam
7bccfaefac Merge pull request #784 from akhilmhdh/fix/import-delete
fix: resolved secret import delete and include_import response control
2023-07-24 11:47:02 -04:00
akhilmhdh
e2b666345b fix: resolved secret import delete and include_import response control 2023-07-24 20:53:59 +05:30
Maidul Islam
90910819a3 Merge pull request #778 from afrieirham/docs/running-docs-locally
docs: add running infisical docs locally guide
2023-07-24 09:00:46 -04:00
Afrie Irham
8b070484dd docs: add running infisical docs locally 2023-07-24 20:36:42 +08:00
BlackMagiq
a764087c83 Merge pull request #782 from Infisical/improve-security-docs
Add section on service token best practices
2023-07-24 18:14:26 +07:00
Tuan Dang
27d5fa5aa0 Add section on service token best practices 2023-07-24 18:10:37 +07:00
Tuan Dang
2e7705999c Updated changelog and contributors in README 2023-07-24 12:56:02 +07:00
Vladyslav Matsiiako
428bf8e252 Merge branch 'main' of https://github.com/Infisical/infisical 2023-07-23 13:59:42 -07:00
Vladyslav Matsiiako
264740d84d style updates 2023-07-23 13:59:25 -07:00
Maidul Islam
723bcd4d83 Update react.mdx 2023-07-23 15:44:54 -04:00
Tuan Dang
9ed516ccb6 Uncomment Google SSO for signup 2023-07-24 02:35:51 +07:00
Tuan Dang
067ade94c8 Merge branch 'main' of https://github.com/Infisical/infisical 2023-07-24 01:54:09 +07:00
Tuan Dang
446edb6ed9 Add CLI support for SAML SSO 2023-07-24 01:53:56 +07:00
Maidul Islam
896529b7c6 auto scope raw secrets GET with service token 2023-07-23 12:31:03 -04:00
BlackMagiq
5c836d1c10 Merge pull request #779 from afrieirham/integration/digital-ocean-app-platform
Digital Ocean App Platform Integration
2023-07-23 23:01:35 +07:00
Tuan Dang
409d46aa10 Fix merge conflicts 2023-07-23 22:55:18 +07:00
Tuan Dang
682c63bc2a Fix DigitalOcean getApps case where there are no apps 2023-07-23 22:42:41 +07:00
BlackMagiq
1419371588 Merge pull request #776 from afrieirham/integration/cloud66
Cloud 66 integration
2023-07-23 22:19:23 +07:00
Tuan Dang
77fdb6307c Optimize Cloud66 integration sync function 2023-07-23 22:16:27 +07:00
Afrie Irham
c61bba2b6b docs: add digital ocean app platform integration guide 2023-07-23 22:39:08 +08:00
Afrie Irham
2dc0563042 view: fix integration name only show 3 words 2023-07-23 22:02:59 +08:00
Afrie Irham
b5fb2ef354 feat: DO app platform integration 2023-07-23 22:01:48 +08:00
Tuan Dang
dc01758946 Update SAML Okta docs screenshots 2023-07-23 16:59:08 +07:00
BlackMagiq
1f8683f59e Merge pull request #777 from Infisical/saml-docs
Add docs for Okta SAML 2.0 SSO
2023-07-23 16:49:47 +07:00
Tuan Dang
a5273cb86f Add docs for Okta SAML 2.0 SSO 2023-07-23 16:47:43 +07:00
Afrie Irham
d48b5157d4 docs: add cloud 66 integration guide 2023-07-23 17:39:29 +08:00
Afrie Irham
94a23bfa23 feat: add cloud 66 integration 2023-07-23 16:36:26 +08:00
Tuan Dang
fcdfa424bc Restrict changing user auth methods if SAML SSO is enforced 2023-07-23 15:19:17 +07:00
BlackMagiq
3fba1b3ff7 Merge pull request #774 from Infisical/saml-sso-edge-cases
Block inviting members to organization if SAML SSO is configured
2023-07-23 13:27:30 +07:00
Tuan Dang
953eed70b2 Add back attribution source for non-SAML SSO case 2023-07-23 13:24:12 +07:00
Tuan Dang
39ba795604 Block inviting members to organization if SAML SSO is configured 2023-07-23 13:05:37 +07:00
BlackMagiq
5b36227321 Merge pull request #773 from Infisical/debug-google-sso
Initialize organization bot upon creating organization
2023-07-23 12:07:45 +07:00
Tuan Dang
70d04be978 Initialize organization bot upon creating organization 2023-07-23 12:03:39 +07:00
BlackMagiq
565f234921 Merge pull request #772 from Infisical/switch-to-google-sso
Add user support for changing authentication methods
2023-07-22 12:38:22 +07:00
Tuan Dang
ab43e32982 Add user support for changing auth methods 2023-07-22 12:33:57 +07:00
Maidul Islam
be677fd6c2 disable token error 2023-07-21 18:41:32 -04:00
Maidul Islam
3d93c6a995 add sentry error to integ 2023-07-21 17:45:27 -04:00
Maidul Islam
edb201e11f comment out unused import 2023-07-21 17:33:46 -04:00
Maidul Islam
1807b3e029 add logs for integration and comment out google sso 2023-07-21 17:29:56 -04:00
Tuan Dang
c02c8e67d3 Merge branch 'main' of https://github.com/Infisical/infisical 2023-07-21 23:54:03 +07:00
Tuan Dang
d4c5be5f48 Update file casing 2023-07-21 23:53:50 +07:00
Tuan Dang
5f33c9a389 Update file casing 2023-07-21 23:53:16 +07:00
BlackMagiq
c9acb22261 Merge pull request #770 from Infisical/docs
Add/revise docs for Codefresh and Bitbucket integrations
2023-07-21 23:44:57 +07:00
Tuan Dang
33f0510995 Add docs for Codefresh integration, revise docs for Bitbucket integration 2023-07-21 23:41:04 +07:00
BlackMagiq
25b239a18b Merge pull request #755 from zwkee/integration/bitbucket
BitBucket Integration
2023-07-21 20:55:42 +07:00
Tuan Dang
504e0f6dc3 Fix lint issues backend 2023-07-21 20:52:35 +07:00
Tuan Dang
f450be3a00 Fix merge conflicts 2023-07-21 20:49:41 +07:00
Tuan Dang
d9f6c27e4d Update Bitbucket sync function 2023-07-21 20:16:39 +07:00
BlackMagiq
9cef35e9e6 Merge pull request #769 from Infisical/saml
Add Google SSO and SAML SSO (Okta)
2023-07-21 18:02:10 +07:00
Tuan Dang
2621ccdcf1 Add descriptions for SSO endpoints 2023-07-21 17:59:15 +07:00
Tuan Dang
75e90201c0 Lint and move redirectSSO into controller 2023-07-21 17:54:09 +07:00
Tuan Dang
fd3cf70e13 Add Google SSO 2023-07-21 17:48:36 +07:00
Tuan Dang
44108621b4 Run linter, fix import error 2023-07-21 15:01:28 +07:00
Tuan Dang
5ee65359bf Fix merge conflicts 2023-07-21 14:37:13 +07:00
Tuan Dang
241dceb845 Remove bodyparser and audit fix deps 2023-07-21 13:39:07 +07:00
Maidul Islam
af650ef4c7 patch env delete bug 2023-07-20 20:03:01 -04:00
Maidul Islam
817ddd228c Update overview.mdx 2023-07-20 19:15:58 -04:00
Maidul Islam
15d81233b4 update docs overvew 2023-07-20 18:25:54 -04:00
Maidul Islam
705b1833d0 update CLI usage and docs for pinning docker 2023-07-20 18:18:43 -04:00
Maidul Islam
beb8d2634a add docs to pin cli 2023-07-20 17:59:59 -04:00
Maidul Islam
fb3ceb4581 Revamp docker docs 2023-07-20 17:28:33 -04:00
Maidul Islam
2df33dc84f Merge pull request #764 from akhilmhdh/fix/include-optional
made include_imports optional in raw secrets fetch
2023-07-20 09:54:47 -04:00
akhilmhdh
043133444d fix: made include_imports optional in raw secrets fetch 2023-07-20 14:18:35 +05:30
BlackMagiq
df25657715 Merge pull request #760 from chisom5/feature-codefresh-integration
Codefresh integration
2023-07-20 00:14:27 +07:00
chisom okoye
79c2baba1a Merge branch 'Infisical:main' into feature-codefresh-integration 2023-07-19 17:39:45 +01:00
Maidul Islam
52a2a782f1 Merge pull request #762 from akhilmhdh/fix/sec-import-fail
fix: resolved empty secrets on fresh env and added empty states
2023-07-19 12:38:01 -04:00
Tuan Dang
eda095b55f Fix merge conflicts 2023-07-19 23:29:01 +07:00
akhilmhdh
93761f6487 fix: resolved empty secrets on fresh env and added empty states 2023-07-19 21:58:03 +05:30
Tuan Dang
c5438fbe6d Fix merge conflicts 2023-07-19 23:25:52 +07:00
Tuan Dang
e8fdaf571c Make sync function for Codefresh 2023-07-19 23:17:59 +07:00
Tuan Dang
846e2e037f Update 2023-07-19 22:23:48 +07:00
chisom okoye
a0a7ff8715 Codefresh integration
Worked on codefresh integration syncing secrets to infiscial
2023-07-19 16:22:25 +01:00
Maidul Islam
284608762b update secret import docs 2023-07-19 00:57:35 -04:00
vmatsiiako
8960773150 Update overview.mdx 2023-07-18 21:51:18 -07:00
Maidul Islam
4684c9f8b1 Update secret-reference.mdx 2023-07-19 00:40:32 -04:00
Maidul Islam
abbf3e80f9 Update secret-reference.mdx 2023-07-19 00:31:45 -04:00
Maidul Islam
d272f580cf update k8 helm for import feature 2023-07-19 00:25:22 -04:00
Maidul Islam
da9cb70184 only send risk notif when risks are found 2023-07-19 00:05:19 -04:00
Maidul Islam
1f3f0375b9 add secret import to k8 operator 2023-07-18 23:59:03 -04:00
Vladyslav Matsiiako
8ad851d4b0 added the ability to change user name 2023-07-18 18:36:34 -07:00
Maidul Islam
3b5bc151ba Merge pull request #758 from akhilmhdh/feat/secret-import
Implemented secret link/import feature
2023-07-18 16:58:46 -04:00
Maidul Islam
678cdd3308 Merge branch 'main' into feat/secret-import 2023-07-18 16:52:25 -04:00
Maidul Islam
33554f4057 patch bug when imports don't show with no secrets 2023-07-18 15:32:14 -04:00
Maidul Islam
c539d4d243 remove print 2023-07-18 15:31:31 -04:00
akhilmhdh
124e6dd998 feat(secret-import): added workspace validation for get imports and imported secret api 2023-07-18 19:40:35 +05:30
Vladyslav Matsiiako
cef29f5dd7 minor style update 2023-07-17 21:39:05 -07:00
Maidul Islam
95c914631a patch notify user on risk found 2023-07-17 21:52:24 -04:00
Maidul Islam
49ae61da08 remove border from risk selection 2023-07-17 21:49:58 -04:00
Maidul Islam
993abd0921 add secret scanning status to api 2023-07-17 21:28:47 -04:00
Maidul Islam
f37b497e48 Update overview.mdx 2023-07-17 21:11:27 -04:00
Maidul Islam
0d2e55a06f add telemetry for cloud secret scanning 2023-07-17 20:29:20 -04:00
Maidul Islam
040243d4f7 add telemetry for cloud secret scanning 2023-07-17 20:29:07 -04:00
Maidul Islam
c450b01763 update email for secret leak 2023-07-17 20:20:11 -04:00
Maidul Islam
4cd203c194 add ss-webhook to values file k8-infisical 2023-07-17 19:56:07 -04:00
Maidul Islam
178d444deb add web hook under api temporarily 2023-07-17 18:58:39 -04:00
Maidul Islam
139ca9022e Update build-staging-img.yml 2023-07-17 17:36:57 -04:00
Maidul Islam
34d3e80d17 Merge pull request #743 from Infisical/git-scanning-app
bring back secret engine for dev
2023-07-17 17:21:34 -04:00
Maidul Islam
deac5fe101 Merge branch 'main' into git-scanning-app 2023-07-17 17:20:04 -04:00
Maidul Islam
216f3a0d1b reload page after org link 2023-07-17 17:18:55 -04:00
Maidul Islam
43f4110c94 update risk status names 2023-07-17 16:46:15 -04:00
Maidul Islam
56d430afd6 update risk status and update email notifications 2023-07-17 16:41:33 -04:00
akhilmhdh
f681f0a98d fix(secret-import): resolved build failure in frontend 2023-07-18 00:29:42 +05:30
akhilmhdh
23cd6fd861 doc(secret-imports): updated docs for secret import 2023-07-17 23:10:42 +05:30
akhilmhdh
cf45c3dc8b feat(secret-import): updated cli to support secret import 2023-07-17 23:10:14 +05:30
akhilmhdh
45584e0c1a feat(secret-import): implemented ui for secret import 2023-07-17 23:08:57 +05:30
akhilmhdh
202900a7a3 feat(secret-import): implemented api for secret import 2023-07-17 23:08:42 +05:30
Maidul Islam
38b6a48bee Merge pull request #754 from JunedKhan101/docs-typo-fix
fixed typo
2023-07-17 10:49:46 -04:00
Maidul Islam
53abce5780 remove secret engine folder 2023-07-16 16:51:01 -04:00
Maidul Islam
8c844fb188 move secret scanning to main container 2023-07-16 16:48:36 -04:00
Kee, Zhen Wei
df7ad9e645 feat(integration): add integration with BitBucket 2023-07-16 22:04:51 +08:00
Juned Khan
a9135cdbcd fixed typo 2023-07-16 14:47:35 +05:30
Akhil Mohan
9b96daa185 Merge pull request #752 from afrieirham/feat/sort-integrations-alphabetically
feat: sort cloud and framework integrations alphabetically
2023-07-16 14:34:26 +05:30
Afrie Irham
9919d3ee6a feat: sort cloud and framework integrations alphabetically 2023-07-16 11:05:37 +08:00
Vladyslav Matsiiako
dfcd6b1efd changed docs structure 2023-07-14 19:14:36 -07:00
Vladyslav Matsiiako
07bc4c4a3a change docs structure 2023-07-14 19:11:39 -07:00
Vladyslav Matsiiako
d69465517f Added styling 2023-07-14 16:26:18 -07:00
Maidul Islam
6d807c0c74 Merge pull request #749 from RezaRahemtola/fix/cli-vault-cmd-last-line-break
fix(cli): Missing trailing linebreak in vault commands
2023-07-14 18:38:23 -04:00
Reza Rahemtola
868cc80210 fix(cli): Missing trailing linebreak in vault commands 2023-07-14 23:09:25 +02:00
Maidul Islam
3d4a616147 remove secret scanning from prod docker compose 2023-07-14 15:21:04 -04:00
vmatsiiako
bd3f9130e4 Merge pull request #747 from unkletayo/adetayoreadme-youtubelink-fix
docs(readme):update broken YouTube  page link
2023-07-14 09:19:51 -07:00
Adetayo Akinsanya
f607841acf Update README.md with the correct youtube link 2023-07-14 17:15:09 +01:00
Adetayo Akinsanya
55d813043d Update README.md
This PR fixes broken link to the YouTube page in the Readme file
2023-07-14 08:15:51 +01:00
Vladyslav Matsiiako
b2a3a3a0e6 added click-to-copy and changed the slack link 2023-07-13 19:09:00 -07:00
Maidul Islam
67d5f52aca extract correct params after git app install 2023-07-13 19:56:49 -04:00
Vladyslav Matsiiako
a34047521c styled cli redirect 2023-07-13 16:37:05 -07:00
Vladyslav Matsiiako
7ff806e8a6 fixed the signup orgId issue 2023-07-13 16:16:00 -07:00
Vladyslav Matsiiako
9763353d59 Fixed routing issues 2023-07-13 16:09:33 -07:00
Maidul Islam
4382935cb5 Merge pull request #733 from akhilmhdh/feat/webhooks
Feat/webhooks
2023-07-13 18:47:47 -04:00
Maidul Islam
7e3646ddcd add docs on how to pin k8 operator to avoid breaking changes 2023-07-13 17:53:59 -04:00
akhilmhdh
f7766fc182 fix: resolved just space in a secret value and not changing save state 2023-07-13 23:53:24 +05:30
akhilmhdh
3176370ef6 feat(webhook): removed console.log 2023-07-13 23:22:20 +05:30
akhilmhdh
9bed1682fc feat(webhooks): updated docs 2023-07-13 23:22:20 +05:30
akhilmhdh
daf2e2036e feat(webhook): implemented ui for webhooks 2023-07-13 23:22:20 +05:30
akhilmhdh
0f81c78639 feat(webhook): implemented api for webhooks 2023-07-13 23:21:18 +05:30
Vladyslav Matsiiako
8a19cfe0c6 removed secret scanning from the menu 2023-07-13 10:31:54 -07:00
Maidul Islam
a00fec9bca trigger standalone docker img too 2023-07-13 11:23:41 -04:00
BlackMagiq
209f224517 Merge pull request #745 from Infisical/docs-sdk
Remove individual SDK pages from docs
2023-07-13 17:10:26 +07:00
Tuan Dang
0b7f2b7d4b Remove individual SDK pages from docs in favor of each SDKs README on GitHub 2023-07-13 17:08:32 +07:00
BlackMagiq
eff15fc3d0 Merge pull request #744 from Infisical/usage-billing
Fix subscription context get organization from useOrganization
2023-07-13 17:07:42 +07:00
Tuan Dang
2614459772 Fix subscription context get organization from useOrganization 2023-07-13 17:01:53 +07:00
Vladyslav Matsiiako
4e926746cf fixing the pro trial bug 2023-07-12 15:46:42 -07:00
Maidul Islam
f022f6d3ee update secret engine port 2023-07-12 16:39:45 -04:00
Maidul Islam
1133ae4ae9 bring back secret engine for dev 2023-07-12 16:10:09 -04:00
Maidul Islam
edd5afa13b remove secret engine from main 2023-07-12 15:50:36 -04:00
vmatsiiako
442f572acc Merge branch 'infisical-radar-app' into main 2023-07-12 12:12:24 -07:00
Vladyslav Matsiiako
be58f3c429 removed the learning item from sidebar 2023-07-12 11:50:36 -07:00
vmatsiiako
3eea5d9322 Merge pull request #735 from Infisical/new-sidebars
fixing the bugs with sidebars
2023-07-12 11:23:26 -07:00
Vladyslav Matsiiako
e4e87163e8 removed org member section 2023-07-12 11:19:56 -07:00
Vladyslav Matsiiako
d3aeb729e0 fixing ui/ux bugs 2023-07-12 11:18:42 -07:00
Maidul Islam
2e7c7cf1da fix typo in folder docs 2023-07-12 01:41:14 -04:00
Maidul Islam
5d39416532 replace cli quick start 2023-07-12 01:38:59 -04:00
Maidul Islam
af95adb589 Update usage.mdx 2023-07-12 01:31:09 -04:00
Maidul Islam
0fc4f96773 Merge pull request #736 from Infisical/revamp-docs
Revamp core docs
2023-07-12 01:29:10 -04:00
Maidul Islam
0a9adf33c8 revamp core docs 2023-07-12 01:23:28 -04:00
Vladyslav Matsiiako
f9110cedfa fixing the bug with switching orgs 2023-07-11 22:13:54 -07:00
vmatsiiako
88ec55fc49 Merge pull request #700 from Infisical/new-sidebars
new sidebars
2023-07-11 17:29:48 -07:00
Vladyslav Matsiiako
98b2a2a5c1 adding trial to the sidebar 2023-07-11 17:26:36 -07:00
vmatsiiako
27eeafbf36 Merge pull request #730 from Infisical/main
Catching up the branch
2023-07-11 16:19:39 -07:00
Vladyslav Matsiiako
0cf63028df fixing style and solving merge conflicts 2023-07-11 16:19:07 -07:00
vmatsiiako
0b52b3cf58 Update mint.json 2023-07-11 14:14:23 -07:00
vmatsiiako
e1764880a2 Update overview.mdx 2023-07-11 14:09:57 -07:00
vmatsiiako
d3a47ffcdd Update mint.json 2023-07-11 13:56:24 -07:00
vmatsiiako
9c1f88bb9c Update mint.json 2023-07-11 13:49:55 -07:00
Maidul Islam
ae2f3184e2 Merge pull request #711 from afrieirham/form-ux-enhancement
fix: enable users to press `Enter` in forms
2023-07-11 16:34:21 -04:00
BlackMagiq
3f1db47c30 Merge pull request #731 from Infisical/office-365-smtp
Add support for Office365 SMTP
2023-07-11 15:04:26 +07:00
Tuan Dang
3e3bbe298d Add support for Office365 SMTP 2023-07-11 14:50:41 +07:00
Vladyslav Matsiiako
46dc357651 final changes to sidebars 2023-07-11 00:04:14 -07:00
Maidul Islam
07d25cb673 extract version from tag 2023-07-10 23:26:14 -04:00
Maidul Islam
264f75ce8e correct gha for k8 operator 2023-07-10 23:20:45 -04:00
Maidul Islam
9713a19405 add semvar to k8 images 2023-07-10 23:14:10 -04:00
Maidul Islam
ccfb8771f1 Merge pull request #728 from JunedKhan101/feature-723-remove-trailing-slash
Implemented feature to remove the trailing slash from the domain url
2023-07-10 10:26:53 -04:00
BlackMagiq
b36801652f Merge pull request #729 from Infisical/trial-revamp
Infisical Cloud Pro Free Trial Update
2023-07-10 15:13:28 +07:00
Tuan Dang
9e5b9cbdb5 Fix lint errors 2023-07-10 15:06:00 +07:00
Vladyslav Matsiiako
bdf4ebd1bc second iteration of the new sidebar 2023-07-09 23:58:27 -07:00
Tuan Dang
e91e7f96c2 Update free plan logic 2023-07-10 13:48:46 +07:00
Juned Khan
34fef4aaad Implemented feature to remove the trailing slash from the domain url 2023-07-10 12:16:51 +05:30
Maidul Islam
09330458e5 Merge pull request #721 from agoodman1999/main
add --path flag to docs for infisical secrets set
2023-07-10 00:09:09 -04:00
Maidul Islam
ed95b99ed1 Merge branch 'main' into main 2023-07-10 00:08:25 -04:00
Maidul Islam
dc1e1e8dcb Merge pull request #726 from RezaRahemtola/fix/docs
fix(docs): Wrong integration name and missing link
2023-07-10 00:05:47 -04:00
Maidul Islam
13a81c9222 add 401 error message for get secrets in cli 2023-07-09 23:25:35 -04:00
Maidul Islam
6354464859 update terraform docs with path and env 2023-07-09 22:40:00 -04:00
vmatsiiako
ec26404b94 Merge pull request #727 from Infisical/main
Catching up with main
2023-07-09 11:13:40 -07:00
Reza Rahemtola
5ef2508736 docs: Add missing pull request contribution link 2023-07-09 15:44:25 +02:00
Reza Rahemtola
93264fd2d0 docs: Fix wrong integration name 2023-07-09 15:40:59 +02:00
Afrie Irham
7020c7aeab fix: completing allow user to press Enter in forgot password flow 2023-07-09 15:08:25 +08:00
Maidul Islam
25b1673321 improve k8 operator docs 2023-07-08 21:48:06 -04:00
Maidul Islam
628bc711c2 update k8 docks for quick start 2023-07-08 21:12:05 -04:00
Maidul Islam
a3b4228685 add path to export command 2023-07-08 16:15:45 -04:00
Maidul Islam
374c8e4a1a Update ingress class values.yaml 2023-07-08 13:47:13 -04:00
Maidul Islam
5afcf2798f Update build-staging-img.yml 2023-07-08 13:32:35 -04:00
Maidul Islam
1657cf0a7e Update values.yaml 2023-07-08 13:16:10 -04:00
Maidul Islam
c9820d0071 Update values.yaml 2023-07-08 12:55:49 -04:00
Maidul Islam
b53c046eef Merge pull request #713 from akhilmhdh/feat/secret-reference
secret reference
2023-07-07 19:02:57 -04:00
Maidul Islam
fd10d7ed34 add docs for k8 secret refs 2023-07-07 18:59:23 -04:00
Maidul Islam
c5aae44249 add docs for k8 secret refs 2023-07-07 18:56:38 -04:00
Maidul Islam
83aa6127ec update k8 chart version 2023-07-07 15:56:47 -04:00
Maidul Islam
5a2299f758 update k8 operator crd for secret refs 2023-07-07 15:55:45 -04:00
Maidul Islam
57cdab0727 update k8 operator crd for secret refs 2023-07-07 15:55:22 -04:00
Maidul Islam
f82fa1b3b3 add secret reference support 2023-07-07 15:49:21 -04:00
Tuan Dang
e95eef2071 Merge branch 'main' of https://github.com/Infisical/infisical 2023-07-07 13:01:51 +07:00
Tuan Dang
53efdac0f0 Bring back catch TokenExpiredError in backend error-handling middleware 2023-07-07 13:01:38 +07:00
BlackMagiq
f5eafc39c5 Merge pull request #717 from atimapreandrew/add-laravel-forge-docs
Added docs for Laravel Forge Integration
2023-07-07 12:14:47 +07:00
Tuan Dang
0f72ccf82e Remove Laravel Forge from self-hosting docs, update image name 2023-07-07 12:13:47 +07:00
vmatsiiako
c191eb74fd Update README.md 2023-07-06 21:39:05 -07:00
agoodman1999
f9fca42c5b fix incorrect leading slash in example 2023-07-06 13:36:15 -04:00
agoodman1999
11a19eef07 add --path flag to docs for infisical secrets set 2023-07-06 13:20:48 -04:00
akhilmhdh
8a237af4ac feat(secret-ref): updated reference corner cases of trailing slashes 2023-07-06 22:15:10 +05:30
Andrew Atimapre
24413e1edd Added docs for Laravel Forge Integration 2023-07-06 15:43:43 +01:00
akhilmhdh
5aba0c60b8 feat(secret-ref): removed migration field unset op, refactored service token scope check to a utility fn 2023-07-06 20:01:46 +05:30
akhilmhdh
5599132efe fix(secret-ref): resolved service token unable to fetch secrets in cli 2023-07-06 18:58:48 +05:30
vmatsiiako
7f9e27e3d3 Update README.md 2023-07-05 15:41:38 -07:00
vmatsiiako
7d36360111 Updated AWS deploy image 2023-07-05 15:34:22 -07:00
vmatsiiako
d350297ce1 Deploy to AWS button updated 2023-07-05 15:28:17 -07:00
vmatsiiako
18d4e42d1f Update README.md 2023-07-05 15:19:54 -07:00
Maidul Islam
9faf5a3d5c add secret scanning to gamma values 2023-07-05 18:17:09 -04:00
Maidul Islam
da113612eb diable secret scan by default 2023-07-05 18:09:46 -04:00
Maidul Islam
e9e2eade89 update helm chart version 2023-07-05 17:56:30 -04:00
Maidul Islam
3cbc9c1b5c update helm chart to include git app 2023-07-05 17:54:29 -04:00
Maidul Islam
0772510e47 update gha for git app gamma deploy 2023-07-05 15:52:43 -04:00
Maidul Islam
f389aa07eb update docker file for prod build 2023-07-05 15:39:44 -04:00
Maidul Islam
27a110a93a build secret scanning 2023-07-05 15:22:29 -04:00
akhilmhdh
13eaa4e9a1 feat(secret-ref): updated doc 2023-07-05 23:00:17 +05:30
akhilmhdh
7ec7d05fb0 feat(secret-ref): implemented cli changes for secret reference 2023-07-05 23:00:17 +05:30
akhilmhdh
7fe4089bb0 feat(secret-ref): implemented ui for service token changes 2023-07-05 23:00:17 +05:30
akhilmhdh
0cee453202 feat(secret-ref): implemented backend changes for multi env and folder in service token 2023-07-05 23:00:17 +05:30
BlackMagiq
088d8097a9 Merge pull request #712 from atimapreandrew/laravel-forge-integration
Laravel forge integration
2023-07-05 23:43:43 +07:00
Tuan Dang
4e6fae03ff Patch sync Laravel Forge integration 2023-07-05 23:40:43 +07:00
Andrew Atimapre
732d0dfdca Added docs for Laravel Forge Integration 2023-07-05 13:45:10 +01:00
Afrie Irham
93e0232c21 fix: allow user to press Enter in forgot password page 2023-07-05 19:02:48 +08:00
Afrie Irham
37707c422a fix: allow user to press Enter in login page 2023-07-05 18:40:48 +08:00
Afrie Irham
2f1bd9ca61 fix: enable user to press Enter in signup flow 2023-07-05 18:32:03 +08:00
Tuan Dang
3d9ddbf9bc Merge branch 'main' of https://github.com/Infisical/infisical 2023-07-05 13:52:06 +07:00
Tuan Dang
7c9140dcec Update trial message 2023-07-05 13:51:50 +07:00
Maidul Islam
a63d179a0d add email notifications for risks 2023-07-04 22:06:29 -04:00
Maidul Islam
95dd8718bd Merge pull request #709 from raykeating/add-path-flag-to-infisical-run-docs
add --path flag to docs
2023-07-04 20:25:56 -04:00
Ray Keating
ff2c9e98c0 add --path flag to docs 2023-07-04 19:48:36 -04:00
Andrew Atimapre
23f4a350e7 Added docs for Laravel Forge Integration 2023-07-04 21:08:15 +01:00
Andrew Atimapre
696225d8d2 laravel forge integration 2023-07-04 20:01:49 +01:00
Andrew Atimapre
6c1ccc17b3 laravel forge integration 2023-07-04 19:28:42 +01:00
Andrew Atimapre
aa60f3a664 Merge branch 'main' of github.com:atimapreandrew/infisical 2023-07-04 17:49:08 +01:00
Maidul Islam
f01fb2830a patch Eslint GetToken issue 2023-07-04 11:11:05 -04:00
Maidul Islam
9f6aa6b13e add v1 secret scanning 2023-07-04 10:54:44 -04:00
BlackMagiq
b2ee15a4ff Merge pull request #708 from Infisical/free-trial
Initialize users on Infisical Cloud to Pro (Trial) Tier
2023-07-04 16:26:05 +07:00
Tuan Dang
42de0fbe73 Fix lint errors 2023-07-04 16:22:06 +07:00
Tuan Dang
553c986aa8 Update free trial indicator in usage and billing page 2023-07-04 16:01:20 +07:00
vmatsiiako
9a1e2260a0 Merge pull request #701 from Infisical/main
Update branch
2023-06-30 16:54:26 -07:00
Andrew Atimapre
98f7ce2585 Merge branch 'main' of github.com:atimapreandrew/infisical 2023-06-30 17:55:22 +01:00
BlackMagiq
c30ec8cb5f Merge pull request #697 from Infisical/revamp-project-settings
Standardize styling of Project Settings Page
2023-06-30 16:44:02 +07:00
Tuan Dang
104c752f9a Finish preliminary standardization of project settings page 2023-06-30 16:38:54 +07:00
Maidul Islam
b66bea5671 Merge pull request #692 from akhilmhdh/feat/multi-line-secrets
multi line support for secrets
2023-06-29 17:35:25 -04:00
Maidul Islam
f9313204a7 add docs for k8 re sync interval 2023-06-29 16:08:43 -04:00
Maidul Islam
cb5c371a4f add re-sync interval 2023-06-29 15:02:53 -04:00
BlackMagiq
a32df58f46 Merge pull request #695 from Infisical/check-rbac
Rewire RBAC paywall to new mechanism
2023-06-29 18:53:07 +07:00
Tuan Dang
e2658cc8dd Rewire RBAC paywall to new mechanism 2023-06-29 18:47:35 +07:00
BlackMagiq
1fbec20c6f Merge pull request #694 from Infisical/clean-org-settings
Clean Personal Settings and Organization Settings Pages
2023-06-29 18:19:24 +07:00
Tuan Dang
ddff8be53c Fix build error 2023-06-29 18:15:59 +07:00
Tuan Dang
114d488345 Fix merge conflicts 2023-06-29 17:53:33 +07:00
Tuan Dang
c4da5a6ead Fix merge conflicts 2023-06-29 17:49:01 +07:00
Tuan Dang
056f5a4555 Finish preliminary making user settings, org settings styling similar to usage and billing page 2023-06-29 17:47:23 +07:00
Vladyslav Matsiiako
dfc88d99f6 first draft new sidebar 2023-06-28 14:28:52 -07:00
Andrew Atimapre
033f41a7d5 Merge branch 'main' of github.com:atimapreandrew/infisical 2023-06-28 19:15:08 +01:00
akhilmhdh
5612a01039 fix(multi-line): resolved linting issues 2023-06-28 20:50:02 +05:30
akhilmhdh
f1d609cf40 fix: resolved secret version empty 2023-06-28 20:32:12 +05:30
akhilmhdh
0e9c71ae9f feat(multi-line): added support for multi-line in ui 2023-06-28 20:32:12 +05:30
Maidul Islam
d1af399489 Merge pull request #684 from akhilmhdh/feat/integrations-page-revamp
integrations page revamp
2023-06-27 17:50:49 -04:00
Maidul Islam
f445bac42f swap out for v3 secrets 2023-06-27 17:20:30 -04:00
Maidul Islam
798f091ff2 fix fetching secrets via service token 2023-06-27 15:00:03 -04:00
akhilmhdh
8381944bb2 feat(integrations-page): fixed id in delete modal 2023-06-27 23:56:43 +05:30
Tuan Dang
f9d0e0d971 Replace - with Unlimited in compare plans table 2023-06-27 22:00:13 +07:00
Tuan Dang
29d50f850b Correct current plan text in usage and billing 2023-06-27 19:01:31 +07:00
Tuan Dang
81c69d92b3 Restyle org name change section 2023-06-27 18:48:26 +07:00
BlackMagiq
5cd9f37fdf Merge pull request #687 from Infisical/paywalls
Add paywall for PIT and redirect paywall to contact sales in self-hosted
2023-06-27 17:49:42 +07:00
Tuan Dang
1cf65aca1b Remove print statement 2023-06-27 17:46:36 +07:00
Tuan Dang
470c429bd9 Merge remote-tracking branch 'origin' into paywalls 2023-06-27 17:46:18 +07:00
Tuan Dang
c8d081e818 Remove print statement 2023-06-27 17:45:20 +07:00
Tuan Dang
492c6a6f97 Fix lint errors 2023-06-27 17:30:37 +07:00
Tuan Dang
1dfd18e779 Add paywall for PIT and redirect paywall to contact sales in self-hosted 2023-06-27 17:19:33 +07:00
BlackMagiq
caed17152d Merge pull request #686 from Infisical/org-settings
Revamped organization usage and billing page for Infisical Cloud
2023-06-27 16:16:02 +07:00
Tuan Dang
825143f17c Adjust breadcrumb spacing 2023-06-27 16:12:18 +07:00
Tuan Dang
da144b4d02 Hide usage and billing from Navbar in self-hosted 2023-06-27 15:56:48 +07:00
Tuan Dang
f4c4545099 Merge remote-tracking branch 'origin' into org-settings 2023-06-27 15:39:51 +07:00
Tuan Dang
924a969307 Fix lint errors for revamped billing and usage page 2023-06-27 15:39:36 +07:00
Vladyslav Matsiiako
072f6c737c UI update to inetgrations 2023-06-26 18:08:00 -07:00
akhilmhdh
5f683dd389 feat(integrations-page): updated current integrations width and fixed id in delete modal 2023-06-26 14:31:13 +05:30
Tuan Dang
2526cbe6ca Add padding Checkly integration page 2023-06-26 12:39:29 +07:00
Vladyslav Matsiiako
6959fc52ac minor style updates 2023-06-25 21:49:28 -07:00
Andrew Atimapre
81bd684305 removed unnecessary variable declarations 2023-06-25 17:17:29 +01:00
BlackMagiq
68c8dad829 Merge pull request #682 from atimapreandrew/remove-unnecessary-backend-dependencies
removed await-to-js and builder-pattern dependencies from backend
2023-06-25 18:41:56 +07:00
Tuan Dang
ca3f7bac6c Remove catch error-handling in favor of error-handling middleware 2023-06-25 17:31:19 +07:00
Tuan Dang
a127d452bd Continue to make progress on usage and billing page revamp 2023-06-25 17:03:41 +07:00
akhilmhdh
7c77cc4ea4 fix(integrations-page): eslint fixes to the new upstream changes made 2023-06-24 23:44:52 +05:30
akhilmhdh
9c0e32a790 fix(integrations-page): added back cloudflare changes in main integrations page 2023-06-24 23:35:55 +05:30
akhilmhdh
611fae785a chore: updated to latested storybook v7 stable version 2023-06-24 23:31:37 +05:30
akhilmhdh
0ef4ac1cdc feat(integration-page): implemented new optimized integrations page 2023-06-24 23:31:37 +05:30
akhilmhdh
c04ea7e731 feat(integration-page): updated components and api hooks 2023-06-24 23:30:27 +05:30
Andrew Atimapre
9bdecaf02f removed await-to-js and builder-pattern dependencies from backend 2023-06-24 00:29:31 +01:00
Vladyslav Matsiiako
6b222bad01 youtube link change 2023-06-22 19:49:21 -07:00
Maidul Islam
079d68c042 remove dummy file content 2023-06-22 22:28:39 -04:00
Maidul Islam
4b800202fb git app with probot 2023-06-22 22:26:23 -04:00
Vladyslav Matsiiako
12d0916625 casting to date 2023-06-22 16:25:21 -07:00
Vladyslav Matsiiako
e0976d6bd6 added ? to getTime 2023-06-22 16:16:46 -07:00
Vladyslav Matsiiako
a31f364361 converted date to unix 2023-06-22 16:10:54 -07:00
Vladyslav Matsiiako
8efa17928c intercom date fix 2023-06-22 15:57:20 -07:00
Vladyslav Matsiiako
48bfdd500d date format intercom 2023-06-22 15:30:21 -07:00
Vladyslav Matsiiako
4621122cfb added created timestamp to intercom 2023-06-22 15:17:11 -07:00
Vladyslav Matsiiako
62fb048cce intercom debugging 2023-06-22 15:09:02 -07:00
Vladyslav Matsiiako
d4d0fe60b3 Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-22 15:00:16 -07:00
Vladyslav Matsiiako
0a6e8e009b intercom update 2023-06-22 14:59:55 -07:00
Maidul Islam
9f319d7ce3 add dummy value for intercom 2023-06-22 17:17:38 -04:00
Vladyslav Matsiiako
7b3bd54386 intercom check 2023-06-22 13:29:26 -07:00
Tuan Dang
8d82e2d0fc Replace generic error with BadRequestError for missing refresh token in exchange 2023-06-22 18:08:23 +07:00
Tuan Dang
ffd4655e2f Add API Key auth mode support for v1/workspace 2023-06-22 17:53:09 +07:00
BlackMagiq
8f119fbdd3 Merge pull request #677 from Infisical/stripe-error
Remove all Stripe logic from codebase + any related issues
2023-06-22 17:24:11 +07:00
Tuan Dang
b22a179a17 Fix lint issues 2023-06-22 17:03:28 +07:00
Tuan Dang
1cbab58d29 Merge remote-tracking branch 'origin' into stripe-error 2023-06-22 16:38:46 +07:00
Tuan Dang
28943f3b6f Finish removing Stripe from codebase 2023-06-22 16:38:02 +07:00
Maidul Islam
b1f4e17aaf increase limit 2023-06-22 00:40:45 -04:00
Maidul Islam
afd0c6de08 remove unused import 2023-06-21 15:08:47 -04:00
Maidul Islam
cf114b0d3c Merge pull request #648 from quinton11/feat/cli-login-redirect
feat: cli login via browser
2023-06-21 14:47:47 -04:00
Maidul Islam
f785d62315 remove img from login by cli 2023-06-21 14:46:57 -04:00
Maidul Islam
7aeda9e245 remove service accounts from k8 docs 2023-06-21 12:45:56 -04:00
quinton11
8a5e655122 fix: frontend lint errors 2023-06-21 07:29:05 +00:00
quinton
9b447a4ab0 Merge branch 'main' into feat/cli-login-redirect 2023-06-21 06:47:57 +00:00
BlackMagiq
f3e84dc6eb Merge pull request #667 from Stijn-Kuijper/cloudflare-pages-integration
Cloudflare Pages integration
2023-06-21 13:09:38 +07:00
Tuan Dang
a18a86770e Add docs for Cloudflare Pages integration 2023-06-21 13:05:35 +07:00
Tuan Dang
6300f86cc4 Optimize and patch minor issues for Cloudflare Pages integration 2023-06-21 12:11:53 +07:00
Tuan Dang
df662b1058 Resolve merge conflicts 2023-06-21 11:44:07 +07:00
Maidul Islam
db019178b7 Merge pull request #661 from akhilmhdh/feat/folder-doc
doc(folders): updated docs about folders
2023-06-20 13:27:28 -04:00
BlackMagiq
dcec2dfcb0 Merge pull request #664 from khoa165/add-eslint
Add eslint rule and fix as many issues Add eslint rule and fix as many issues as possibleas possible
2023-06-21 00:03:26 +07:00
Stijn-Kuijper
e6ad153e83 feat: option to choose target environment 2023-06-20 13:43:22 +02:00
Khoa Le
9d33e4756b Add eslint rule and fix as many issues as possible 2023-06-19 23:42:04 -04:00
quinton11
c267aee20f feat: interactive login 2023-06-19 22:25:21 +00:00
akhilmhdh
381e40f9a3 doc(folders): updated docs about folders 2023-06-19 22:38:38 +05:30
Stijn-Kuijper
1760b319d3 cleanup 2023-06-19 16:00:53 +02:00
Stijn-Kuijper
59737f89c1 fix: cloudlfare pages sync request fix 2023-06-19 15:44:41 +02:00
Stijn-Kuijper
17097965d9 feat: cloudflare pages integration sync 2023-06-19 15:14:57 +02:00
Stijn-Kuijper
1a54bf34ef feat: fix getApps and create for cloudflare pages integration 2023-06-19 13:58:38 +02:00
quinton11
7e8ba077ae fix: terminal text alignment 2023-06-19 09:32:55 +00:00
Stijn-Kuijper
6ca010e2ba Merge branch 'Infisical:main' into cloudflare-pages-integration 2023-06-18 18:26:55 +02:00
BlackMagiq
e9eacc445d Merge pull request #650 from akhilmhdh/feat/integrations-page
Feat/integrations page
2023-06-17 10:06:30 +02:00
Tuan Dang
db12dafad2 Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-17 10:35:49 +07:00
Tuan Dang
75acda0d7d Add option to attach accessId onto integration auth middleware 2023-06-17 10:35:42 +07:00
Maidul Islam
b98e276767 Merge pull request #658 from Infisical/cli-switch-v2-to-v3-secrets
cli: switch from v2 secrets to v3
2023-06-16 18:23:23 -04:00
Maidul Islam
149c58fa3e cli: switch from v2 secrets to v3 2023-06-16 17:49:25 -04:00
Maidul Islam
62d79b82f8 Merge pull request #642 from akhilmhdh/feat/folder-env-overview
Folder support in secret overview page
2023-06-16 13:19:16 -04:00
akhilmhdh
7f7e63236b fix: resolved dashboardpage latestKey undefined error 2023-06-16 20:45:31 +05:30
Maidul Islam
965a5cc113 update rate limits 2023-06-16 10:03:12 -04:00
quinton11
5a4a36a06a fix: minor change 2023-06-16 13:20:17 +00:00
quinton11
dd0fdea19f fix: included mfa login flow 2023-06-16 12:58:00 +00:00
Tuan Dang
af31549309 Update pairing-session link 2023-06-16 01:15:24 +01:00
BlackMagiq
072e5013fc Merge pull request #653 from pgaijin66/bugfix/docs/remove-duplicate-api-key-header
bugfix(docs): remove duplicate api key header from API reference docu…
2023-06-16 00:54:20 +01:00
Prabesh Thapa
43f2cf8dc3 bugfix(docs): remove duplicate api key header from API reference documentation 2023-06-15 16:49:50 -07:00
vmatsiiako
0aca308bbd Update README.md 2023-06-15 15:01:52 -07:00
Vladyslav Matsiiako
ff567892f9 added empty state for integrations and terraform 2023-06-15 14:58:40 -07:00
Vladyslav Matsiiako
15fc12627a minor style updates 2023-06-15 13:51:28 -07:00
akhilmhdh
a743c12c1b feat(folder-scoped-integrations): implemented ui for folders in integration page 2023-06-15 22:46:40 +05:30
akhilmhdh
2471418591 feat(folder-scoped-integration): implemented api changes for integrations to support folders 2023-06-15 22:46:39 +05:30
BlackMagiq
c77ebd4d0e Merge pull request #649 from Infisical/environment-paywall
Update implementation for environment limit paywall
2023-06-15 15:56:32 +01:00
Tuan Dang
ccaf9a9ffc Update implementation for environment limit paywall 2023-06-15 15:48:19 +01:00
Stijn-Kuijper
381806d84b feat: initial getApps for Cloudflare Pages 2023-06-15 09:46:09 +02:00
Vladyslav Matsiiako
391e37d49e fixed bugs with env and password reset 2023-06-14 21:27:37 -07:00
Maidul Islam
7088b3c9d8 patch refresh token cli 2023-06-14 17:32:01 -04:00
Maidul Islam
ccf0877b81 Revert "Revert "add refresh token to cli""
This reverts commit 6b0e0f70d2.
2023-06-14 17:32:01 -04:00
quinton11
9e9129dd02 feat: cli login via browser 2023-06-14 19:12:56 +00:00
Maidul Islam
0aa9390ece Merge pull request #647 from Budhathoki356/fix/typo
fix: minor typos in code
2023-06-14 14:51:44 -04:00
Maidul Islam
e47934a08a Merge branch 'main' into fix/typo 2023-06-14 14:47:22 -04:00
Eklal Budhathoki
04b7383bbe fix: minor typos in code 2023-06-15 00:17:00 +05:45
BlackMagiq
930b1e8d0c Merge pull request #645 from Infisical/environment-paywall
Update getPlan to consider the user's current workspace
2023-06-14 12:32:42 +01:00
Tuan Dang
82a026a426 Update refreshPlan to consider workspace 2023-06-14 12:28:01 +01:00
Tuan Dang
92647341a9 Update getPlan with workspace-specific consideration and add environmentLimit to returned plan 2023-06-14 11:52:48 +01:00
Maidul Islam
776cecc3ef create prod release action 2023-06-13 22:16:26 -04:00
Maidul Islam
a4fb2378bb wait for helm upgrade before mark complete 2023-06-13 22:06:53 -04:00
Maidul Islam
9742fdc770 rename docker image 2023-06-13 22:00:51 -04:00
Maidul Islam
786778fef6 isolate gamma environment 2023-06-13 21:56:15 -04:00
Maidul Islam
3f946180dd add terraform docs 2023-06-13 18:28:41 -04:00
akhilmhdh
b1b32a34c9 feat(folder-sec-overview): made folder cell fully select 2023-06-13 20:16:14 +05:30
Tuan Dang
3d70333f9c Update password-reset email response 2023-06-13 15:31:55 +01:00
akhilmhdh
a6cf7107b9 feat(folder-sec-overview): implemented folder based ui for sec overview 2023-06-13 19:26:33 +05:30
akhilmhdh
d590dd5db8 feat(folder-sec-overview): added folder path support in get secrets and get folders 2023-06-13 19:26:33 +05:30
Stijn-Kuijper
c64cf39b69 feat: cloudflare pages integration create page 2023-06-13 12:37:07 +02:00
Tuan Dang
f4404f66b8 Correct link to E2EE API usage example 2023-06-13 11:30:47 +01:00
BlackMagiq
9a62496d5c Merge pull request #641 from Infisical/improve-api-docs
Add REST API integration option to the introduction in docs
2023-06-13 11:26:53 +01:00
Tuan Dang
e24c1f38e0 Add REST API integration option in docs introduction 2023-06-13 11:23:13 +01:00
Stijn-Kuijper
dffcee52d7 feat: cloudflare integration auth page 2023-06-13 11:52:40 +02:00
Stijn-Kuijper
db28536ea8 feat: add clouflare pages button to integrations page 2023-06-13 11:12:12 +02:00
BlackMagiq
3ca9b7d6bf Merge pull request #640 from Infisical/improve-api-docs
Improve API docs for non-E2EE examples
2023-06-13 10:05:43 +01:00
Tuan Dang
37d2d580f4 Improve API docs for non-E2EE 2023-06-13 10:02:10 +01:00
Vladyslav Matsiiako
41dd2fda8a Changed the intercom to aprovider model 2023-06-12 21:42:29 -07:00
Vladyslav Matsiiako
22ca4f2e92 Fixed the typeerror issue 2023-06-12 20:56:19 -07:00
vmatsiiako
5882eb6f8a Merge pull request #639 from Infisical/intercom-tour
Switched intercom to AppLayout
2023-06-12 20:20:06 -07:00
Maidul Islam
c13d5e29f4 add intercom env replace during start up 2023-06-12 16:19:27 -07:00
Vladyslav Matsiiako
d99c54ca50 Switched intercom to layout 2023-06-12 15:38:12 -07:00
Tuan Dang
9dd0dac2f9 Patch frontend lint error 2023-06-12 18:07:15 +01:00
Tuan Dang
98efffafaa Patch subscription plan frontend validation 2023-06-12 17:47:32 +01:00
BlackMagiq
342ee50063 Merge pull request #638 from Infisical/non-e2ee-secrets
Add support for Encrypted Standard (ES) mode — i.e. read/write secrets in plaintext
2023-06-12 12:19:02 +01:00
Tuan Dang
553cf11ad2 Fix lint issue 2023-06-12 12:16:23 +01:00
Tuan Dang
4616cffecd Add support for read/write non-e2ee secrets 2023-06-12 12:04:28 +01:00
Vladyslav Matsiiako
39feb9a6ae Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-11 19:24:38 -07:00
Vladyslav Matsiiako
82c1f8607d Added intercom 2023-06-11 19:23:30 -07:00
vmatsiiako
d4c3cbb53a Merge pull request #636 from mswider/self-hosted-env
Allow custom environments in self-hosted instances
2023-06-11 16:53:40 -07:00
mswider
1dea6749ba Allow custom environments in self-hosted instances 2023-06-11 18:19:01 -05:00
Tuan Dang
631eac803e Finish preliminary v3/secrets/raw endpoints 2023-06-11 12:11:25 +01:00
Tuan Dang
facabc683b Fix merge conflicts 2023-06-10 11:07:31 +01:00
BlackMagiq
4b99a9ea93 Merge pull request #633 from akhilmhdh/feat/folders-service-token
Folder scoped service token
2023-06-10 11:02:16 +01:00
akhilmhdh
445afb397c feat(folder-scoped-st): added batch,create secrets v2 secretpath support and service token 2023-06-10 12:10:43 +05:30
akhilmhdh
7d554f46d5 feat(folder-scoped-st): changed text css transformation in folders 2023-06-10 12:09:43 +05:30
Maidul Islam
bbef7d415c remove old commit 2023-06-09 18:41:10 -07:00
Maidul Islam
bb7b398fa7 throw unauthorized error instead of 500 for permission denied 2023-06-09 18:40:41 -07:00
Maidul Islam
570457c7c9 check path before service token create 2023-06-09 18:38:39 -07:00
Vladyslav Matsiiako
1b77b1d70b fixed the etxt issue 2023-06-09 17:02:41 -07:00
Vladyslav Matsiiako
0f697a91ab updated the workspace limit 2023-06-09 16:14:35 -07:00
Vladyslav Matsiiako
df6d23d1d3 fixed the ts error 2023-06-09 15:31:38 -07:00
Tuan Dang
0187d3012b Add non-e2ee option for getSecret, getSecrets, start createSecret 2023-06-09 21:20:12 +01:00
Vladyslav Matsiiako
4299a76fcd changed the default envs 2023-06-09 12:52:44 -07:00
Vladyslav Matsiiako
2bae6cf084 lots of frontend improvements 2023-06-09 12:50:17 -07:00
akhilmhdh
22beebc5d0 feat(folder-scoped-st): implemented frontend ui for folder scoped service token 2023-06-09 23:44:33 +05:30
akhilmhdh
6cb0a20675 feat(folder-scoped-st): implemented backend api for folder scoped service tokens 2023-06-09 23:44:33 +05:30
Tuan Dang
00fae0023a Add cluster URL image to docs for Vault integration 2023-06-09 15:57:47 +01:00
BlackMagiq
0377219a7a Merge pull request #632 from Infisical/vault-integration
Finish preliminary Vault integration, made docs for Vault and Checkly
2023-06-09 15:45:00 +01:00
Tuan Dang
00dfcfcf4e Finish preliminary Vault integration, made docs for Vault and Checkly 2023-06-09 15:36:37 +01:00
Vladyslav Matsiiako
f5441e9996 Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-08 11:08:48 -07:00
Vladyslav Matsiiako
ee2fb33b50 changed the docs order 2023-06-08 11:08:27 -07:00
BlackMagiq
c51b194ba6 Merge pull request #629 from Infisical/optimize-checkly
Optimize Checkly integration
2023-06-08 11:21:28 +01:00
Tuan Dang
2920ba5195 Update Checkly envars only if changed 2023-06-08 11:18:23 +01:00
Tuan Dang
cd837b07aa Remove Sentry, part-try-catch from sync Checkly 2023-06-08 11:04:34 +01:00
BlackMagiq
a8e71e8170 Merge pull request #627 from Infisical/checkly-integration
Checkly integration
2023-06-08 10:56:19 +01:00
BlackMagiq
5fa96411d6 Merge branch 'main' into checkly-integration 2023-06-08 10:53:10 +01:00
Tuan Dang
329ab8ae61 Add +devices for verifyMfaToken user 2023-06-08 00:58:23 +01:00
Tuan Dang
3242d9b44e Fix change password button active state on no errors 2023-06-08 00:28:51 +01:00
Tuan Dang
8ce48fea43 Fix change password button active state on no errors 2023-06-08 00:27:59 +01:00
Maidul Islam
b011144258 reduce password forgot limit 2023-06-07 16:27:16 -07:00
Tuan Dang
674828e8e4 Copy data folder into backend build folder 2023-06-07 23:57:37 +01:00
Tuan Dang
c0563aff77 Bring back try-catch for initGlobalFeatureSet 2023-06-07 23:13:25 +01:00
BlackMagiq
7cec42a7fb Merge pull request #628 from Infisical/pentest-remediation
Fix issues/bugs
2023-06-07 22:52:08 +01:00
Tuan Dang
78493d9521 Fix lint errors 2023-06-07 22:47:47 +01:00
Vladyslav Matsiiako
49b3e8b538 comment fixes 2023-06-07 13:12:58 -07:00
Vladyslav Matsiiako
a3fca200fc comment fixes 2023-06-07 13:12:21 -07:00
Vladyslav Matsiiako
158eb584d2 integration with checkly done 2023-06-07 13:11:39 -07:00
Maidul Islam
e8bffb7217 Merge pull request #626 from akhilmhdh/fix/reload-submit
fix(ui): resolved reloading when form submission
2023-06-07 11:46:03 -07:00
akhilmhdh
604810ebd2 fix(ui): resolved reloading when form submission 2023-06-07 22:45:50 +05:30
Maidul Islam
d4108d1fab update email docs for self hosting 2023-06-07 10:13:43 -07:00
Tuan Dang
4d6ae0eef8 Merge remote-tracking branch 'origin' into pentest-remediation 2023-06-07 16:30:13 +01:00
BlackMagiq
8193490d7f Merge pull request #624 from Infisical/stabilize-server-try-catch
Bring back express-async-errors
2023-06-07 16:27:16 +01:00
Tuan Dang
0deba5e345 Bring back express-async-errors 2023-06-07 16:25:13 +01:00
Tuan Dang
a2055194c5 Fix merge conflicts 2023-06-07 13:12:54 +01:00
Tuan Dang
8c0d643a37 Fix merge conflicts 2023-06-07 12:58:24 +01:00
BlackMagiq
547a1fd142 Merge pull request #617 from Spelchure/removing-sentry-logs
feat: remove try-catch blocks for handling errors in middleware
2023-06-07 12:17:17 +01:00
Maidul Islam
04765ffb94 update email setup docs 2023-06-06 23:27:15 -07:00
Vladyslav Matsiiako
6b9aa200b5 login/signup styling fixes 2023-06-06 19:41:02 -07:00
Tuan Dang
5667e47b31 Add default rely on Cloudflare for IP addresses 2023-06-07 00:50:25 +01:00
Tuan Dang
a8ed187443 Add check for most common passwords 2023-06-07 00:06:35 +01:00
Tuan Dang
c5be497052 Strengthen password requirement 2023-06-06 23:06:44 +01:00
Maidul Islam
77d47e071b add folder id to versions in batch update 2023-06-06 13:31:08 -07:00
Maidul Islam
4bf2407d13 remove encryptionKey validation check 2023-06-06 09:43:11 -07:00
Tuan Dang
846f5c6680 Upgraded JWT invalidation/session logic to separate TokenVersion model. 2023-06-06 16:36:52 +01:00
BlackMagiq
6f1f07c9a5 Merge branch 'main' into removing-sentry-logs 2023-06-06 15:17:59 +01:00
Tuan Dang
aaca66e5a4 Patch support for ENCRYPTION_KEY and ROOT_ENCRYPTION_KEY in generateSecretBlindIndexHelper 2023-06-06 14:24:06 +01:00
Tuan Dang
b9dad5c3f0 Begin preliminary tokenVersion impl 2023-06-06 11:25:08 +01:00
Maidul Islam
3a79a855cb Merge pull request #622 from Infisical/folder-patch-v2
Patch backfill data
2023-06-05 23:14:14 -07:00
Maidul Islam
e28d0cbace bring back tags to secret version 2023-06-05 23:12:48 -07:00
Maidul Islam
c0fbe82ecb update populate number 2023-06-05 20:21:09 -07:00
Maidul Islam
b0e7304bff Patch backfill data 2023-06-05 20:19:15 -07:00
Tuan Dang
5a1b6acc93 Fix merge conflicts with auth changes 2023-06-05 21:27:01 +01:00
Tuan Dang
5f5ed5d0a9 Change export convention for helper functions 2023-06-05 21:00:23 +01:00
Spelchure
bfee0a6d30 feat: remove try-catch blocks for handling errors in middleware 2023-06-05 21:15:35 +03:00
Maidul Islam
08868681d8 Merge pull request #621 from akhilmhdh/fix/folder-breadcrumb
feat(folders): resolved auth issues and added the env dropdown change…
2023-06-05 09:51:59 -07:00
akhilmhdh
6dee858154 feat(folders): resolved auth issues and added the env dropdown change inside folders 2023-06-05 20:47:58 +05:30
Tuan Dang
0c18bd71c4 Implement preliminary pentest remediations 2023-06-05 00:44:10 +01:00
Maidul Islam
b9dfff1cd8 add migration complete logs 2023-06-04 16:37:57 -07:00
Maidul Islam
44b9533636 Merge pull request #609 from akhilmhdh/feat/folders
Feat/folders
2023-06-04 16:24:43 -07:00
Maidul Islam
599c8d94c9 Merge pull request #620 from Infisical/single-rate-limit-store
use mongo rate limit store
2023-06-04 13:45:56 -07:00
Maidul Islam
77788e1524 use mongo rate limit store 2023-06-04 13:43:57 -07:00
Maidul Islam
3df62a6e0a patch dup email bug for login 2023-06-04 11:07:52 -07:00
akhilmhdh
e74cc471db fix(folders): changed to secret path in controllers for get by path op 2023-06-04 13:26:20 +05:30
akhilmhdh
58d3f3945a feat(folders): removed old comments 2023-06-04 13:22:29 +05:30
akhilmhdh
29fa618bff feat(folders): changed / to root in breadcrumbs for folders 2023-06-04 13:18:05 +05:30
akhilmhdh
668b5a9cfd feat(folders): adopted new strategy for rollback on folders 2023-06-04 13:18:05 +05:30
akhilmhdh
6ce0f48b2c fix(folders): fixed algorithm missing in rollback versions and resolved env change reset folderid 2023-06-04 13:18:05 +05:30
Vladyslav Matsiiako
467e85b717 Minor style changes 2023-06-04 13:18:05 +05:30
akhilmhdh
579516bd38 feat(folders): implemented ui for folders in dashboard 2023-06-04 13:18:05 +05:30
akhilmhdh
deaa85cbe7 feat(folders): added support for snapshot by env and folder 2023-06-04 13:18:05 +05:30
Vladyslav Matsiiako
08a4404fed fixed the email issue 2023-06-03 13:58:53 -07:00
Maidul Islam
73aa01c568 prevent passport init when envs are undefined 2023-06-03 12:20:04 -07:00
vmatsiiako
b926601a29 Merge pull request #535 from sheensantoscapadngan/feature/google-signin-signup-integration
Feature/google signin signup integration
2023-06-02 23:44:45 -07:00
Maidul Islam
f619ee7297 revise self hosting order 2023-06-02 14:10:40 -07:00
Maidul Islam
bb825c3d68 add DO docs link 2023-06-02 14:06:04 -07:00
Maidul Islam
6bbd7f05a2 add digital ocean docs 2023-06-02 14:05:00 -07:00
Vladyslav Matsiiako
4865b69e6d updated the slack link 2023-06-01 13:54:02 -07:00
Vladyslav Matsiiako
6f3c7c0fbf fix ts issues 2023-05-31 11:29:08 -07:00
Vladyslav Matsiiako
be80b5124f Final style changes to login/signup 2023-05-31 11:25:37 -07:00
BlackMagiq
4232776637 Merge pull request #614 from Infisical/check-in-process
Update contributing docs to include expectations and procedures for submitting pull requests
2023-05-31 12:46:21 +03:00
Tuan Dang
2c12ede694 Add note for prioritization of first 3 PRs for contribution due to high volume of issues, PRs, and other initiatives in the pipeline 2023-05-31 12:44:30 +03:00
Tuan Dang
94141fedd6 Update contributing docs, add pull requests section 2023-05-31 12:25:08 +03:00
BlackMagiq
720ab446f9 Merge pull request #612 from Infisical/migration-script
Add migration script for server key re-encryption
2023-05-30 21:03:14 +03:00
Tuan Dang
1a1693dbbf Add encryption key validation to validation script 2023-05-30 20:46:20 +03:00
Tuan Dang
9440afa386 Remove re-encryption from Infisical, move to migration script 2023-05-30 20:30:58 +03:00
Maidul Islam
8b1ec1424d patch quote type in docs 2023-05-30 11:19:15 -04:00
Maidul Islam
bd56f3b64c update docker run command for self host 2023-05-30 11:14:22 -04:00
Sheen Capadngan
f20ea723b7 Merge branch 'main' into feature/google-signin-signup-integration 2023-05-30 20:46:20 +08:00
Maidul Islam
86f76ebe70 no default user for selfhosting docs 2023-05-30 08:39:43 -04:00
Maidul Islam
821385c2f3 Revert "add prod img publish ste p"
This reverts commit f7dbd41431.
2023-05-30 07:29:45 -04:00
Maidul Islam
03c65c8635 Revert "add dummy step"
This reverts commit 893c4777fe.
2023-05-30 07:29:31 -04:00
Maidul Islam
893c4777fe add dummy step 2023-05-29 18:49:14 -04:00
Maidul Islam
f7dbd41431 add prod img publish ste p 2023-05-29 18:46:04 -04:00
Maidul Islam
8d1f3e930a revert keychain name 2023-05-29 18:08:49 -04:00
Maidul Islam
f25715b3c4 update keychain name 2023-05-29 17:36:07 -04:00
Tuan Dang
37251ed607 Begin migration script for re-encryption 2023-05-29 23:46:16 +03:00
Maidul Islam
c078fb8bc1 allow user to create new keychain 2023-05-29 15:56:48 -04:00
Tuan Dang
2ae3c48b88 Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-29 14:21:27 +03:00
Tuan Dang
ce28151952 Update posthog-js version 2023-05-29 14:21:16 +03:00
Sheen Capadngan
e6027b3c72 removed try catch from requireAuth middleware 2023-05-29 18:14:16 +08:00
Sheen Capadngan
7f4db518cc Merge branch 'main' into feature/google-signin-signup-integration 2023-05-29 18:00:43 +08:00
BlackMagiq
7562e7d667 Merge pull request #607 from Infisical/changelog
Add preliminary changelog to docs
2023-05-29 12:18:27 +03:00
Tuan Dang
5c8f33a2d8 Add preliminary changelog to docs 2023-05-29 12:15:47 +03:00
Vladyslav Matsiiako
d4f65e23c7 Merge branch 'feature/google-signin-signup-integration' of https://github.com/sheensantoscapadngan/infisical into feature/google-signin-signup-integration 2023-05-28 14:22:42 -07:00
Vladyslav Matsiiako
50609d06f5 Final style changes to signup 2023-05-28 14:22:37 -07:00
Vladyslav Matsiiako
3a5ad93450 Final style changes to signup 2023-05-28 14:22:17 -07:00
Tuan Dang
8493d51f5c Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-28 22:23:51 +03:00
Tuan Dang
e90f63b375 Install and require express-async-errors earlier 2023-05-28 22:23:26 +03:00
Maidul Islam
af9ffdc51f delete pre commit (pre-commit.com) 2023-05-28 14:36:07 -04:00
Maidul Islam
3a76a82438 add dummy ENCRYPTION_KEY for testing backend docker img 2023-05-28 14:09:32 -04:00
Sheen Capadngan
8e972c704a resolved error handling issue with requireAuth middleware 2023-05-28 23:06:20 +08:00
Sheen Capadngan
b975115443 Merge branch 'main' into feature/google-signin-signup-integration 2023-05-28 22:12:02 +08:00
BlackMagiq
4a1821d537 Merge pull request #606 from Infisical/gitlab-integration
Add pagination to retrieve envars for GitLab integration
2023-05-28 16:51:29 +03:00
Tuan Dang
01b87aeebf Add pagination to retrieve envars for GitLab integration 2023-05-28 16:46:05 +03:00
Vladyslav Matsiiako
cea3b59053 Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-27 19:12:47 -07:00
Vladyslav Matsiiako
a6f6711c9a posthog attribution adjustment 2023-05-27 19:12:32 -07:00
vmatsiiako
3d3b416da2 Merge pull request #602 from piyushchhabra/fix/project-list-scroll
fix(ui): fixed scroll on project list selection
2023-05-26 23:13:07 -07:00
Vladyslav Matsiiako
bfbe2f2dcf brought the button back down and removed side bar for other browsers 2023-05-26 23:08:57 -07:00
Maidul Islam
8e5db3ee2f Merge pull request #605 from Infisical/revert-601-add-refresh-token-cli
Revert "add refresh token to cli"
2023-05-26 16:56:13 -04:00
Maidul Islam
6b0e0f70d2 Revert "add refresh token to cli" 2023-05-26 16:56:02 -04:00
Maidul Islam
1fb9aad08a Revert "only re-store user creds when token expire"
This reverts commit df9efa65e7.
2023-05-26 16:55:29 -04:00
BlackMagiq
61a09d817b Merge pull request #604 from Infisical/revised-encryption-key
Update dummy variables in test
2023-05-26 17:31:59 +03:00
Tuan Dang
57b8ed4eef Merge remote-tracking branch 'origin' into revised-encryption-key 2023-05-26 17:29:54 +03:00
Tuan Dang
c3a1d03a9b Update test dummy variables 2023-05-26 17:29:23 +03:00
BlackMagiq
11afb6db51 Merge pull request #603 from Infisical/revised-encryption-key
Add encryption metadata and upgrade ENCRYPTION_KEY to ROOT_ENCRYPTION_KEY
2023-05-26 17:01:00 +03:00
Tuan Dang
200d9de740 Fix merge conflicts 2023-05-26 16:41:17 +03:00
vmatsiiako
17060b22d7 Update README.md 2023-05-25 21:24:07 -07:00
Tuan Dang
c730280eff Update FeatureSet interface to include used counts 2023-05-26 00:26:16 +03:00
Maidul Islam
c45120e6e9 add shorter env name for file vault 2023-05-25 13:27:20 -04:00
piyushchhabra
c96fbd3724 fix(ui): fixing scroll on project list selection 2023-05-25 19:44:06 +05:30
Tuan Dang
e1e2eb7c3b Add SecretBlindIndexData for development user initialization 2023-05-25 16:07:08 +03:00
Tuan Dang
7812061e66 Update isPaid telemetry accounting to be tier-based instead of via slug 2023-05-25 12:59:18 +03:00
Maidul Islam
ca41c65fe0 small helm doc changes 2023-05-24 23:46:34 -04:00
vmatsiiako
d8c15a366d Merge pull request #600 from piyushchhabra/fix/gui-tags-overflow
fix(ui): fixed tags overflow in delete card
2023-05-24 20:19:53 -07:00
Maidul Islam
df9efa65e7 only re-store user creds when token expire 2023-05-24 19:46:02 -04:00
Maidul Islam
1c5616e3b6 revise pre commit doc 2023-05-24 19:11:33 -04:00
Maidul Islam
27030138ec Merge pull request #601 from Infisical/add-refresh-token-cli
add refresh token to cli
2023-05-24 18:53:52 -04:00
Maidul Islam
c37ce4eaea add refresh token to cli 2023-05-24 18:51:42 -04:00
piyushchhabra
5aa367fe54 fix(ui): fixed tags overflow in card + port correction in README 2023-05-24 23:03:12 +05:30
Sheen Capadngan
fac4968193 moved oauth controller endpoints to auth 2023-05-24 23:44:30 +08:00
Maidul Islam
17647587f9 remove tests for time being 2023-05-24 10:48:11 -04:00
Maidul Islam
f3dc7fcf7b add timout to pull requests 2023-05-24 10:48:11 -04:00
Sheen Capadngan
93cf7cde2d fixed login issue after mfa 2023-05-24 21:33:24 +08:00
Sheen Capadngan
422d04d7d7 migrated to standard request 2023-05-24 18:50:59 +08:00
Sheen Capadngan
4c41d279e9 Merge branch 'main' into feature/google-signin-signup-integration 2023-05-24 18:39:18 +08:00
Tuan Dang
e65c6568e1 Modify convention for PostHog isPaid attr to be tier-based instead of slug 2023-05-24 10:26:06 +03:00
Maidul Islam
9d40a96633 Update README.md 2023-05-23 20:22:01 -04:00
Maidul Islam
859fe09ac6 Merge pull request #598 from Infisical/maidul98-patch-1
add pre commit install command to README.md
2023-05-23 20:20:57 -04:00
Maidul Islam
d0d6419d4d add pre commit install command to README.md 2023-05-23 20:20:10 -04:00
Maidul Islam
8b05ce11f7 add pre commit to husky 2023-05-23 20:15:39 -04:00
Maidul Islam
a7fb0786f9 improve pre commit docs 2023-05-23 19:45:10 -04:00
Maidul Islam
f2de1778cb catch case when hook path is default 2023-05-23 19:34:31 -04:00
Vladyslav Matsiiako
952cf47b9a Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-23 15:41:43 -07:00
Vladyslav Matsiiako
1d17596af1 added boolean flag for the plan in posthog logging 2023-05-23 15:41:34 -07:00
Maidul Islam
01385687e0 make posthog failed calls level=debug 2023-05-23 18:16:13 -04:00
Maidul Islam
d2e3aa15b0 patch standalone docker image 2023-05-23 17:16:32 -04:00
Tuan Dang
96607153dc Modularize getOrganizationPlan function 2023-05-23 23:54:54 +03:00
Tuan Dang
a8502377c7 Add endpoint for updating organization plan 2023-05-23 23:14:20 +03:00
BlackMagiq
5aa99001cc Merge pull request #597 from Infisical/connect-to-license-server
Added add/remove/get organization payment methods and get cloud plans…
2023-05-23 22:39:35 +03:00
Tuan Dang
83dd35299c Added add/remove/get organization payment methods and get cloud plans from license server 2023-05-23 22:28:41 +03:00
Maidul Islam
b5b2f402ad add missing required envrs 2023-05-23 14:09:45 -04:00
Maidul Islam
ec34572087 patch invite only 2023-05-23 13:18:21 -04:00
BlackMagiq
7f7d120c2f Merge pull request #595 from Infisical/connect-to-license-server
Add support for fetching plan details from license server
2023-05-23 17:02:20 +03:00
Tuan Dang
899d46514c Add forwarding usedSeats and subscription quantity to license server on org member add/delete 2023-05-23 16:59:13 +03:00
Maidul Islam
658df21189 Add auto install pre commit 2023-05-23 00:09:00 -04:00
Sheen Capadngan
51914c6a2e resolved package-lock conflicts 2023-05-22 22:07:30 +08:00
Sheen Capadngan
ad37a14f2e Merge branch 'main' into feature/google-signin-signup-integration 2023-05-22 21:54:51 +08:00
Tuan Dang
8341faddc5 Add support for pulling plan details from license server with LICENSE_KEY, LICENSE_SERVER_KEY 2023-05-22 15:43:33 +03:00
Maidul Islam
8e3a23e6d8 fix prod node img for standalone 2023-05-22 08:18:50 -04:00
Sheen Capadngan
bc61de4a80 add provider auth secret to kubernetes and docker yaml 2023-05-20 23:15:36 +08:00
Maidul Islam
1c89474159 hello 2023-05-19 17:23:15 -04:00
Maidul Islam
2f765600b1 add pre-commit hook 2023-05-19 17:20:27 -04:00
Maidul Islam
d9057216b5 remove keyring access during telemetry 2023-05-19 16:10:59 -04:00
Maidul Islam
6aab90590f add version to cli run telemtry 2023-05-19 12:24:49 -04:00
Maidul Islam
f7466d4855 update cli telemetry 2023-05-19 12:20:37 -04:00
Maidul Islam
ea2565ed35 Merge pull request #591 from Infisical/cli-telemetry
Cli telemetry
2023-05-19 10:55:27 -04:00
Maidul Islam
4586656b85 add post hog api to go releaser and update cli telemetry 2023-05-19 10:49:57 -04:00
Maidul Islam
e4953398df add telemetry to cli 2023-05-19 00:16:26 -04:00
Maidul Islam
7722231656 Merge pull request #590 from Infisical/infisical-scan-docs
Infisical scan docs
2023-05-18 15:59:51 -04:00
Maidul Islam
845a476974 add secret scanning to README.md 2023-05-18 15:57:48 -04:00
Maidul Islam
fc19a17f4b update readme with scaning feature 2023-05-18 15:42:25 -04:00
Maidul Islam
0890b1912f Merge pull request #589 from Infisical/infisical-scan-docs
add docs for infisical scan
2023-05-18 15:20:26 -04:00
Maidul Islam
82ecc2d7dc add secret scanning to resources 2023-05-18 15:18:29 -04:00
Maidul Islam
460bdbb91c Merge pull request #587 from Infisical/snyk-upgrade-76cf9e766d00cfa629a2db56d3b5fc39
[Snyk] Upgrade posthog-js from 1.53.4 to 1.54.0
2023-05-18 14:57:16 -04:00
Maidul Islam
446a63a917 add docs for infisical scan 2023-05-18 14:55:39 -04:00
Maidul Islam
d67cb7b507 Merge pull request #588 from Infisical/add-gitleak
rebrand and small tweeks
2023-05-18 12:07:26 -04:00
Maidul Islam
353ff63298 rebrand and small tweeks 2023-05-18 12:04:17 -04:00
Sheen Capadngan
4367822777 re-added token caching and redirection 2023-05-18 23:04:55 +08:00
snyk-bot
9f40266f5c fix: upgrade posthog-js from 1.53.4 to 1.54.0
Snyk has created this PR to upgrade posthog-js from 1.53.4 to 1.54.0.

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-05-18 11:58:06 +00:00
Sheen Capadngan
ca4a9b9937 resolved MFA not appearing 2023-05-18 02:07:07 +08:00
Sheen Capadngan
ec8d62d106 show toast when oauth login error 2023-05-18 01:58:35 +08:00
Maidul Islam
8af8a1d3d5 Merge pull request #580 from Infisical/add-gitleak
add gitleak to cli
2023-05-17 13:20:40 -04:00
Maidul Islam
631423fbc8 Merge pull request #583 from Infisical/snyk-upgrade-32d764d8893bf7596281cd2751bb5f9b
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.317.0 to 3.319.0
2023-05-17 13:11:54 -04:00
Maidul Islam
4383779377 Merge pull request #581 from Infisical/snyk-upgrade-efa6b99248f4e9459845f26b359fc5c8
[Snyk] Upgrade aws-sdk from 2.1362.0 to 2.1364.0
2023-05-17 13:11:37 -04:00
Maidul Islam
8249043826 add testing files and create create scan command 2023-05-17 13:08:00 -04:00
Sheen Capadngan
6ca3b8ba61 handled error cases for external auth login 2023-05-18 00:39:20 +08:00
Vladyslav Matsiiako
20294ee233 Fixed the const issue 2023-05-17 09:27:12 -07:00
Sheen Capadngan
4b2e91da74 added proper error handling for user creation 2023-05-18 00:12:28 +08:00
Sheen Capadngan
fac8affe78 added missing envs for documentation 2023-05-17 22:24:04 +08:00
Sheen Capadngan
1ccec486cc removed caching of providerAuthToken 2023-05-17 21:43:18 +08:00
BlackMagiq
c5a924e935 Merge pull request #585 from Infisical/gitlab-envs
Add support for custom environments in GitLab integration
2023-05-17 14:31:00 +03:00
Tuan Dang
429bfd27b2 Add support for custom environments in GitLab integration 2023-05-17 14:25:18 +03:00
snyk-bot
c99c873d78 fix: upgrade @aws-sdk/client-secrets-manager from 3.317.0 to 3.319.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.317.0 to 3.319.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-16 19:34:58 +00:00
snyk-bot
092a6911ce fix: upgrade aws-sdk from 2.1362.0 to 2.1364.0
Snyk has created this PR to upgrade aws-sdk from 2.1362.0 to 2.1364.0.

See this package in npm:
https://www.npmjs.com/package/aws-sdk

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-16 19:34:50 +00:00
Vladyslav Matsiiako
a9b642e618 Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-15 16:34:02 -07:00
Vladyslav Matsiiako
919ddf5de2 removed console log 2023-05-15 16:33:44 -07:00
Vladyslav Matsiiako
89a89af4e6 improving UX for the onboarding experience 2023-05-15 16:33:11 -07:00
Maidul Islam
b3e68cf3fb add gitleak to cli 2023-05-15 19:31:36 -04:00
Maidul Islam
960063e61a Merge pull request #574 from Infisical/snyk-upgrade-e333c5ab909cc9a88c7a6d9fc95a58ed
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.312.0 to 3.317.0
2023-05-15 17:48:35 -04:00
Maidul Islam
abf4eaf6db Merge pull request #538 from Infisical/snyk-upgrade-64f35bb43ebf5a87403747b8d7956c34
[Snyk] Upgrade fs from 0.0.1-security to 0.0.2
2023-05-15 17:48:25 -04:00
Maidul Islam
739f97f5c9 Merge pull request #575 from Infisical/snyk-upgrade-6fd25092a72767ffb9954920488a4cd5
[Snyk] Upgrade @sentry/node from 7.47.0 to 7.49.0
2023-05-15 17:48:06 -04:00
Maidul Islam
faed5c1821 Merge pull request #576 from Infisical/snyk-upgrade-f1503bf1fac2c534c106f41288ce944d
[Snyk] Upgrade aws-sdk from 2.1360.0 to 2.1362.0
2023-05-15 17:47:54 -04:00
Maidul Islam
c95598aaa6 Merge pull request #578 from akhilmhdh/fix/compose-fail
fix: docker-compose failing due to missing frontend i18n file
2023-05-15 17:47:33 -04:00
akhilmhdh
e791684f4d fix: docker-compose failing due to missing frontend i18n file 2023-05-16 00:19:03 +05:30
Sheen Capadngan
6746f04f33 added self-hosting documentation for google 2023-05-15 23:39:19 +08:00
Vladyslav Matsiiako
d32c5fb869 update the dev stripe product id 2023-05-15 07:31:17 -07:00
Sheen Capadngan
dba19b4a1d Merge branch 'main' into feature/google-signin-signup-integration 2023-05-15 20:41:08 +08:00
Sheen Capadngan
884aed74a5 made last name optional 2023-05-15 20:39:45 +08:00
Vladyslav Matsiiako
abbf1918dc Added limits to the number of projects in an org 2023-05-14 18:25:27 -07:00
Sheen Capadngan
9dc7cc58a7 uncommented code 2023-05-15 00:41:27 +08:00
Maidul Islam
876d0119d3 Merge pull request #564 from parthvnp/feature/457
Add example in CLI usage docs to show how to utilize secrets in shell aliases
2023-05-13 11:23:27 -04:00
Maidul Islam
6d70dc437e update cli usage docs 2023-05-13 11:22:38 -04:00
Maidul Islam
174e22a2bc put aliases docs in Accordion 2023-05-13 11:17:17 -04:00
Sheen Capadngan
6f66b56e7c updated package-lock 2023-05-12 22:24:38 +08:00
Sheen Capadngan
be2bac41bb Merge branch 'main' into feature/google-signin-signup-integration 2023-05-12 22:22:51 +08:00
Vladyslav Matsiiako
f4815641d8 fixed the bug with smaller icon buttons 2023-05-11 18:11:04 -07:00
snyk-bot
5b95c255ec fix: upgrade aws-sdk from 2.1360.0 to 2.1362.0
Snyk has created this PR to upgrade aws-sdk from 2.1360.0 to 2.1362.0.

See this package in npm:
https://www.npmjs.com/package/aws-sdk

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-11 22:13:48 +00:00
snyk-bot
3123f6fc1f fix: upgrade @sentry/node from 7.47.0 to 7.49.0
Snyk has created this PR to upgrade @sentry/node from 7.47.0 to 7.49.0.

See this package in npm:
https://www.npmjs.com/package/@sentry/node

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-11 22:13:44 +00:00
snyk-bot
a913cd97a4 fix: upgrade @aws-sdk/client-secrets-manager from 3.312.0 to 3.317.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.312.0 to 3.317.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-11 22:13:39 +00:00
Sheen Capadngan
0afa44a9f0 removed express-session types 2023-05-11 15:39:04 +08:00
Maidul Islam
781e0b24c8 add docs for spring boot maven 2023-05-10 21:08:38 -04:00
Vladyslav Matsiiako
5a99878d15 Final style edits to the login and signup flows 2023-05-10 16:12:57 -07:00
Vladyslav Matsiiako
0d3e7f3c0c Merge branch 'feature/google-signin-signup-integration' of https://github.com/sheensantoscapadngan/infisical into feature/google-signin-signup-integration 2023-05-10 12:55:19 -07:00
Vladyslav Matsiiako
967e520173 More designed changes to login flow 2023-05-10 12:55:08 -07:00
Sheen Capadngan
ccfe0b1eb9 reverted changes made to nginx config 2023-05-11 00:52:49 +08:00
Sheen Capadngan
0ef5779776 add providerAuthToken for MFA login 2023-05-11 00:47:16 +08:00
Sheen Capadngan
a194e90644 removed session references 2023-05-11 00:41:38 +08:00
Sheen Capadngan
addc849fa6 changed google-auth strategy and removed session use 2023-05-11 00:37:02 +08:00
Sheen Capadngan
074c0bdd77 utilized mongodb as persistent store for sessions 2023-05-10 23:01:44 +08:00
Maidul Islam
28de8cddd7 Merge pull request #567 from Infisical/snyk-upgrade-9b34558b947330a3bd25eec1f2f2e55c
[Snyk] Upgrade aws-sdk from 2.1358.0 to 2.1360.0
2023-05-10 10:31:42 -04:00
Maidul Islam
ed3e53f9a3 Merge pull request #568 from Infisical/snyk-upgrade-5eadfb75ba47539ba48a83549a83d185
[Snyk] Upgrade @godaddy/terminus from 4.11.2 to 4.12.0
2023-05-10 10:31:30 -04:00
Sheen Capadngan
7ee33e9393 resolved merge conflict issues and updated use of translations 2023-05-10 21:39:11 +08:00
Sheen Capadngan
32cef27e8e Merge branch 'main' into feature/google-sign 2023-05-10 21:11:42 +08:00
Vladyslav Matsiiako
1fce8cc769 More style changes to login 2023-05-09 23:24:37 -07:00
Vladyslav Matsiiako
4e7145dfe5 Style changes to login 2023-05-09 20:45:59 -07:00
Maidul Islam
9cb4d5abb7 improve docker compose and add standalone docs 2023-05-09 22:07:48 -04:00
vmatsiiako
efdd1e64c4 Merge pull request #537 from Infisical/snyk-upgrade-852b174d6fb41e4afff7bae352b8818d
[Snyk] Upgrade @fortawesome/react-fontawesome from 0.1.19 to 0.2.0
2023-05-09 17:52:12 -07:00
vmatsiiako
5b3be6063f Merge pull request #573 from akhilmhdh/feat/new-login
refactor(ui): changed frontend to normal i18n without SSR
2023-05-09 17:50:10 -07:00
Maidul Islam
12c399d4a9 fix typo in k8 docs 2023-05-09 17:34:20 -04:00
Maidul Islam
ecd17e1d6d refine k8 deploy docs 2023-05-09 17:32:57 -04:00
Maidul Islam
fb4c811414 update detailed kubernetes helm docs 2023-05-09 16:41:20 -04:00
akhilmhdh
3561c589b1 refactor(ui): changed frontend to normal i18n without SSR 2023-05-09 23:28:23 +05:30
Maidul Islam
420d71d923 add membership validate to folder get 2023-05-09 10:23:41 -04:00
snyk-bot
3db5c040c3 fix: upgrade @godaddy/terminus from 4.11.2 to 4.12.0
Snyk has created this PR to upgrade @godaddy/terminus from 4.11.2 to 4.12.0.

See this package in npm:
https://www.npmjs.com/package/@godaddy/terminus

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-09 08:42:48 +00:00
snyk-bot
b4f336a5bb fix: upgrade aws-sdk from 2.1358.0 to 2.1360.0
Snyk has created this PR to upgrade aws-sdk from 2.1358.0 to 2.1360.0.

See this package in npm:
https://www.npmjs.com/package/aws-sdk

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-09 08:42:43 +00:00
Maidul Islam
43e61c94f0 get folder by id 2023-05-08 21:01:26 -04:00
Maidul Islam
69fa4a80c5 update check for CLI update 2023-05-08 16:43:28 -04:00
Maidul Islam
cf9e8b8a6b patch login bug when override empty 2023-05-08 16:09:57 -04:00
Maidul Islam
c6d5498a42 add dangling prefix for aur 2023-05-08 10:59:24 -04:00
Parth Patel
7aa5ef844c Update CLI usage docs to showcase the ability to inject environment variables in shell aliases 2023-05-08 01:04:35 -04:00
Maidul Islam
ad7972e7e1 Merge pull request #552 from Infisical/snyk-upgrade-5957efd1bee99a5df1416be8165fe61a
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.309.0 to 3.312.0
2023-05-07 19:13:46 -04:00
Maidul Islam
c6d8f24968 Merge pull request #562 from Infisical/snyk-upgrade-2f346d9e1a2c15f0c0929d23f00e36e5
[Snyk] Upgrade @sentry/tracing from 7.47.0 to 7.48.0
2023-05-07 19:13:36 -04:00
Maidul Islam
d8ff0bef0d add semantic-version gh action 2023-05-07 19:09:24 -04:00
Maidul Islam
29b96246b9 add back osx cross build 2023-05-07 17:00:12 -04:00
Maidul Islam
8503c9355b add completions for aurs 2023-05-07 16:55:42 -04:00
Maidul Islam
ddf0a272f6 back out of dir for archive file 2023-05-07 15:41:23 -04:00
Maidul Islam
e3980f8666 bring back completions and man page for cli 2023-05-07 15:27:19 -04:00
Vladyslav Matsiiako
d52534b185 Dashboard UI update 2023-05-07 12:24:40 -07:00
Sheen Capadngan
4c434555a4 finalized signup/signin ux regarding redirects 2023-05-07 20:54:30 +08:00
Tuan Dang
f011d61167 Merge remote-tracking branch 'origin' into revised-encryption-key 2023-05-06 22:22:03 +03:00
Tuan Dang
db07a033e1 Add filter query param to getApps for Netlify integration 2023-05-06 22:19:00 +03:00
Tuan Dang
87e047a152 Checkpoint finish preliminary support for ROOT_ENCRYPTION_KEY 2023-05-06 22:07:59 +03:00
Sheen Capadngan
ea86e59d4f resolved component alignment of signup 2023-05-06 19:26:42 +08:00
Sheen Capadngan
3e19e6fd99 finalized login and signup ui 2023-05-06 18:52:01 +08:00
snyk-bot
3c71bcaa8d fix: upgrade @sentry/tracing from 7.47.0 to 7.48.0
Snyk has created this PR to upgrade @sentry/tracing from 7.47.0 to 7.48.0.

See this package in npm:
https://www.npmjs.com/package/@sentry/tracing

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-06 01:50:17 +00:00
vmatsiiako
476d0be101 Merge pull request #560 from akhilmhdh/fix/token-cache-change
fix(ui): resolved token missing due to cache invalidation
2023-05-05 14:53:23 -07:00
Maidul Islam
2eff7b6128 set internal port for render 2023-05-05 16:26:03 -04:00
Maidul Islam
d8a781af1f remove health check 2023-05-05 16:06:28 -04:00
Maidul Islam
8b42f4f998 typo in doc 2023-05-05 15:50:19 -04:00
Maidul Islam
da127a3c0a update step 2 of fly.io 2023-05-05 15:48:03 -04:00
Maidul Islam
d4aa75a182 update self hosting docs layout 2023-05-05 15:42:41 -04:00
Maidul Islam
d097003e9b set sync=false for mongo db url render 2023-05-05 14:37:24 -04:00
Maidul Islam
b615a5084e update render IaC template 2023-05-05 14:31:11 -04:00
Maidul Islam
379f086828 add render IaC 2023-05-05 14:28:25 -04:00
akhilmhdh
f11a7d0f87 fix(ui): resolved token missing due to cache invalidation 2023-05-05 21:56:26 +05:30
Maidul Islam
f5aeb85c62 rename standalone docker image 2023-05-05 08:43:57 -04:00
Tuan Dang
3d3d7c9821 Merge remote-tracking branch 'origin' into revised-encryption-key 2023-05-05 10:27:44 +03:00
vmatsiiako
2966aa6eda Merge pull request #554 from akhilmhdh/feat/dashboard-v2
feat(ui): fixed lagging issues with new dashboard
2023-05-04 15:38:00 -07:00
Vladyslav Matsiiako
b1f2515731 fixed minor bugs and updated the design 2023-05-04 15:31:06 -07:00
Maidul Islam
c5094ec37d patch copy invite link 2023-05-04 18:27:09 -04:00
Maidul Islam
6c745f617d add org id to complete invite link 2023-05-04 17:50:36 -04:00
Tuan Dang
5eeda6272c Checkpoint adding crypto metadata 2023-05-04 20:35:06 +03:00
Sheen Capadngan
b734b51954 developed new ui for new login and signup page 2023-05-05 00:39:28 +08:00
akhilmhdh
82995fbd02 feat(ui): fixed lagging issues with new dashboard 2023-05-04 20:45:26 +05:30
snyk-bot
8d09a45454 fix: upgrade @aws-sdk/client-secrets-manager from 3.309.0 to 3.312.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.309.0 to 3.312.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-04 00:03:35 +00:00
Vladyslav Matsiiako
38f578c4ae Fixed the issue with favicon 2023-05-03 16:06:50 -07:00
Maidul Islam
65b12eee5e update standlone gwf 2023-05-03 17:22:32 -04:00
Maidul Islam
9043db4727 add github workflow to release standalone app 2023-05-03 17:14:24 -04:00
Maidul Islam
0eceeb6aa9 create standalone infisical docker file 2023-05-03 16:57:19 -04:00
Maidul Islam
2d2bbbd0ad Update README.md 2023-05-03 15:51:15 -04:00
Maidul Islam
c9b4e11539 add note to ENCRYPTION_KEY to indicate non prod 2023-05-03 15:48:20 -04:00
Maidul Islam
fd4ea97e18 remove default smtp since Infisical no longer requires SMTP 2023-05-03 15:45:16 -04:00
Maidul Islam
49d2ecc460 switch install command to run prod docker compose 2023-05-03 15:41:11 -04:00
Sheen Capadngan
1172726e74 added signup v3 endpoints and developed initial new signup flow 2023-05-04 01:32:40 +08:00
Tuan Dang
c766686670 Fix merge conflicts for variable imports 2023-05-03 19:30:30 +03:00
BlackMagiq
ca31a70032 Merge pull request #550 from Infisical/gmail-smtp-support
Add support for Gmail SMTP + docs
2023-05-03 18:34:49 +03:00
Tuan Dang
3334338eaa Add Gmail SMTP option + docs 2023-05-03 18:28:20 +03:00
Tuan Dang
099cee7f39 Begin refactoring backfilling and preparation operations into setup and start adding encryption metadata to models 2023-05-03 14:21:42 +03:00
Sheen Capadngan
f703ee29e5 implemented comments 2023-05-03 18:58:32 +08:00
Maidul Islam
6d5e281811 add helm version requirement 2023-05-02 11:11:41 -04:00
Maidul Islam
87d36ac47a Merge pull request #547 from Infisical/snyk-upgrade-78c720000b2ea0a6b50d66fd8a2a84f9
[Snyk] Upgrade bigint-conversion from 2.3.0 to 2.4.0
2023-05-01 20:44:29 -04:00
Maidul Islam
b72e1198df Merge pull request #548 from Infisical/snyk-upgrade-965bd6eb4d7e75fef5c7e8cb5d4a3e5a
[Snyk] Upgrade mongoose from 6.10.4 to 6.10.5
2023-05-01 20:44:14 -04:00
Maidul Islam
837ea2ef40 add sem var to docker image workflow 2023-05-01 20:43:28 -04:00
Tuan Dang
b462ca3e89 Patch missing function invocation for GitLab envar 2023-05-01 22:38:01 +03:00
BlackMagiq
f639f682c9 Merge pull request #458 from Spelchure/removing-sentry-logs
Replace Sentry error handling logic
2023-05-01 22:35:11 +03:00
snyk-bot
365fcb3044 fix: upgrade mongoose from 6.10.4 to 6.10.5
Snyk has created this PR to upgrade mongoose from 6.10.4 to 6.10.5.

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

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-01 17:58:22 +00:00
snyk-bot
01d9695153 fix: upgrade bigint-conversion from 2.3.0 to 2.4.0
Snyk has created this PR to upgrade bigint-conversion from 2.3.0 to 2.4.0.

See this package in npm:
https://www.npmjs.com/package/bigint-conversion

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-05-01 17:58:17 +00:00
Spelchure
21eb1815c4 feat: remove try-catch blocks for handling errors in middleware 2023-05-01 17:14:39 +03:00
BlackMagiq
85f3ae95b6 Merge pull request #546 from Infisical/update-docs
Add local machine to deployment options
2023-05-01 16:04:11 +03:00
Tuan Dang
e888eed1bf Add local machine to deployment options 2023-05-01 16:02:46 +03:00
Maidul Islam
addac63700 fix broken link for start guide 2023-04-30 15:56:10 -04:00
Maidul Islam
efd13e6b19 remove completions 2023-04-30 15:49:12 -04:00
Maidul Islam
4ac74e6e9a add back completion with dir 2023-04-30 15:36:55 -04:00
BlackMagiq
1d422fa82c Merge pull request #545 from Infisical/docs-guides
Add Preliminary Guides to Docs, Delete README translations
2023-04-30 22:28:06 +03:00
BlackMagiq
8ba3f8d1f7 Merge branch 'main' into docs-guides 2023-04-30 22:25:22 +03:00
Tuan Dang
6b83393952 Add initial Node, Python, Nextjs + Vercel guides to docs, delete README translations 2023-04-30 22:21:34 +03:00
Maidul Islam
da07d71e15 remove completions 2023-04-30 12:42:21 -04:00
vmatsiiako
82d3971d9e Update README.md 2023-04-30 09:07:25 -07:00
Maidul Islam
3dd21374e7 update go releaser distribution 2023-04-30 11:40:19 -04:00
Maidul Islam
c5fe41ae57 Merge pull request #543 from Infisical/multi-tag-repo
Only trigger CLI builds for tags with prefix infisical-cli/v*.*.*
2023-04-30 11:30:36 -04:00
Maidul Islam
9f0313f50b strip v from existing tags 2023-04-30 11:28:55 -04:00
Maidul Islam
a6e670e93a update tag fetch method to filetr for cli tags only 2023-04-30 11:22:28 -04:00
Maidul Islam
ec97e1a930 add mono repo support for goreleaser 2023-04-30 11:09:29 -04:00
Maidul Islam
55ca6938db update cli github action to only listen to infisical-cli/{version} tags 2023-04-30 11:08:58 -04:00
Maidul Islam
1401c7f6bc add go releaser pro 2023-04-30 10:32:39 -04:00
Tuan Dang
bb6d0fd7c6 Patch .secretValue access in INVITE_ONLY_SIGNUP 2023-04-30 14:56:27 +03:00
Tuan Dang
689a20dca2 Begin adding guides to docs 2023-04-30 14:54:54 +03:00
Maidul Islam
e4b4126971 Merge pull request #540 from Infisical/snyk-upgrade-291700b772b89271eb89e390de3aca7f
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.306.0 to 3.309.0
2023-04-29 15:14:27 -04:00
Maidul Islam
04b04cba5c Merge pull request #539 from Infisical/snyk-upgrade-7c8dbe159d6a113e8720970276ee888f
[Snyk] Upgrade sharp from 0.31.3 to 0.32.0
2023-04-29 15:13:58 -04:00
Maidul Islam
89e5f644a4 Update README.md 2023-04-29 15:13:27 -04:00
BlackMagiq
c5619d27d7 Merge pull request #542 from Infisical/revise-readme
Updated README
2023-04-29 21:43:17 +03:00
Tuan Dang
12a1d8e822 Update README 2023-04-29 21:41:33 +03:00
Tuan Dang
a85a7d1b00 Update README 2023-04-29 21:23:05 +03:00
Tuan Dang
fc2846534f Update README 2023-04-29 21:06:25 +03:00
Tuan Dang
2b605856a3 Update README 2023-04-29 20:55:52 +03:00
BlackMagiq
191582ef26 Merge pull request #541 from Infisical/revise-quickstart
Add quickstarts to documentation
2023-04-29 20:40:34 +03:00
Tuan Dang
213b5d465b Merge remote-tracking branch 'origin' into revise-quickstart 2023-04-29 20:39:30 +03:00
Tuan Dang
75f550caf2 Finish documentation quickstarts update 2023-04-29 20:38:58 +03:00
Maidul Islam
daabf5ab70 add k8 quick start 2023-04-29 12:24:03 -04:00
Tuan Dang
7b11976a60 Preliminary README change proposal 2023-04-29 18:55:27 +03:00
Maidul Islam
39be52c6b2 make minor changes to wording for quick start guide 2023-04-29 11:27:04 -04:00
Tuan Dang
bced5d0151 Complete preliminary new quickstarts 2023-04-29 14:39:22 +03:00
snyk-bot
939d7eb433 fix: upgrade @aws-sdk/client-secrets-manager from 3.306.0 to 3.309.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.306.0 to 3.309.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-29 00:29:10 +00:00
snyk-bot
6de25174aa fix: upgrade sharp from 0.31.3 to 0.32.0
Snyk has created this PR to upgrade sharp from 0.31.3 to 0.32.0.

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

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-04-28 20:42:03 +00:00
snyk-bot
fd9387a25e fix: upgrade fs from 0.0.1-security to 0.0.2
Snyk has created this PR to upgrade fs from 0.0.1-security to 0.0.2.

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

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-04-28 20:41:59 +00:00
snyk-bot
b17a40d83e fix: upgrade @fortawesome/react-fontawesome from 0.1.19 to 0.2.0
Snyk has created this PR to upgrade @fortawesome/react-fontawesome from 0.1.19 to 0.2.0.

See this package in npm:
https://www.npmjs.com/package/@fortawesome/react-fontawesome

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-04-28 20:41:56 +00:00
Maidul Islam
2aa79d4ad6 Merge pull request #518 from seonggwonyoon/main
Add namespace option for using helm
2023-04-28 14:18:38 -04:00
Maidul Islam
44b4de754a remove test check in workflow 2023-04-28 13:21:38 -04:00
Maidul Islam
db0f0d0d9c disable secrets integ tests temp 2023-04-28 13:18:25 -04:00
Vladyslav Matsiiako
3471e387ae Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-28 10:11:36 -07:00
Vladyslav Matsiiako
aadd964409 Fix the deployment issue 2023-04-28 10:11:25 -07:00
Tuan Dang
102e45891c Update getAppsGitHub to include pagination 2023-04-28 20:10:29 +03:00
Tuan Dang
b9ae224aef Patch organization invitation emails expiring for existing users and billing logic affected by missing organization populate call 2023-04-28 17:57:50 +03:00
Tuan Dang
e5cb0cbca3 Add preliminary platform, sdks, and cli quickstarts 2023-04-28 14:30:13 +03:00
Vladyslav Matsiiako
330968c7af added gradient to the menu 2023-04-27 19:46:01 -07:00
Vladyslav Matsiiako
68e8e727cd Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-27 18:44:46 -07:00
Vladyslav Matsiiako
3b94ee42e9 Animated menu icons 2023-04-27 18:44:23 -07:00
Maidul Islam
09286b4421 Merge pull request #530 from PylotLight/update-k8s-doc
Update k8s doc to add backend service info
2023-04-27 11:24:31 -04:00
Maidul Islam
04a9604ba9 add advanced use cases for hostAPI 2023-04-27 11:14:47 -04:00
Sheen Capadngan
dfb84e9932 developed initial version of new login page 2023-04-27 23:10:27 +08:00
Maidul Islam
d86f88db92 Merge pull request #526 from Infisical/snyk-upgrade-9829915033f54fef09ffef896e2c5908
[Snyk] Upgrade @sentry/tracing from 7.46.0 to 7.47.0
2023-04-27 09:57:55 -04:00
Maidul Islam
fc53c094b7 Merge branch 'main' into snyk-upgrade-9829915033f54fef09ffef896e2c5908 2023-04-27 09:57:49 -04:00
Maidul Islam
6726ca1882 Merge pull request #522 from Infisical/snyk-upgrade-521a72e06b59b78e721ff564679159b3
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.303.0 to 3.306.0
2023-04-27 09:57:05 -04:00
Maidul Islam
ddbe4d7040 Merge pull request #527 from Infisical/snyk-upgrade-714666653eb4091158908b7ca4704cbb
[Snyk] Upgrade @sentry/node from 7.46.0 to 7.47.0
2023-04-27 09:56:53 -04:00
Maidul Islam
3f6b0a9e66 Merge pull request #528 from Infisical/snyk-upgrade-8b1f2b028bcdff3d60cbaa239abb732d
[Snyk] Upgrade axios from 1.3.4 to 1.3.5
2023-04-27 09:56:43 -04:00
Light
c3a47597b6 fix formatting 2023-04-27 23:31:33 +10:00
Light
a696a99232 add backend service inof to doc 2023-04-27 23:28:19 +10:00
BlackMagiq
8b1e64f75e Merge pull request #529 from Infisical/python-sdk-docs
Finish Python SDK docs
2023-04-27 15:57:19 +03:00
Tuan Dang
f137087ef1 Finish Python SDK docs 2023-04-27 15:53:23 +03:00
snyk-bot
2157fab181 fix: upgrade axios from 1.3.4 to 1.3.5
Snyk has created this PR to upgrade axios from 1.3.4 to 1.3.5.

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

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-27 04:43:44 +00:00
snyk-bot
d2acab57e0 fix: upgrade @sentry/node from 7.46.0 to 7.47.0
Snyk has created this PR to upgrade @sentry/node from 7.46.0 to 7.47.0.

See this package in npm:
https://www.npmjs.com/package/@sentry/node

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-27 04:43:39 +00:00
snyk-bot
811929987b fix: upgrade @sentry/tracing from 7.46.0 to 7.47.0
Snyk has created this PR to upgrade @sentry/tracing from 7.46.0 to 7.47.0.

See this package in npm:
https://www.npmjs.com/package/@sentry/tracing

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-27 04:43:36 +00:00
mv-turtle
4ac13f61e0 Update README.md 2023-04-26 12:05:13 -07:00
Maidul Islam
3d2b0fa3fc Update docker-image.yml 2023-04-26 15:03:31 -04:00
Sheen Capadngan
2dd1570200 updated use of environment variables to utilize await 2023-04-27 02:00:16 +08:00
Sheen Capadngan
69472514af Merge branch 'main' into feature/google-signin-signup-integration 2023-04-27 01:46:26 +08:00
Sheen Capadngan
f956170820 added auth v3 endpoints for login1 and login2 2023-04-27 01:38:06 +08:00
Maidul Islam
242809ce26 add folders to batch and get secrets api 2023-04-26 12:53:14 -04:00
Tuan Dang
492bf39243 Clarify getSecret and caching behavior in docs 2023-04-26 12:11:46 +03:00
BlackMagiq
dbfa4f5277 Merge pull request #524 from Infisical/update-node-sdk
Update Infisical to use new Infisical Node SDK 1.1.3.
2023-04-26 11:58:07 +03:00
Tuan Dang
3fd2e22cbd Move Express example for Node SDK to top of that docs page 2023-04-26 11:53:46 +03:00
Tuan Dang
150eb1f5ee Merge remote-tracking branch 'origin' into update-node-sdk 2023-04-26 11:51:21 +03:00
Tuan Dang
6314a949f8 Update Infisical to use Infisical Node SDK 1.1.3 2023-04-26 11:50:51 +03:00
BlackMagiq
660c5806e3 Merge pull request #523 from Infisical/revise-node-sdk-docs
Revise docs for Node SDK
2023-04-26 09:31:54 +03:00
Tuan Dang
c6d2828262 Merge remote-tracking branch 'origin' into revise-node-sdk-docs 2023-04-26 09:30:03 +03:00
snyk-bot
8dedfad22d fix: upgrade @aws-sdk/client-secrets-manager from 3.303.0 to 3.306.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.303.0 to 3.306.0.

See this package in npm:
https://www.npmjs.com/package/@aws-sdk/client-secrets-manager

See this project in Snyk:
https://app.snyk.io/org/maidul98/project/35057e82-ed7d-4e19-ba4d-719a42135cd6?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-04-26 04:29:05 +00:00
Vladyslav Matsiiako
7a3456ca1d scrolling fix 2023-04-25 19:25:31 -07:00
Vladyslav Matsiiako
a946031d6f fix loading animation 2023-04-25 17:14:39 -07:00
Maidul Islam
f0075e8d09 add folder controller 2023-04-25 16:15:18 -04:00
Sheen Capadngan
007e8c4442 initial setup for google signin signup integration 2023-04-25 23:47:46 +08:00
Vladyslav Matsiiako
3b00df6662 Updated readme 2023-04-25 08:12:12 -07:00
Vladyslav Matsiiako
a263d7481b Added truncation for secret names on the comparison screen 2023-04-25 08:11:31 -07:00
Maidul Islam
6f91331549 Merge pull request #519 from Infisical/snyk-fix-c89a9aceb5e7741daf73a9a657eb1ead
[Snyk] Security upgrade yaml from 2.2.1 to 2.2.2
2023-04-25 10:14:55 -04:00
snyk-bot
13ecc22159 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-YAML-5458867
2023-04-25 06:51:32 +00:00
Seonggwon Yoon
a5c5ec1f4d Print helm with namespace 2023-04-25 10:55:27 +09:00
BlackMagiq
cbb28dc373 Merge pull request #517 from satyamgupta1495/patch-3
Added country flag [India]
2023-04-24 15:31:37 +03:00
BlackMagiq
e00aad4159 Merge pull request #515 from satyamgupta1495/patch-2
Translated README.md in Hindi language
2023-04-24 15:30:57 +03:00
Satyam Gupta
fb8aaa9d9f Added country flag [india] 2023-04-24 17:57:33 +05:30
Maidul Islam
4bda67c9f7 remove check for --env for service tokens 2023-04-24 05:16:08 -07:00
Satyam Gupta
e5c5e4cca2 Updated readme.hi.md 2023-04-24 17:26:33 +05:30
Satyam Gupta
803a97fdfc Translated README.md in Hindi language 2023-04-23 23:10:47 +05:30
Tuan Dang
9e42a7a33e Update quickstart example 2023-04-23 15:51:42 +03:00
Tuan Dang
7127b60867 Undo last README change 2023-04-23 14:06:28 +03:00
BlackMagiq
bcba2e9c2c Merge pull request #514 from satyamgupta1495/patch-1
Translated readme in Hindi Language
2023-04-23 14:02:18 +03:00
Tuan Dang
34c79b08bc Update InfisicalClient initialization 2023-04-23 13:38:36 +03:00
Tuan Dang
aacdaf4556 Modify Node SDK docs to be inline with new initializer 2023-04-23 12:45:13 +03:00
Tuan Dang
a7484f8be5 Update node SDK docs, positioning of examples 2023-04-23 09:49:21 +03:00
Satyam Gupta
51154925fd Translated readme in Hindi Language 2023-04-23 03:18:16 +05:30
Tuan Dang
e1bf31b371 Update envars to new node SDK format 2023-04-22 16:20:33 +03:00
Tuan Dang
3817831577 Update docs for upcoming Node SDK update 2023-04-22 14:34:05 +03:00
BlackMagiq
3846c42c00 Merge pull request #508 from Infisical/secrets-v3
Secrets V3 — Blind Indices (Query for Secrets by Name)
2023-04-22 11:53:48 +03:00
Tuan Dang
03110c8a83 Update package-lock.json 2023-04-22 11:50:26 +03:00
Tuan Dang
e0d5644b3a Add back service token data select fields for GET endpoint 2023-04-22 11:47:23 +03:00
Vladyslav Matsiiako
c7172337ed Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-20 21:29:05 -07:00
Vladyslav Matsiiako
7183546e7e Fix dashboard bugs 2023-04-20 21:28:52 -07:00
Maidul Islam
d717430947 add faq for self hosting 2023-04-20 17:56:43 -07:00
Tuan Dang
7fc01df93e Update package-lock.json 2023-04-19 18:18:38 +03:00
Tuan Dang
9f944135b9 Update docs for blind indices and secrets v3 endpoints 2023-04-19 18:15:35 +03:00
Tuan Dang
ad5852fe3a Enable all auth clients for secrets v3, remove serviceTokenData .populate in middleware, make secret versions and rollbacks compatible with blind indexing 2023-04-19 15:38:13 +03:00
Tuan Dang
acb90ee0f7 Add frontend migration support for existing project to be blind-indexed 2023-04-18 12:43:06 +03:00
Tuan Dang
b62ea41e02 Add workspaces v3 endpoints for blind-index naming/labeling 2023-04-17 23:48:48 +03:00
Tuan Dang
763ec1aa0f And workspace-environment specific integrations syncs to secrets v3 endpoints, add PostHog 2023-04-17 14:23:56 +03:00
Tuan Dang
338d287d35 Update package-lock.json 2023-04-17 11:11:18 +03:00
Tuan Dang
df83e8ceb9 Complete first iteration of CRUD secrets operations by name 2023-04-17 11:09:45 +03:00
Tuan Dang
d9afe90885 Begin frontend for blinded indices 2023-04-15 17:39:30 +03:00
Tuan Dang
fcb677d990 Checkpoint argon2id test to generate blind index 2023-04-15 15:21:44 +03:00
Tuan Dang
3eb810b979 Checkpoint 2023-04-15 11:02:56 +03:00
Tuan Dang
3dfb85b03f Merge remote-tracking branch 'origin' into secrets-v3 2023-04-14 17:48:23 +03:00
Tuan Dang
e5e15d26bf Begin foundation for secrets v3 2023-04-09 18:19:53 +03:00
1515 changed files with 95367 additions and 55112 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
backend/node_modules
frontend/node_modules

View File

@@ -1,5 +1,6 @@
# Keys
# Required key for platform encryption/decryption ops
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NOT BE USED FOR PRODUCTION
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# JWT
@@ -8,6 +9,7 @@ JWT_SIGNUP_SECRET=3679e04ca949f914c03332aaaeba805a
JWT_REFRESH_SECRET=5f2f3c8f0159068dc2bbb3a652a716ff
JWT_AUTH_SECRET=4be6ba5602e0fa0ac6ac05c3cd4d247f
JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
JWT_PROVIDER_AUTH_SECRET=f32f716d70a42c5703f4656015e76201
# JWT lifetime
# Optional lifetimes for JWT tokens expressed in seconds or a string
@@ -15,6 +17,7 @@ JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
JWT_AUTH_LIFETIME=
JWT_REFRESH_LIFETIME=
JWT_SIGNUP_LIFETIME=
JWT_PROVIDER_AUTH_LIFETIME=
# MongoDB
# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref
@@ -32,12 +35,10 @@ SITE_URL=http://localhost:8080
# Mail/SMTP
SMTP_HOST=
SMTP_PORT=
SMTP_NAME=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_PORT=587
SMTP_SECURE=false
SMTP_FROM_ADDRESS=
SMTP_FROM_NAME=Infisical
# Integration
# Optional only if integration is used
@@ -46,11 +47,13 @@ CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB=
CLIENT_ID_GITLAB=
CLIENT_ID_BITBUCKET=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB=
CLIENT_SECRET_GITLAB=
CLIENT_SECRET_BITBUCKET=
CLIENT_SLUG_VERCEL=
# Sentry (optional) for monitoring errors
@@ -60,10 +63,6 @@ SENTRY_DSN=
# Ignore - Not applicable for self-hosted version
POSTHOG_HOST=
POSTHOG_PROJECT_API_KEY=
STRIPE_SECRET_KEY=
STRIPE_PUBLISHABLE_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRODUCT_STARTER=
STRIPE_PRODUCT_TEAM=
STRIPE_PRODUCT_PRO=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
CLIENT_ID_GOOGLE=
CLIENT_SECRET_GOOGLE=

BIN
.github/images/Deploy to AWS.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
.github/images/deploy-aws-button.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
.github/images/deploy-to-aws.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
.github/images/do-k8-install-btn.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -13,6 +13,7 @@ services:
- MONGO_URL=mongodb://test:example@mongo:27017/?authSource=admin
- MONGO_USERNAME=test
- MONGO_PASSWORD=example
- ENCRYPTION_KEY=a984ecdf82ec779e55dbcc21303a900f
networks:
- infisical-test

15
.github/values.yaml vendored
View File

@@ -1,3 +1,10 @@
# secretScanningGitApp:
# enabled: false
# deploymentAnnotations:
# secrets.infisical.com/auto-reload: "true"
# image:
# repository: infisical/staging_deployment_secret-scanning-git-app
frontend:
enabled: true
name: frontend
@@ -6,7 +13,7 @@ frontend:
secrets.infisical.com/auto-reload: "true"
replicaCount: 2
image:
repository: infisical/frontend
repository: infisical/staging_deployment_frontend
tag: "latest"
pullPolicy: Always
kubeSecretRef: managed-secret-frontend
@@ -25,7 +32,7 @@ backend:
secrets.infisical.com/auto-reload: "true"
replicaCount: 2
image:
repository: infisical/backend
repository: infisical/staging_deployment_backend
tag: "latest"
pullPolicy: Always
kubeSecretRef: managed-backend-secret
@@ -51,8 +58,8 @@ mongodbConnection:
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
# annotations:
# kubernetes.io/ingress.class: "nginx"
# cert-manager.io/issuer: letsencrypt-nginx
hostName: gamma.infisical.com ## <- Replace with your own domain
frontend:

View File

@@ -0,0 +1,118 @@
name: Release production images (frontend, backend)
on:
push:
tags:
- "infisical/v*.*.*"
jobs:
backend-image:
name: Build backend image
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production
working-directory: backend
- name: 🧪 Run tests
run: npm run test:ci
working-directory: backend
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 📦 Build backend and export to Docker
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
load: true
context: backend
tags: infisical/backend:test
- name: ⏻ Spawn backend container and dependencies
run: |
docker compose -f .github/resources/docker-compose.be-test.yml up --wait --quiet-pull
- name: 🧪 Test backend image
run: |
./.github/resources/healthcheck.sh infisical-backend-test
- name: ⏻ Shut down backend container and dependencies
run: |
docker compose -f .github/resources/docker-compose.be-test.yml down
- name: 🏗️ Build backend and push
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: backend
tags: |
infisical/backend:${{ steps.commit.outputs.short }}
infisical/backend:latest
infisical/backend:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64
frontend-image:
name: Build frontend image
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 📦 Build frontend and export to Docker
uses: depot/build-push-action@v1
with:
load: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
project: 64mmf0n610
context: frontend
tags: infisical/frontend:test
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
- name: ⏻ Spawn frontend container
run: |
docker run -d --rm --name infisical-frontend-test infisical/frontend:test
- name: 🧪 Test frontend image
run: |
./.github/resources/healthcheck.sh infisical-frontend-test
- name: ⏻ Shut down frontend container
run: |
docker stop infisical-frontend-test
- name: 🏗️ Build frontend and push
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
push: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: frontend
tags: |
infisical/frontend:${{ steps.commit.outputs.short }}
infisical/frontend:latest
infisical/frontend:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}

View File

@@ -5,7 +5,6 @@ jobs:
backend-image:
name: Build backend image
runs-on: ubuntu-latest
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
@@ -51,14 +50,14 @@ jobs:
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: backend
tags: infisical/backend:${{ steps.commit.outputs.short }},
infisical/backend:latest
tags: |
infisical/staging_deployment_backend:${{ steps.commit.outputs.short }}
infisical/staging_deployment_backend:latest
platforms: linux/amd64,linux/arm64
frontend-image:
name: Build frontend image
runs-on: ubuntu-latest
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
@@ -81,12 +80,12 @@ jobs:
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
project: 64mmf0n610
context: frontend
tags: infisical/frontend:test
tags: infisical/staging_deployment_frontend:test
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
- name: ⏻ Spawn frontend container
run: |
docker run -d --rm --name infisical-frontend-test infisical/frontend:test
docker run -d --rm --name infisical-frontend-test infisical/staging_deployment_frontend:test
- name: 🧪 Test frontend image
run: |
./.github/resources/healthcheck.sh infisical-frontend-test
@@ -100,8 +99,9 @@ jobs:
push: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: frontend
tags: infisical/frontend:${{ steps.commit.outputs.short }},
infisical/frontend:latest
tags: |
infisical/staging_deployment_frontend:${{ steps.commit.outputs.short }}
infisical/staging_deployment_frontend:latest
platforms: linux/amd64,linux/arm64
build-args: |
POSTHOG_API_KEY=${{ secrets.PUBLIC_POSTHOG_API_KEY }}
@@ -135,7 +135,7 @@ jobs:
- name: Download helm values to file and upgrade gamma deploy
run: |
wget https://raw.githubusercontent.com/Infisical/infisical/main/.github/values.yaml
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --recreate-pods
helm upgrade infisical infisical-helm-charts/infisical --values values.yaml --wait --install
if [[ $(helm status infisical) == *"FAILED"* ]]; then
echo "Helm upgrade failed"
exit 1

View File

@@ -13,6 +13,7 @@ jobs:
check-be-pr:
name: Check
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: ☁️ Checkout source
@@ -26,17 +27,17 @@ jobs:
- name: 📦 Install dependencies
run: npm ci --only-production
working-directory: backend
- name: 🧪 Run tests
run: npm run test:ci
working-directory: backend
- name: 📁 Upload test results
uses: actions/upload-artifact@v3
if: always()
with:
name: be-test-results
path: |
./backend/reports
./backend/coverage
# - name: 🧪 Run tests
# run: npm run test:ci
# working-directory: backend
# - name: 📁 Upload test results
# uses: actions/upload-artifact@v3
# if: always()
# with:
# name: be-test-results
# path: |
# ./backend/reports
# ./backend/coverage
- name: 🏗️ Run build
run: npm run build
working-directory: backend

View File

@@ -2,40 +2,35 @@ name: Check Frontend Pull Request
on:
pull_request:
types: [ opened, synchronize ]
types: [opened, synchronize]
paths:
- 'frontend/**'
- '!frontend/README.md'
- '!frontend/.*'
- 'frontend/.eslintrc.js'
- "frontend/**"
- "!frontend/README.md"
- "!frontend/.*"
- "frontend/.eslintrc.js"
jobs:
check-fe-pr:
name: Check
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
-
name: ☁️ Checkout source
- name: ☁️ Checkout source
uses: actions/checkout@v3
-
name: 🔧 Setup Node 16
- name: 🔧 Setup Node 16
uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'npm'
node-version: "16"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
-
name: 📦 Install dependencies
- name: 📦 Install dependencies
run: npm ci --only-production --ignore-scripts
working-directory: frontend
# -
# name: 🧪 Run tests
# run: npm run test:ci
# working-directory: frontend
-
name: 🏗️ Run build
- name: 🏗️ Run build
run: npm run build
working-directory: frontend

View File

@@ -0,0 +1,75 @@
name: Release standalone docker image
on:
push:
tags:
- "infisical/v*.*.*"
jobs:
infisical-standalone:
name: Build infisical standalone image
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical/}"
- name: ☁️ Checkout source
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: 📦 Install dependencies to test all dependencies
run: npm ci --only-production
working-directory: backend
- uses: paulhatch/semantic-version@v5.0.2
id: version
with:
# The prefix to use to identify tags
tag_prefix: "infisical-standalone/v"
# A string which, if present in a git commit, indicates that a change represents a
# major (breaking) change, supports regular expressions wrapped with '/'
major_pattern: "(MAJOR)"
# Same as above except indicating a minor change, supports regular expressions wrapped with '/'
minor_pattern: "(MINOR)"
# A string to determine the format of the version output
version_format: "${major}.${minor}.${patch}-prerelease${increment}"
# Optional path to check for changes. If any changes are detected in the path the
# 'changed' output will true. Enter multiple paths separated by spaces.
change_path: "backend,frontend"
# Prevents pre-v1.0.0 version from automatically incrementing the major version.
# If enabled, when the major version is 0, major releases will be treated as minor and minor as patch. Note that the version_type output is unchanged.
enable_prerelease_mode: true
# - name: 🧪 Run tests
# run: npm run test:ci
# working-directory: backend
- name: version output
run: |
echo "Output Value: ${{ steps.version.outputs.major }}"
echo "Output Value: ${{ steps.version.outputs.minor }}"
echo "Output Value: ${{ steps.version.outputs.patch }}"
echo "Output Value: ${{ steps.version.outputs.version }}"
echo "Output Value: ${{ steps.version.outputs.version_type }}"
echo "Output Value: ${{ steps.version.outputs.increment }}"
- name: Save commit hashes for tag
id: commit
uses: pr-mpt/actions-commit-hash@v2
- name: 🔧 Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: 🐋 Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: 📦 Build backend and export to Docker
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
push: true
context: .
tags: |
infisical/infisical:latest
infisical/infisical:${{ steps.commit.outputs.short }}
infisical/infisical:${{ steps.extract_version.outputs.version }}
platforms: linux/amd64,linux/arm64
file: Dockerfile.standalone-infisical

View File

@@ -4,7 +4,7 @@ on:
push:
# run only against tags
tags:
- "v*"
- "infisical-cli/v*.*.*"
permissions:
contents: write
@@ -41,13 +41,15 @@ jobs:
git clone https://github.com/plentico/osxcross-target.git ../../osxcross/target
- uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
distribution: goreleaser-pro
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}
FURY_TOKEN: ${{ secrets.FURYPUSHTOKEN }}
AUR_KEY: ${{ secrets.AUR_KEY }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
- uses: actions/setup-python@v4
- run: pip install --upgrade cloudsmith-cli
- name: Publish to CloudSmith

View File

@@ -1,10 +1,16 @@
name: Release Docker image for K8 operator
on: [workflow_dispatch]
on:
push:
tags:
- "infisical-k8-operator/v*.*.*"
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Extract version from tag
id: extract_version
run: echo "::set-output name=version::${GITHUB_REF_NAME#infisical-k8-operator/}"
- uses: actions/checkout@v2
- name: 🔧 Set up QEMU
@@ -26,4 +32,6 @@ jobs:
context: k8-operator
push: true
platforms: linux/amd64,linux/arm64
tags: infisical/kubernetes-operator:latest
tags: |
infisical/kubernetes-operator:latest
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
node_modules
.env
.env.dev
.env.gamma
.env.prod
.env.infisical

View File

@@ -11,10 +11,16 @@ before:
- ./cli/scripts/completions.sh
- ./cli/scripts/manpages.sh
monorepo:
tag_prefix: infisical-cli/
dir: cli
builds:
- id: darwin-build
binary: infisical
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
ldflags:
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
flags:
- -trimpath
env:
@@ -32,7 +38,9 @@ builds:
env:
- CGO_ENABLED=0
binary: infisical
ldflags: -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
ldflags:
- -X github.com/Infisical/infisical-merge/packages/util.CLI_VERSION={{ .Version }}
- -X github.com/Infisical/infisical-merge/packages/telemetry.POSTHOG_API_KEY_FOR_CLI={{ .Env.POSTHOG_API_KEY_FOR_CLI }}
flags:
- -trimpath
goos:
@@ -61,10 +69,10 @@ archives:
- goos: windows
format: zip
files:
- README*
- LICENSE*
- manpages/*
- completions/*
- ../README*
- ../LICENSE*
- ../manpages/*
- ../completions/*
release:
replace_existing_draft: true
@@ -74,14 +82,7 @@ checksum:
name_template: "checksums.txt"
snapshot:
name_template: "{{ incpatch .Version }}-devel"
changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
name_template: "{{ .Version }}-devel"
# publishers:
# - name: fury.io
@@ -107,6 +108,22 @@ brews:
zsh_completion.install "completions/infisical.zsh" => "_infisical"
fish_completion.install "completions/infisical.fish"
man1.install "manpages/infisical.1.gz"
- name: 'infisical@{{.Version}}'
tap:
owner: Infisical
name: homebrew-get-cli
commit_author:
name: "Infisical"
email: ai@infisical.com
folder: Formula
homepage: "https://infisical.com"
description: "The official Infisical CLI"
install: |-
bin.install "infisical"
bash_completion.install "completions/infisical.bash" => "infisical"
zsh_completion.install "completions/infisical.zsh" => "_infisical"
fish_completion.install "completions/infisical.fish"
man1.install "manpages/infisical.1.gz"
nfpms:
- id: infisical
@@ -164,7 +181,7 @@ aurs:
mkdir -p "${pkgdir}/usr/share/zsh/site-functions/"
mkdir -p "${pkgdir}/usr/share/fish/vendor_completions.d/"
install -Dm644 "./completions/infisical.bash" "${pkgdir}/usr/share/bash-completion/completions/infisical"
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/infisical"
install -Dm644 "./completions/infisical.zsh" "${pkgdir}/usr/share/zsh/site-functions/_infisical"
install -Dm644 "./completions/infisical.fish" "${pkgdir}/usr/share/fish/vendor_completions.d/infisical.fish"
# man pages
install -Dm644 "./manpages/infisical.1.gz" "${pkgdir}/usr/share/man/man1/infisical.1.gz"

View File

@@ -3,3 +3,5 @@
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
infisical scan git-changes --staged -v

1
.infisicalignore Normal file
View File

@@ -0,0 +1 @@
.github/resources/docker-compose.be-test.yml:generic-api-key:16

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"workbench.editor.wrapTabs": true
}

View File

@@ -0,0 +1,107 @@
ARG POSTHOG_HOST=https://app.posthog.com
ARG POSTHOG_API_KEY=posthog-api-key
FROM node:16-alpine AS frontend-dependencies
WORKDIR /app
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
# Install dependencies
RUN npm ci --only-production --ignore-scripts
# Rebuild the source code only when needed
FROM node:16-alpine AS frontend-builder
WORKDIR /app
# Copy dependencies
COPY --from=frontend-dependencies /app/node_modules ./node_modules
# Copy all files
COPY /frontend .
ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
# Build
RUN npm run build
# Production image
FROM node:16-alpine AS frontend-runner
WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN mkdir -p /app/.next/cache/images && chown nextjs:nodejs /app/.next/cache/images
VOLUME /app/.next/cache/images
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
COPY --chown=nextjs:nodejs --chmod=555 frontend/scripts ./scripts
COPY --from=frontend-builder /app/public ./public
RUN chown nextjs:nodejs ./public/data
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=frontend-builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
ENV NEXT_TELEMETRY_DISABLED 1
##
## BACKEND
##
FROM node:16-alpine AS backend-build
WORKDIR /app
COPY backend/package*.json ./
RUN npm ci --only-production
COPY /backend .
RUN npm run build
# Production stage
FROM node:16-alpine AS backend-runner
WORKDIR /app
COPY backend/package*.json ./
RUN npm ci --only-production
COPY --from=backend-build /app .
# Production stage
FROM node:16-alpine AS production
WORKDIR /
# Install PM2
RUN npm install -g pm2
# Copy ecosystem.config.js
COPY ecosystem.config.js .
RUN apk add --no-cache nginx
COPY nginx/default-stand-alone-docker.conf /etc/nginx/nginx.conf
COPY --from=backend-runner /app /backend
COPY --from=frontend-runner /app/ /app/
EXPOSE 80
ENV HTTPS_ENABLED false
CMD ["pm2-runtime", "start", "ecosystem.config.js"]

405
README.md

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,13 @@
# Security Policy
## Supported Versions
## Supported versions
We always recommend using the latest version of Infisical to ensure you get all security updates.
## Reporting a Vulnerability
## Reporting vulnerabilities
Please report security vulnerabilities or concerns to team@infisical.com.
Please do not file GitHub issues or post on our public forum for security vulnerabilities, as they are public!
Infisical takes security issues very seriously. If you have any concerns about Infisical or believe you have uncovered a vulnerability, please get in touch via the e-mail address security@infisical.com. In the message, try to provide a description of the issue and ideally a way of reproducing it. The security team will get back to you as soon as possible.
Note that this security address should be used only for undisclosed vulnerabilities. Please report any security problems to us before disclosing it publicly.

View File

@@ -1,12 +1,40 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"plugins": [
"@typescript-eslint",
"unused-imports"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"no-console": 2
"no-empty-function": "off",
"@typescript-eslint/no-empty-function": "off",
"no-console": 2,
"quotes": [
"error",
"double",
{
"avoidEscape": true
}
],
"comma-dangle": [
"error",
"only-multiline"
],
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"sort-imports": 1
}
}

7
backend/.prettierrc Normal file
View File

@@ -0,0 +1,7 @@
{
"singleQuote": false,
"printWidth": 100,
"trailingComma": "none",
"tabWidth": 2,
"semi": true
}

View File

@@ -19,6 +19,10 @@ RUN npm ci --only-production
COPY --from=build /app .
RUN apk add --no-cache bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
&& apk add infisical=0.8.1
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s \
CMD node healthcheck.js

View File

@@ -14,7 +14,7 @@ declare global {
JWT_SIGNUP_LIFETIME: string;
JWT_SIGNUP_SECRET: string;
MONGO_URL: string;
NODE_ENV: 'development' | 'staging' | 'testing' | 'production';
NODE_ENV: "development" | "staging" | "testing" | "production";
VERBOSE_ERROR_OUTPUT: string;
LOKI_HOST: string;
CLIENT_ID_HEROKU: string;
@@ -39,12 +39,6 @@ declare global {
SMTP_PASSWORD: string;
SMTP_FROM_ADDRESS: string;
SMTP_FROM_NAME: string;
STRIPE_PRODUCT_STARTER: string;
STRIPE_PRODUCT_TEAM: string;
STRIPE_PRODUCT_PRO: string;
STRIPE_PUBLISHABLE_KEY: string;
STRIPE_SECRET_KEY: string;
STRIPE_WEBHOOK_SECRET: string;
TELEMETRY_ENABLED: string;
LICENSE_KEY: string;
}

View File

@@ -1,9 +1,9 @@
export default {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverageFrom: ['src/*.{js,ts}', '!**/node_modules/**'],
modulePaths: ['<rootDir>/src'],
testMatch: ['<rootDir>/tests/**/*.test.ts'],
setupFiles: ['<rootDir>/test-resources/env-vars.js'],
setupFilesAfterEnv: ['<rootDir>/tests/setupTests.ts']
preset: "ts-jest",
testEnvironment: "node",
collectCoverageFrom: ["src/*.{js,ts}", "!**/node_modules/**"],
modulePaths: ["<rootDir>/src"],
testMatch: ["<rootDir>/tests/**/*.test.ts"],
setupFiles: ["<rootDir>/test-resources/env-vars.js"],
setupFilesAfterEnv: ["<rootDir>/tests/setupTests.ts"],
};

12619
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +1,46 @@
{
"dependencies": {
"@aws-sdk/client-secrets-manager": "^3.303.0",
"@godaddy/terminus": "^4.11.2",
"@aws-sdk/client-secrets-manager": "^3.319.0",
"@godaddy/terminus": "^4.12.0",
"@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.45.0",
"@sentry/tracing": "^7.46.0",
"@sentry/node": "^7.41.0",
"@sentry/node": "^7.49.0",
"@sentry/tracing": "^7.48.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0",
"aws-sdk": "^2.1338.0",
"axios": "^1.1.3",
"argon2": "^0.30.3",
"aws-sdk": "^2.1364.0",
"axios": "^1.3.5",
"axios-retry": "^3.4.0",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
"builder-pattern": "^2.2.0",
"bigint-conversion": "^2.4.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-async-errors": "^3.1.1",
"express-rate-limit": "^6.7.0",
"express-validator": "^6.14.2",
"handlebars": "^4.7.7",
"helmet": "^5.1.1",
"infisical-node": "^1.0.37",
"infisical-node": "^1.2.1",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongoose": "^6.10.4",
"mongoose": "^6.10.5",
"nanoid": "^3.3.6",
"node-cache": "^5.1.2",
"nodemailer": "^6.8.0",
"passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0",
"posthog-node": "^2.6.0",
"probot": "^12.3.1",
"query-string": "^7.1.3",
"request-ip": "^3.3.0",
"rate-limit-mongo": "^2.3.2",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"swagger-autogen": "^2.22.0",
"swagger-ui-express": "^4.6.2",
"tweetnacl": "^1.0.3",
@@ -53,7 +57,7 @@
"start": "node build/index.js",
"dev": "nodemon",
"swagger-autogen": "node ./swagger/index.ts",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
"lint": "eslint . --ext .ts",
"lint-and-fix": "eslint . --ext .ts --fix",
"lint-staged": "lint-staged",
@@ -86,6 +90,8 @@
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.3",
"@types/nodemailer": "^6.4.6",
"@types/passport": "^1.0.12",
"@types/picomatch": "^2.3.0",
"@types/supertest": "^2.0.12",
"@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3",
@@ -93,11 +99,13 @@
"@typescript-eslint/parser": "^5.40.1",
"cross-env": "^7.0.3",
"eslint": "^8.26.0",
"eslint-plugin-unused-imports": "^2.0.0",
"install": "^0.13.0",
"jest": "^29.3.1",
"jest-junit": "^15.0.0",
"nodemon": "^2.0.19",
"npm": "^8.19.3",
"smee-client": "^1.2.3",
"supertest": "^6.3.3",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1"

View File

@@ -1532,7 +1532,15 @@
"/api/v1/invite-org/signup": {
"post": {
"description": "",
"parameters": [],
"parameters": [
{
"name": "host",
"in": "header",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
@@ -2071,6 +2079,15 @@
"targetEnvironment": {
"example": "any"
},
"targetEnvironmentId": {
"example": "any"
},
"targetService": {
"example": "any"
},
"targetServiceId": {
"example": "any"
},
"owner": {
"example": "any"
},
@@ -2297,6 +2314,13 @@
"schema": {
"type": "string"
}
},
{
"name": "teamId",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
@@ -2309,6 +2333,107 @@
}
}
},
"/api/v1/integration-auth/{integrationAuthId}/teams": {
"get": {
"description": "",
"parameters": [
{
"name": "integrationAuthId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/integration-auth/{integrationAuthId}/vercel/branches": {
"get": {
"description": "",
"parameters": [
{
"name": "integrationAuthId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "appId",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/integration-auth/{integrationAuthId}/railway/environments": {
"get": {
"description": "",
"parameters": [
{
"name": "integrationAuthId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "appId",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v1/integration-auth/{integrationAuthId}/railway/services": {
"get": {
"description": "",
"parameters": [
{
"name": "integrationAuthId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "appId",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v2/signup/complete-account/signup": {
"post": {
"description": "",
@@ -2870,9 +2995,6 @@
}
}
}
},
"400": {
"description": "Bad Request"
}
},
"security": [
@@ -2882,6 +3004,26 @@
]
}
},
"/api/v2/organizations/{organizationId}/service-accounts": {
"get": {
"description": "",
"parameters": [
{
"name": "organizationId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v2/workspace/{workspaceId}/environments": {
"post": {
"description": "",
@@ -4018,9 +4160,6 @@
"responses": {
"200": {
"description": "OK"
},
"400": {
"description": "Bad Request"
}
},
"requestBody": {
@@ -4073,6 +4212,138 @@
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v2/service-accounts/me": {
"get": {
"description": "",
"parameters": [],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v2/service-accounts/{serviceAccountId}": {
"get": {
"description": "",
"parameters": [
{
"name": "serviceAccountId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"delete": {
"description": "",
"parameters": [
{
"name": "serviceAccountId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v2/service-accounts/": {
"post": {
"description": "",
"parameters": [],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v2/service-accounts/{serviceAccountId}/name": {
"patch": {
"description": "",
"parameters": [
{
"name": "serviceAccountId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"example": "any"
}
}
}
}
}
}
}
},
"/api/v2/service-accounts/{serviceAccountId}/permissions/workspace": {
"get": {
"description": "",
"parameters": [
{
"name": "serviceAccountId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"post": {
"description": "",
"parameters": [
{
"name": "serviceAccountId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
@@ -4080,6 +4351,90 @@
"400": {
"description": "Bad Request"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"environment": {
"example": "any"
},
"workspaceId": {
"example": "any"
},
"read": {
"example": "any"
},
"write": {
"example": "any"
},
"encryptedKey": {
"example": "any"
},
"nonce": {
"example": "any"
}
}
}
}
}
}
}
},
"/api/v2/service-accounts/{serviceAccountId}/permissions/workspace/{serviceAccountWorkspacePermissionId}": {
"delete": {
"description": "",
"parameters": [
{
"name": "serviceAccountId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "serviceAccountWorkspacePermissionId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v2/service-accounts/{serviceAccountId}/keys": {
"get": {
"description": "",
"parameters": [
{
"name": "serviceAccountId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "workspaceId",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
@@ -4149,6 +4504,297 @@
}
}
},
"/api/v3/secrets/": {
"get": {
"description": "",
"parameters": [
{
"name": "workspaceId",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "environment",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v3/secrets/{secretName}": {
"post": {
"description": "",
"parameters": [
{
"name": "secretName",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"workspaceId": {
"example": "any"
},
"environment": {
"example": "any"
},
"type": {
"example": "any"
},
"secretKeyCiphertext": {
"example": "any"
},
"secretKeyIV": {
"example": "any"
},
"secretKeyTag": {
"example": "any"
},
"secretValueCiphertext": {
"example": "any"
},
"secretValueIV": {
"example": "any"
},
"secretValueTag": {
"example": "any"
},
"secretCommentCiphertext": {
"example": "any"
},
"secretCommentIV": {
"example": "any"
},
"secretCommentTag": {
"example": "any"
}
}
}
}
}
}
},
"get": {
"description": "",
"parameters": [
{
"name": "secretName",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "workspaceId",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "environment",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "type",
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
},
"patch": {
"description": "",
"parameters": [
{
"name": "secretName",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"workspaceId": {
"example": "any"
},
"environment": {
"example": "any"
},
"type": {
"example": "any"
},
"secretValueCiphertext": {
"example": "any"
},
"secretValueIV": {
"example": "any"
},
"secretValueTag": {
"example": "any"
}
}
}
}
}
}
},
"delete": {
"description": "",
"parameters": [
{
"name": "secretName",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"workspaceId": {
"example": "any"
},
"environment": {
"example": "any"
},
"type": {
"example": "any"
}
}
}
}
}
}
}
},
"/api/v3/workspaces/{workspaceId}/secrets/blind-index-status": {
"get": {
"description": "",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v3/workspaces/{workspaceId}/secrets": {
"get": {
"description": "",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/api/v3/workspaces/{workspaceId}/secrets/names": {
"post": {
"description": "",
"parameters": [
{
"name": "workspaceId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"secretsToUpdate": {
"example": "any"
}
}
}
}
}
}
}
},
"/api/status": {
"get": {
"description": "",

View File

@@ -1,64 +1,92 @@
import infisical from 'infisical-node';
export const getPort = () => infisical.get('PORT')! || 4000;
export const getInviteOnlySignup = () => infisical.get('INVITE_ONLY_SIGNUP')! == undefined ? false : infisical.get('INVITE_ONLY_SIGNUP');
export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!;
export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10;
export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d';
export const getJwtAuthSecret = () => infisical.get('JWT_AUTH_SECRET')!;
export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')! || '5m';
export const getJwtMfaSecret = () => infisical.get('JWT_MFA_LIFETIME')! || '5m';
export const getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d';
export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_SECRET')!;
export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!;
export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')! || '15m';
export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!;
export const getMongoURL = () => infisical.get('MONGO_URL')!;
export const getNodeEnv = () => infisical.get('NODE_ENV')! || 'production';
export const getVerboseErrorOutput = () => infisical.get('VERBOSE_ERROR_OUTPUT')! === 'true' && true;
export const getLokiHost = () => infisical.get('LOKI_HOST')!;
export const getClientIdAzure = () => infisical.get('CLIENT_ID_AZURE')!;
export const getClientIdHeroku = () => infisical.get('CLIENT_ID_HEROKU')!;
export const getClientIdVercel = () => infisical.get('CLIENT_ID_VERCEL')!;
export const getClientIdNetlify = () => infisical.get('CLIENT_ID_NETLIFY')!;
export const getClientIdGitHub = () => infisical.get('CLIENT_ID_GITHUB')!;
export const getClientIdGitLab = () => infisical.get('CLIENT_ID_GITLAB')!;
export const getClientSecretAzure = () => infisical.get('CLIENT_SECRET_AZURE')!;
export const getClientSecretHeroku = () => infisical.get('CLIENT_SECRET_HEROKU')!;
export const getClientSecretVercel = () => infisical.get('CLIENT_SECRET_VERCEL')!;
export const getClientSecretNetlify = () => infisical.get('CLIENT_SECRET_NETLIFY')!;
export const getClientSecretGitHub = () => infisical.get('CLIENT_SECRET_GITHUB')!;
export const getClientSecretGitLab = () => infisical.get('CLIENT_SECRET_GITLAB')!;
export const getClientSlugVercel = () => infisical.get('CLIENT_SLUG_VERCEL')!;
export const getPostHogHost = () => infisical.get('POSTHOG_HOST')! || 'https://app.posthog.com';
export const getPostHogProjectApiKey = () => infisical.get('POSTHOG_PROJECT_API_KEY')! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
export const getSentryDSN = () => infisical.get('SENTRY_DSN')!;
export const getSiteURL = () => infisical.get('SITE_URL')!;
export const getSmtpHost = () => infisical.get('SMTP_HOST')!;
export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false;
export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587;
export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!;
export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!;
export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!;
export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical';
export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!;
export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!;
export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!;
export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_KEY')!;
export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!;
export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!;
export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true;
export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!;
export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true
export const getHttpsEnabled = () => {
if (getNodeEnv() != "production") {
import InfisicalClient from "infisical-node";
export const client = new InfisicalClient({
token: process.env.INFISICAL_TOKEN!,
});
export const getPort = async () => (await client.getSecret("PORT")).secretValue || 4000;
export const getEncryptionKey = async () => {
const secretValue = (await client.getSecret("ENCRYPTION_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue;
}
export const getRootEncryptionKey = async () => {
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue;
}
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"
export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10;
export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d";
export const getJwtAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue;
export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtMfaSecret = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d";
export const getJwtRefreshSecret = async () => (await client.getSecret("JWT_REFRESH_SECRET")).secretValue;
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue;
export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m";
export const getJwtProviderAuthSecret = async () => (await client.getSecret("JWT_PROVIDER_AUTH_SECRET")).secretValue;
export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
export const getJwtSignupSecret = async () => (await client.getSecret("JWT_SIGNUP_SECRET")).secretValue;
export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue;
export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production";
export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true;
export const getLokiHost = async () => (await client.getSecret("LOKI_HOST")).secretValue;
export const getClientIdAzure = async () => (await client.getSecret("CLIENT_ID_AZURE")).secretValue;
export const getClientIdHeroku = async () => (await client.getSecret("CLIENT_ID_HEROKU")).secretValue;
export const getClientIdVercel = async () => (await client.getSecret("CLIENT_ID_VERCEL")).secretValue;
export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue;
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
export const getClientIdGoogle = async () => (await client.getSecret("CLIENT_ID_GOOGLE")).secretValue;
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue;
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue;
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue;
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue;
export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue;
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue;
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue;
export const getClientSecretGoogle = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE")).secretValue;
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue;
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com";
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE";
export const getSentryDSN = async () => (await client.getSecret("SENTRY_DSN")).secretValue;
export const getSiteURL = async () => (await client.getSecret("SITE_URL")).secretValue;
export const getSmtpHost = async () => (await client.getSecret("SMTP_HOST")).secretValue;
export const getSmtpSecure = async () => (await client.getSecret("SMTP_SECURE")).secretValue === "true" || false;
export const getSmtpPort = async () => parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587;
export const getSmtpUsername = async () => (await client.getSecret("SMTP_USERNAME")).secretValue;
export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWORD")).secretValue;
export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
export const getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue;
export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue;
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue;
export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
export const getLicenseKey = async () => {
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue;
}
export const getLicenseServerKey = async () => {
const secretValue = (await client.getSecret("LICENSE_SERVER_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue;
}
export const getLicenseServerUrl = async () => (await client.getSecret("LICENSE_SERVER_URL")).secretValue || "https://portal.infisical.com";
export const getTelemetryEnabled = async () => (await client.getSecret("TELEMETRY_ENABLED")).secretValue !== "false" && true;
export const getLoopsApiKey = async () => (await client.getSecret("LOOPS_API_KEY")).secretValue;
export const getSmtpConfigured = async () => (await client.getSecret("SMTP_HOST")).secretValue == "" || (await client.getSecret("SMTP_HOST")).secretValue == undefined ? false : true
export const getHttpsEnabled = async () => {
if ((await getNodeEnv()) != "production") {
// no https for anything other than prod
return false
}
if (infisical.get('HTTPS_ENABLED') == undefined || infisical.get('HTTPS_ENABLED') == "") {
if ((await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || (await client.getSecret("HTTPS_ENABLED")).secretValue == "") {
// default when no value present
return true
}
return infisical.get('HTTPS_ENABLED') === 'true' && true
return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true
}

View File

@@ -1,10 +1,24 @@
import axios from 'axios';
import axiosRetry from 'axios-retry';
import axios from "axios";
import axiosRetry from "axios-retry";
import {
getLicenseKeyAuthToken,
getLicenseServerKeyAuthToken,
setLicenseKeyAuthToken,
setLicenseServerKeyAuthToken,
} from "./storage";
import {
getLicenseKey,
getLicenseServerKey,
getLicenseServerUrl,
} from "./index";
const axiosInstance = axios.create();
// should have JWT to interact with the license server
export const licenseServerKeyRequest = axios.create();
export const licenseKeyRequest = axios.create();
export const standardRequest = axios.create();
// add retry functionality to the axios instance
axiosRetry(axiosInstance, {
axiosRetry(standardRequest, {
retries: 3,
retryDelay: axiosRetry.exponentialDelay, // exponential back-off delay between retries
retryCondition: (error) => {
@@ -13,4 +27,98 @@ axiosRetry(axiosInstance, {
},
});
export default axiosInstance;
export const refreshLicenseServerKeyToken = async () => {
const licenseServerKey = await getLicenseServerKey();
const licenseServerUrl = await getLicenseServerUrl();
const { data: { token } } = await standardRequest.post(
`${licenseServerUrl}/api/auth/v1/license-server-login`, {},
{
headers: {
"X-API-KEY": licenseServerKey,
},
}
);
setLicenseServerKeyAuthToken(token);
return token;
}
export const refreshLicenseKeyToken = async () => {
const licenseKey = await getLicenseKey();
const licenseServerUrl = await getLicenseServerUrl();
const { data: { token } } = await standardRequest.post(
`${licenseServerUrl}/api/auth/v1/license-login`, {},
{
headers: {
"X-API-KEY": licenseKey,
},
}
);
setLicenseKeyAuthToken(token);
return token;
}
licenseServerKeyRequest.interceptors.request.use((config) => {
const token = getLicenseServerKeyAuthToken();
if (token && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (err) => {
return Promise.reject(err);
});
licenseServerKeyRequest.interceptors.response.use((response) => {
return response
}, async function (err) {
const originalRequest = err.config;
if (err.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
// refresh
const token = await refreshLicenseServerKeyToken();
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
return licenseServerKeyRequest(originalRequest);
}
return Promise.reject(err);
});
licenseKeyRequest.interceptors.request.use((config) => {
const token = getLicenseKeyAuthToken();
if (token && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (err) => {
return Promise.reject(err);
});
licenseKeyRequest.interceptors.response.use((response) => {
return response
}, async function (err) {
const originalRequest = err.config;
if (err.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
// refresh
const token = await refreshLicenseKeyToken();
axios.defaults.headers.common["Authorization"] = "Bearer " + token;
return licenseKeyRequest(originalRequest);
}
return Promise.reject(err);
});

View File

@@ -0,0 +1,30 @@
const MemoryLicenseServerKeyTokenStorage = () => {
let authToken: string;
return {
setToken: (token: string) => {
authToken = token;
},
getToken: () => authToken,
};
};
const MemoryLicenseKeyTokenStorage = () => {
let authToken: string;
return {
setToken: (token: string) => {
authToken = token;
},
getToken: () => authToken,
};
};
const licenseServerTokenStorage = MemoryLicenseServerKeyTokenStorage();
const licenseTokenStorage = MemoryLicenseKeyTokenStorage();
export const getLicenseServerKeyAuthToken = licenseServerTokenStorage.getToken;
export const setLicenseServerKeyAuthToken = licenseServerTokenStorage.setToken;
export const getLicenseKeyAuthToken = licenseTokenStorage.getToken;
export const setLicenseKeyAuthToken = licenseTokenStorage.setToken;

View File

@@ -1,29 +1,39 @@
import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import * as bigintConversion from 'bigint-conversion';
import { Request, Response } from "express";
import fs from "fs";
import path from "path";
import jwt from "jsonwebtoken";
import * as bigintConversion from "bigint-conversion";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require('jsrp');
import { User, LoginSRPDetail } from '../../models';
import { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth';
import { checkUserDevice } from '../../helpers/user';
const jsrp = require("jsrp");
import {
LoginSRPDetail,
TokenVersion,
User,
} from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import {
ACTION_LOGIN,
ACTION_LOGOUT
} from '../../variables';
import { BadRequestError } from '../../utils/errors';
import { EELogService } from '../../ee/services';
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
ACTION_LOGOUT,
AUTH_MODE_JWT,
} from "../../variables";
import {
getJwtRefreshSecret,
BadRequestError,
UnauthorizedRequestError,
} from "../../utils/errors";
import { EELogService } from "../../ee/services";
import { getChannelFromUserAgent } from "../../utils/posthog";
import {
getHttpsEnabled,
getJwtAuthLifetime,
getJwtAuthSecret,
getHttpsEnabled
} from '../../config';
getJwtRefreshSecret,
} from "../../config";
declare module 'jsonwebtoken' {
declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
refreshVersion?: number;
}
}
@@ -34,23 +44,22 @@ declare module 'jsonwebtoken' {
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
try {
const {
email,
clientPublicKey
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier');
email,
}).select("+salt +verifier");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
verifier: user.verifier,
},
async () => {
// generate server-side public key
@@ -64,17 +73,10 @@ export const login1 = async (req: Request, res: Response) => {
return res.status(200).send({
serverPublicKey,
salt: user.salt
salt: user.salt,
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to start authentication process'
});
}
};
/**
@@ -85,13 +87,12 @@ export const login1 = async (req: Request, res: Response) => {
* @returns
*/
export const login2 = async (req: Request, res: Response) => {
try {
const { email, clientProof } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier +publicKey +encryptedPrivateKey +iv +tag');
email,
}).select("+salt +verifier +publicKey +encryptedPrivateKey +iv +tag");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
@@ -104,7 +105,7 @@ export const login2 = async (req: Request, res: Response) => {
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt
b: loginSRPDetailFromDB.serverBInt,
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
@@ -115,30 +116,34 @@ export const login2 = async (req: Request, res: Response) => {
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
const tokens = await issueAuthTokens({ userId: user._id.toString() });
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: getHttpsEnabled()
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
userId: user._id,
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP,
});
// return (access) token in response
@@ -147,22 +152,15 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
tag: user.tag,
});
}
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
message: "Failed to authenticate. Try again?",
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
};
/**
@@ -172,44 +170,59 @@ export const login2 = async (req: Request, res: Response) => {
* @returns
*/
export const logout = async (req: Request, res: Response) => {
try {
await clearTokens({
userId: req.user._id.toString()
});
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
await clearTokens(req.authData.tokenVersionId)
}
// clear httpOnly cookie
res.cookie('jid', '', {
res.cookie("jid", "", {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: getHttpsEnabled() as boolean
path: "/",
sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean,
});
const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT,
userId: req.user._id
userId: req.user._id,
});
logoutAction && await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to logout'
});
}
return res.status(200).send({
message: 'Successfully logged out.'
message: "Successfully logged out.",
});
};
export const getCommonPasswords = async (req: Request, res: Response) => {
const commonPasswords = fs.readFileSync(
path.resolve(__dirname, "../../data/" + "common_passwords.txt"),
"utf8"
).split("\n");
return res.status(200).send(commonPasswords);
}
export const revokeAllSessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({
user: req.user._id,
}, {
$inc: {
refreshVersion: 1,
accessVersion: 1,
},
});
return res.status(200).send({
message: "Successfully revoked all sessions.",
});
}
/**
* Return user is authenticated
* @param req
@@ -218,52 +231,60 @@ export const logout = async (req: Request, res: Response) => {
*/
export const checkAuth = async (req: Request, res: Response) => {
return res.status(200).send({
message: 'Authenticated'
message: "Authenticated",
});
}
/**
* Return new token by redeeming refresh token
* Return new JWT access token by first validating the refresh token
* @param req
* @param res
* @returns
*/
export const getNewToken = async (req: Request, res: Response) => {
try {
const refreshToken = req.cookies.jid;
if (!refreshToken) {
throw new Error('Failed to find token in request cookies');
}
if (!refreshToken) throw BadRequestError({
message: "Failed to find refresh token in request cookies"
});
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, getJwtRefreshSecret())
jwt.verify(refreshToken, await getJwtRefreshSecret())
);
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey');
_id: decodedToken.userId,
}).select("+publicKey +refreshVersion +accessVersion");
if (!user) throw new Error('Failed to authenticate unfound user');
if (!user) throw new Error("Failed to authenticate unfound user");
if (!user?.publicKey)
throw new Error('Failed to authenticate not fully set up account');
throw new Error("Failed to authenticate not fully set up account");
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
if (!tokenVersion) throw UnauthorizedRequestError({
message: "Failed to validate refresh token",
});
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
message: "Failed to validate refresh token",
});
const token = createToken({
payload: {
userId: decodedToken.userId
userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion,
},
expiresIn: getJwtAuthLifetime(),
secret: getJwtAuthSecret()
expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret(),
});
return res.status(200).send({
token
token,
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Invalid request'
});
}
};
export const handleAuthProviderCallback = (req: Request, res: Response) => {
res.redirect(`/login/provider/success?token=${encodeURIComponent(req.providerAuthToken)}`);
}

View File

@@ -1,7 +1,7 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Bot, BotKey } from '../../models';
import { createBot } from '../../helpers/bot';
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Bot, BotKey } from "../../models";
import { createBot } from "../../helpers/bot";
interface BotKey {
encryptedKey: string;
@@ -16,32 +16,23 @@ interface BotKey {
* @returns
*/
export const getBotByWorkspaceId = async (req: Request, res: Response) => {
let bot;
try {
const { workspaceId } = req.params;
bot = await Bot.findOne({
workspace: workspaceId
let bot = await Bot.findOne({
workspace: workspaceId,
});
if (!bot) {
// case: bot doesn't exist for workspace with id [workspaceId]
// -> create a new bot and return it
bot = await createBot({
name: 'Infisical Bot',
workspaceId
});
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get bot for workspace'
name: "Infisical Bot",
workspaceId: new Types.ObjectId(workspaceId),
});
}
return res.status(200).send({
bot
bot,
});
};
@@ -52,56 +43,46 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
* @returns
*/
export const setBotActiveState = async (req: Request, res: Response) => {
let bot;
try {
const { isActive, botKey }: { isActive: boolean, botKey: BotKey } = req.body;
if (isActive) {
// bot state set to active -> share workspace key with bot
if (!botKey?.encryptedKey || !botKey?.nonce) {
return res.status(400).send({
message: 'Failed to set bot state to active - missing bot key'
message: "Failed to set bot state to active - missing bot key",
});
}
await BotKey.findOneAndUpdate({
workspace: req.bot.workspace
workspace: req.bot.workspace,
}, {
encryptedKey: botKey.encryptedKey,
nonce: botKey.nonce,
sender: req.user._id,
bot: req.bot._id,
workspace: req.bot.workspace
workspace: req.bot.workspace,
}, {
upsert: true,
new: true
new: true,
});
} else {
// case: bot state set to inactive -> delete bot's workspace key
await BotKey.deleteOne({
bot: req.bot._id
bot: req.bot._id,
});
}
bot = await Bot.findOneAndUpdate({
_id: req.bot._id
const bot = await Bot.findOneAndUpdate({
_id: req.bot._id,
}, {
isActive
isActive,
}, {
new: true
new: true,
});
if (!bot) throw new Error('Failed to update bot active state');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update bot active state'
});
}
if (!bot) throw new Error("Failed to update bot active state");
return res.status(200).send({
bot
bot,
});
};

View File

@@ -1,19 +1,21 @@
import * as authController from './authController';
import * as botController from './botController';
import * as integrationAuthController from './integrationAuthController';
import * as integrationController from './integrationController';
import * as keyController from './keyController';
import * as membershipController from './membershipController';
import * as membershipOrgController from './membershipOrgController';
import * as organizationController from './organizationController';
import * as passwordController from './passwordController';
import * as secretController from './secretController';
import * as serviceTokenController from './serviceTokenController';
import * as signupController from './signupController';
import * as stripeController from './stripeController';
import * as userActionController from './userActionController';
import * as userController from './userController';
import * as workspaceController from './workspaceController';
import * as authController from "./authController";
import * as botController from "./botController";
import * as integrationAuthController from "./integrationAuthController";
import * as integrationController from "./integrationController";
import * as keyController from "./keyController";
import * as membershipController from "./membershipController";
import * as membershipOrgController from "./membershipOrgController";
import * as organizationController from "./organizationController";
import * as passwordController from "./passwordController";
import * as secretController from "./secretController";
import * as serviceTokenController from "./serviceTokenController";
import * as signupController from "./signupController";
import * as userActionController from "./userActionController";
import * as userController from "./userController";
import * as workspaceController from "./workspaceController";
import * as secretScanningController from "./secretScanningController";
import * as webhookController from "./webhookController";
import * as secretImportController from "./secretImportController";
export {
authController,
@@ -28,8 +30,10 @@ export {
secretController,
serviceTokenController,
signupController,
stripeController,
userActionController,
userController,
workspaceController
workspaceController,
secretScanningController,
webhookController,
secretImportController
};

View File

@@ -1,53 +1,41 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
IntegrationAuth,
Bot
} from '../../models';
import { INTEGRATION_SET, getIntegrationOptions as getIntegrationOptionsFunc } from '../../variables';
import { IntegrationService } from '../../services';
import {
getApps,
getTeams,
revokeAccess
} from '../../integrations';
import { Request, Response } from "express";
import { Types } from "mongoose";
import { standardRequest } from "../../config/request";
import { getApps, getTeams, revokeAccess } from "../../integrations";
import { Bot, IntegrationAuth } from "../../models";
import { IntegrationService } from "../../services";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
INTEGRATION_BITBUCKET_API_URL,
INTEGRATION_RAILWAY_API_URL,
INTEGRATION_SET,
INTEGRATION_VERCEL_API_URL,
INTEGRATION_RAILWAY_API_URL
} from '../../variables';
import request from '../../config/request';
getIntegrationOptions as getIntegrationOptionsFunc
} from "../../variables";
/***
* Return integration authorization with id [integrationAuthId]
*/
export const getIntegrationAuth = async (req: Request, res: Response) => {
let integrationAuth;
try {
const { integrationAuthId } = req.params;
integrationAuth = await IntegrationAuth.findById(integrationAuthId);
const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
if (!integrationAuth) return res.status(400).send({
message: 'Failed to find integration authorization'
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
if (!integrationAuth)
return res.status(400).send({
message: 'Failed to get integration authorization'
message: "Failed to find integration authorization"
});
}
return res.status(200).send({
integrationAuth
});
}
};
export const getIntegrationOptions = async (req: Request, res: Response) => {
const INTEGRATION_OPTIONS = getIntegrationOptionsFunc();
const INTEGRATION_OPTIONS = await getIntegrationOptionsFunc();
return res.status(200).send({
integrationOptions: INTEGRATION_OPTIONS,
integrationOptions: INTEGRATION_OPTIONS
});
};
@@ -57,37 +45,25 @@ export const getIntegrationOptions = async (req: Request, res: Response) => {
* @param res
* @returns
*/
export const oAuthExchange = async (
req: Request,
res: Response
) => {
try {
export const oAuthExchange = async (req: Request, res: Response) => {
const { workspaceId, code, integration } = req.body;
if (!INTEGRATION_SET.has(integration))
throw new Error('Failed to validate integration');
if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
const environments = req.membership.workspace?.environments || [];
if(environments.length === 0){
throw new Error("Failed to get environments")
if (environments.length === 0) {
throw new Error("Failed to get environments");
}
const integrationAuth = await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
code,
environment: environments[0].slug,
environment: environments[0].slug
});
return res.status(200).send({
integrationAuth
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get OAuth2 code-token exchange'
});
}
};
/**
@@ -96,24 +72,24 @@ export const oAuthExchange = async (
* @param req
* @param res
*/
export const saveIntegrationAccessToken = async (
req: Request,
res: Response
) => {
export const saveIntegrationAccessToken = async (req: Request, res: Response) => {
// TODO: refactor
// TODO: check if access token is valid for each integration
let integrationAuth;
try {
const {
workspaceId,
accessId,
accessToken,
url,
namespace,
integration
}: {
workspaceId: string;
accessId: string | null;
accessToken: string;
url: string;
namespace: string;
integration: string;
} = req.body;
@@ -122,18 +98,26 @@ export const saveIntegrationAccessToken = async (
isActive: true
});
if (!bot) throw new Error('Bot must be enabled to save integration access token');
if (!bot) throw new Error("Bot must be enabled to save integration access token");
integrationAuth = await IntegrationAuth.findOneAndUpdate({
integrationAuth = await IntegrationAuth.findOneAndUpdate(
{
workspace: new Types.ObjectId(workspaceId),
integration
}, {
},
{
workspace: new Types.ObjectId(workspaceId),
integration
}, {
integration,
url,
namespace,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
},
{
new: true,
upsert: true
});
}
);
// encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
@@ -143,19 +127,12 @@ export const saveIntegrationAccessToken = async (
accessExpiresAt: undefined
});
if (!integrationAuth) throw new Error('Failed to save integration access token');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to save access token for integration'
});
}
if (!integrationAuth) throw new Error("Failed to save integration access token");
return res.status(200).send({
integrationAuth
});
}
};
/**
* Return list of applications allowed for integration with integration authorization id [integrationAuthId]
@@ -164,22 +141,16 @@ export const saveIntegrationAccessToken = async (
* @returns
*/
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
let apps;
try {
const teamId = req.query.teamId as string;
const workspaceSlug = req.query.workspaceSlug as string;
apps = await getApps({
const apps = await getApps({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
...teamId && { teamId }
accessId: req.accessId,
...(teamId && { teamId }),
...(workspaceSlug && { workspaceSlug })
});
} 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
@@ -201,7 +172,7 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
return res.status(200).send({
teams
});
}
};
/**
* Return list of available Vercel (preview) branches for Vercel project with
@@ -210,7 +181,6 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
* @param res
*/
export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params;
const appId = req.query.appId as string;
interface VercelBranch {
@@ -221,21 +191,23 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
const params = new URLSearchParams({
projectId: appId,
...(req.integrationAuth.teamId ? {
...(req.integrationAuth.teamId
? {
teamId: req.integrationAuth.teamId
} : {})
}
: {})
});
let branches: string[] = [];
if (appId && appId !== '') {
const { data }: { data: VercelBranch[] } = await request.get(
if (appId && appId !== "") {
const { data }: { data: VercelBranch[] } = await standardRequest.get(
`${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`,
{
params,
headers: {
Authorization: `Bearer ${req.accessToken}`,
'Accept-Encoding': 'application/json'
"Accept-Encoding": "application/json"
}
}
);
@@ -246,7 +218,7 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
return res.status(200).send({
branches
});
}
};
/**
* Return list of Railway environments for Railway project with
@@ -255,7 +227,6 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
* @param res
*/
export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params;
const appId = req.query.appId as string;
interface RailwayEnvironment {
@@ -263,7 +234,7 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
id: string;
name: string;
isEphemeral: boolean;
}
};
}
interface Environment {
@@ -273,7 +244,7 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
let environments: Environment[] = [];
if (appId && appId !== '') {
if (appId && appId !== "") {
const query = `
query GetEnvironments($projectId: String!, $after: String, $before: String, $first: Int, $isEphemeral: Boolean, $last: Int) {
environments(projectId: $projectId, after: $after, before: $before, first: $first, isEphemeral: $isEphemeral, last: $last) {
@@ -290,30 +261,40 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
const variables = {
projectId: appId
}
};
const { data: { data: { environments: { edges } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
const {
data: {
data: {
environments: { edges }
}
}
} = await standardRequest.post(
INTEGRATION_RAILWAY_API_URL,
{
query,
variables,
}, {
headers: {
'Authorization': `Bearer ${req.accessToken}`,
'Content-Type': 'application/json',
variables
},
});
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Content-Type": "application/json"
}
}
);
environments = edges.map((e: RailwayEnvironment) => {
return ({
return {
name: e.node.name,
environmentId: e.node.id
});
};
});
}
return res.status(200).send({
environments
});
}
};
/**
* Return list of Railway services for Railway project with id
@@ -322,14 +303,13 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
* @param res
*/
export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => {
const { integrationAuthId } = req.params;
const appId = req.query.appId as string;
interface RailwayService {
node: {
id: string;
name: string;
}
};
}
interface Service {
@@ -367,20 +347,32 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
}
`;
if (appId && appId !== '') {
if (appId && appId !== "") {
const variables = {
id: appId
}
};
const { data: { data: { project: { services: { edges } } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, {
const {
data: {
data: {
project: {
services: { edges }
}
}
}
} = await standardRequest.post(
INTEGRATION_RAILWAY_API_URL,
{
query,
variables
}, {
headers: {
'Authorization': `Bearer ${req.accessToken}`,
'Content-Type': 'application/json',
},
});
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Content-Type": "application/json"
}
}
);
services = edges.map((e: RailwayService) => ({
name: e.node.name,
@@ -391,7 +383,67 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
return res.status(200).send({
services
});
}
};
/**
* Return list of workspaces allowed for Bitbucket integration
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: Response) => {
interface WorkspaceResponse {
size: number;
page: number;
pageLen: number;
next: string;
previous: string;
values: Array<Workspace>;
}
interface Workspace {
type: string;
uuid: string;
name: string;
slug: string;
is_private: boolean;
created_on: string;
updated_on: string;
}
const workspaces: Workspace[] = [];
let hasNextPage = true;
let workspaceUrl = `${INTEGRATION_BITBUCKET_API_URL}/2.0/workspaces`
while (hasNextPage) {
const { data }: { data: WorkspaceResponse } = await standardRequest.get(
workspaceUrl,
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Accept-Encoding": "application/json"
}
}
);
if (data?.values.length > 0) {
data.values.forEach((workspace) => {
workspaces.push(workspace)
})
}
if (data.next) {
workspaceUrl = data.next
} else {
hasNextPage = false
}
}
return res.status(200).send({
workspaces
});
};
/**
* Delete integration authorization with id [integrationAuthId]
@@ -400,21 +452,12 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
* @returns
*/
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
let integrationAuth;
try {
integrationAuth = await revokeAccess({
const integrationAuth = await revokeAccess({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
accessToken: req.accessToken
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to delete integration authorization",
});
}
return res.status(200).send({
integrationAuth,
integrationAuth
});
};

View File

@@ -1,11 +1,11 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Integration
} from '../../models';
import { EventService } from '../../services';
import { eventPushSecrets } from '../../events';
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Integration } from "../../models";
import { EventService } from "../../services";
import { eventStartIntegration } from "../../events";
import Folder from "../../models/folder";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
/**
* Create/initialize an (empty) integration for integration authorization
@@ -14,9 +14,6 @@ import { eventPushSecrets } from '../../events';
* @returns
*/
export const createIntegration = async (req: Request, res: Response) => {
let integration;
try {
const {
integrationAuthId,
app,
@@ -29,13 +26,28 @@ export const createIntegration = async (req: Request, res: Response) => {
targetServiceId,
owner,
path,
region
region,
secretPath
} = req.body;
const folders = await Folder.findOne({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment
});
if (folders) {
const folder = getFolderByPath(folders.nodes, secretPath);
if (!folder) {
throw BadRequestError({
message: "Path for service token does not exist"
});
}
}
// TODO: validate [sourceEnvironment] and [targetEnvironment]
// initialize new integration after saving integration access token
integration = await new Integration({
const integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment,
isActive,
@@ -48,6 +60,7 @@ export const createIntegration = async (req: Request, res: Response) => {
owner,
path,
region,
secretPath,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();
@@ -55,22 +68,15 @@ export const createIntegration = async (req: Request, res: Response) => {
if (integration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: integration.workspace.toString()
event: eventStartIntegration({
workspaceId: integration.workspace,
environment: sourceEnvironment
})
});
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create integration'
});
}
return res.status(200).send({
integration,
integration
});
};
@@ -81,12 +87,9 @@ export const createIntegration = async (req: Request, res: Response) => {
* @returns
*/
export const updateIntegration = async (req: Request, res: Response) => {
let integration;
// TODO: add integration-specific validation to ensure that each
// integration has the correct fields populated in [Integration]
try {
const {
environment,
isActive,
@@ -94,11 +97,26 @@ export const updateIntegration = async (req: Request, res: Response) => {
appId,
targetEnvironment,
owner, // github-specific integration param
secretPath
} = req.body;
integration = await Integration.findOneAndUpdate(
const folders = await Folder.findOne({
workspace: req.integration.workspace,
environment
});
if (folders) {
const folder = getFolderByPath(folders.nodes, secretPath);
if (!folder) {
throw BadRequestError({
message: "Path for service token does not exist"
});
}
}
const integration = await Integration.findOneAndUpdate(
{
_id: req.integration._id,
_id: req.integration._id
},
{
environment,
@@ -107,30 +125,25 @@ export const updateIntegration = async (req: Request, res: Response) => {
appId,
targetEnvironment,
owner,
secretPath
},
{
new: true,
new: true
}
);
if (integration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: integration.workspace.toString(),
}),
});
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to update integration",
event: eventStartIntegration({
workspaceId: integration.workspace,
environment
})
});
}
return res.status(200).send({
integration,
integration
});
};
@@ -142,24 +155,15 @@ export const updateIntegration = async (req: Request, res: Response) => {
* @returns
*/
export const deleteIntegration = async (req: Request, res: Response) => {
let integration;
try {
const { integrationId } = req.params;
integration = await Integration.findOneAndDelete({
_id: integrationId,
const integration = await Integration.findOneAndDelete({
_id: integrationId
});
if (!integration) throw new Error("Failed to find integration");
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to delete integration",
});
}
return res.status(200).send({
integration,
integration
});
};

View File

@@ -1,7 +1,6 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Key } from '../../models';
import { findMembership } from '../../helpers/membership';
import { Request, Response } from "express";
import { Key } from "../../models";
import { findMembership } from "../../helpers/membership";
/**
* Add (encrypted) copy of workspace key for workspace with id [workspaceId] for user with
@@ -11,18 +10,17 @@ import { findMembership } from '../../helpers/membership';
* @returns
*/
export const uploadKey = async (req: Request, res: Response) => {
try {
const { workspaceId } = req.params;
const { key } = req.body;
// validate membership of receiver
const receiverMembership = await findMembership({
user: key.userId,
workspace: workspaceId
workspace: workspaceId,
});
if (!receiverMembership) {
throw new Error('Failed receiver membership validation for workspace');
throw new Error("Failed receiver membership validation for workspace");
}
await new Key({
@@ -30,18 +28,11 @@ export const uploadKey = async (req: Request, res: Response) => {
nonce: key.nonce,
sender: req.user._id,
receiver: key.userId,
workspace: workspaceId
workspace: workspaceId,
}).save();
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to upload key to workspace'
});
}
return res.status(200).send({
message: 'Successfully uploaded key to workspace'
message: "Successfully uploaded key to workspace",
});
};
@@ -52,30 +43,21 @@ export const uploadKey = async (req: Request, res: Response) => {
* @returns
*/
export const getLatestKey = async (req: Request, res: Response) => {
let latestKey;
try {
const { workspaceId } = req.params;
// get latest key
latestKey = await Key.find({
const latestKey = await Key.find({
workspace: workspaceId,
receiver: req.user._id
receiver: req.user._id,
})
.sort({ createdAt: -1 })
.limit(1)
.populate('sender', '+publicKey');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get latest key'
});
}
.populate("sender", "+publicKey");
const resObj: any = {};
if (latestKey.length > 0) {
resObj['latestKey'] = latestKey[0];
resObj["latestKey"] = latestKey[0];
}
return res.status(200).send(resObj);

View File

@@ -1,13 +1,9 @@
import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import { Membership, MembershipOrg, User, Key } from '../../models';
import {
findMembership,
deleteMembership as deleteMember
} from '../../helpers/membership';
import { sendMail } from '../../helpers/nodemailer';
import { ADMIN, MEMBER, ACCEPTED } from '../../variables';
import { getSiteURL } from '../../config';
import { Request, Response } from "express";
import { Key, Membership, MembershipOrg, User } from "../../models";
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
import { sendMail } from "../../helpers/nodemailer";
import { ACCEPTED, ADMIN, MEMBER } from "../../variables";
import { getSiteURL } from "../../config";
/**
* Check that user is a member of workspace with id [workspaceId]
@@ -16,9 +12,7 @@ import { getSiteURL } from '../../config';
* @returns
*/
export const validateMembership = async (req: Request, res: Response) => {
try {
const { workspaceId } = req.params;
// validate membership
const membership = await findMembership({
user: req.user._id,
@@ -26,18 +20,11 @@ export const validateMembership = async (req: Request, res: Response) => {
});
if (!membership) {
throw new Error('Failed to validate membership');
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed workspace connection check'
});
throw new Error("Failed to validate membership");
}
return res.status(200).send({
message: 'Workspace membership confirmed'
message: "Workspace membership confirmed"
});
};
@@ -48,19 +35,15 @@ export const validateMembership = async (req: Request, res: Response) => {
* @returns
*/
export const deleteMembership = async (req: Request, res: Response) => {
let deletedMembership;
try {
const { membershipId } = req.params;
// check if membership to delete exists
const membershipToDelete = await Membership.findOne({
_id: membershipId
}).populate('user');
}).populate("user");
if (!membershipToDelete) {
throw new Error(
"Failed to delete workspace membership that doesn't exist"
);
throw new Error("Failed to delete workspace membership that doesn't exist");
}
// check if user is a member and admin of the workspace
@@ -71,25 +54,18 @@ export const deleteMembership = async (req: Request, res: Response) => {
});
if (!membership) {
throw new Error('Failed to validate workspace membership');
throw new Error("Failed to validate workspace membership");
}
if (membership.role !== ADMIN) {
// user is not an admin member of the workspace
throw new Error('Insufficient role for deleting workspace membership');
throw new Error("Insufficient role for deleting workspace membership");
}
// delete workspace membership
deletedMembership = await deleteMember({
const deletedMembership = await deleteMember({
membershipId: membershipToDelete._id.toString()
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete membership'
});
}
return res.status(200).send({
deletedMembership
@@ -103,22 +79,20 @@ export const deleteMembership = async (req: Request, res: Response) => {
* @returns
*/
export const changeMembershipRole = async (req: Request, res: Response) => {
let membershipToChangeRole;
try {
const { membershipId } = req.params;
const { role } = req.body;
if (![ADMIN, MEMBER].includes(role)) {
throw new Error('Failed to validate role');
throw new Error("Failed to validate role");
}
// validate target membership
membershipToChangeRole = await findMembership({
const membershipToChangeRole = await findMembership({
_id: membershipId
});
if (!membershipToChangeRole) {
throw new Error('Failed to find membership to change role');
throw new Error("Failed to find membership to change role");
}
// check if user is a member and admin of target membership's
@@ -129,23 +103,16 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
});
if (!membership) {
throw new Error('Failed to validate membership');
throw new Error("Failed to validate membership");
}
if (membership.role !== ADMIN) {
// user is not an admin member of the workspace
throw new Error('Insufficient role for changing member roles');
throw new Error("Insufficient role for changing member roles");
}
membershipToChangeRole.role = role;
await membershipToChangeRole.save();
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to change membership role'
});
}
return res.status(200).send({
membership: membershipToChangeRole
@@ -159,17 +126,14 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
* @returns
*/
export const inviteUserToWorkspace = async (req: Request, res: Response) => {
let invitee, latestKey;
try {
const { workspaceId } = req.params;
const { email }: { email: string } = req.body;
invitee = await User.findOne({
const invitee = await User.findOne({
email
}).select('+publicKey');
}).select("+publicKey");
if (!invitee || !invitee?.publicKey)
throw new Error('Failed to validate invitee');
if (!invitee || !invitee?.publicKey) throw new Error("Failed to validate invitee");
// validate invitee's workspace membership - ensure member isn't
// already a member of the workspace
@@ -178,8 +142,7 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
workspace: workspaceId
});
if (inviteeMembership)
throw new Error('Failed to add existing member of workspace');
if (inviteeMembership) throw new Error("Failed to add existing member of workspace");
// validate invitee's organization membership - ensure that only
// (accepted) organization members can be added to the workspace
@@ -189,42 +152,34 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
status: ACCEPTED
});
if (!membershipOrg)
throw new Error("Failed to validate invitee's organization membership");
if (!membershipOrg) throw new Error("Failed to validate invitee's organization membership");
// get latest key
latestKey = await Key.findOne({
const latestKey = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate('sender', '+publicKey');
.populate("sender", "+publicKey");
// create new workspace membership
const m = await new Membership({
await new Membership({
user: invitee._id,
workspace: workspaceId,
role: MEMBER
}).save();
await sendMail({
template: 'workspaceInvitation.handlebars',
subjectLine: 'Infisical workspace invitation',
template: "workspaceInvitation.handlebars",
subjectLine: "Infisical workspace invitation",
recipients: [invitee.email],
substitutions: {
inviterFirstName: req.user.firstName,
inviterEmail: req.user.email,
workspaceName: req.membership.workspace.name,
callback_url: getSiteURL() + '/login'
callback_url: (await getSiteURL()) + "/login"
}
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to invite user to workspace'
});
}
return res.status(200).send({
invitee,

View File

@@ -1,13 +1,28 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { MembershipOrg, Organization, User } from '../../models';
import { deleteMembershipOrg as deleteMemberFromOrg } from '../../helpers/membershipOrg';
import { createToken } from '../../helpers/auth';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
import { sendMail } from '../../helpers/nodemailer';
import { TokenService } from '../../services';
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables';
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
import { Types } from "mongoose";
import { Request, Response } from "express";
import { MembershipOrg, Organization, User } from "../../models";
import { SSOConfig } from "../../ee/models";
import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
import { createToken } from "../../helpers/auth";
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELicenseService } from "../../ee/services";
import {
ACCEPTED,
ADMIN,
INVITED,
MEMBER,
OWNER,
TOKEN_EMAIL_ORG_INVITATION
} from "../../variables";
import {
getJwtSignupLifetime,
getJwtSignupSecret,
getSiteURL,
getSmtpConfigured
} from "../../config";
import { validateUserEmail } from "../../validation";
/**
* Delete organization membership with id [membershipOrgId] from organization
@@ -15,20 +30,16 @@ import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured
* @param res
* @returns
*/
export const deleteMembershipOrg = async (req: Request, res: Response) => {
let membershipOrgToDelete;
try {
export const deleteMembershipOrg = async (req: Request, _res: Response) => {
const { membershipOrgId } = req.params;
// check if organization membership to delete exists
membershipOrgToDelete = await MembershipOrg.findOne({
const membershipOrgToDelete = await MembershipOrg.findOne({
_id: membershipOrgId
}).populate('user');
}).populate("user");
if (!membershipOrgToDelete) {
throw new Error(
"Failed to delete organization membership that doesn't exist"
);
throw new Error("Failed to delete organization membership that doesn't exist");
}
// check if user is a member and admin of the organization
@@ -39,29 +50,22 @@ export const deleteMembershipOrg = async (req: Request, res: Response) => {
});
if (!membershipOrg) {
throw new Error('Failed to validate organization membership');
throw new Error("Failed to validate organization membership");
}
if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) {
// user is not an admin member of the organization
throw new Error('Insufficient role for deleting organization membership');
throw new Error("Insufficient role for deleting organization membership");
}
// delete organization membership
const deletedMembershipOrg = await deleteMemberFromOrg({
await deleteMemberFromOrg({
membershipOrgId: membershipOrgToDelete._id.toString()
});
await updateSubscriptionOrgQuantity({
organizationId: membershipOrg.organization.toString()
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete organization membership'
});
}
return membershipOrgToDelete;
};
@@ -77,14 +81,6 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
// [membershipOrgId]
let membershipToChangeRole;
// try {
// } catch (err) {
// Sentry.setUser({ email: req.user.email });
// Sentry.captureException(err);
// return res.status(400).send({
// message: 'Failed to change organization membership role'
// });
// }
return res.status(200).send({
membershipOrg: membershipToChangeRole
@@ -99,8 +95,7 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
* @returns
*/
export const inviteUserToOrganization = async (req: Request, res: Response) => {
let invitee, inviteeMembershipOrg, completeInviteLink;
try {
let inviteeMembershipOrg, completeInviteLink;
const { organizationId, inviteeEmail } = req.body;
const host = req.headers.host;
const siteUrl = `${req.protocol}://${host}`;
@@ -112,12 +107,38 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
});
if (!membershipOrg) {
throw new Error('Failed to validate organization membership');
throw new Error("Failed to validate organization membership");
}
invitee = await User.findOne({
const plan = await EELicenseService.getPlan(organizationId);
const ssoConfig = await SSOConfig.findOne({
organization: new Types.ObjectId(organizationId)
});
if (ssoConfig && ssoConfig.isActive) {
// case: SAML SSO is enabled for the organization
return res.status(400).send({
message:
"Failed to invite member due to SAML SSO configured for organization"
});
}
if (plan.memberLimit !== null) {
// case: limit imposed on number of members allowed
if (plan.membersUsed >= plan.memberLimit) {
// case: number of members used exceeds the number of members allowed
return res.status(400).send({
message:
"Failed to invite member due to member limit reached. Upgrade plan to invite more members."
});
}
}
const invitee = await User.findOne({
email: inviteeEmail
}).select('+publicKey');
}).select("+publicKey");
if (invitee) {
// case: invitee is an existing user
@@ -128,9 +149,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
});
if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) {
throw new Error(
'Failed to invite an existing member of the organization'
);
throw new Error("Failed to invite an existing member of the organization");
}
if (!inviteeMembershipOrg) {
@@ -139,7 +158,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
inviteEmail: inviteeEmail,
organization: organizationId,
role: MEMBER,
status: invitee?.publicKey ? ACCEPTED : INVITED
status: INVITED
}).save();
}
} else {
@@ -152,6 +171,9 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
if (!inviteeMembershipOrg) {
// case: invitee has never been invited before
// validate that email is not disposable
validateUserEmail(inviteeEmail);
await new MembershipOrg({
inviteEmail: inviteeEmail,
organization: organizationId,
@@ -171,32 +193,28 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
});
await sendMail({
template: 'organizationInvitation.handlebars',
subjectLine: 'Infisical organization invitation',
template: "organizationInvitation.handlebars",
subjectLine: "Infisical organization invitation",
recipients: [inviteeEmail],
substitutions: {
inviterFirstName: req.user.firstName,
inviterEmail: req.user.email,
organizationName: organization.name,
email: inviteeEmail,
organizationId: organization._id.toString(),
token,
callback_url: getSiteURL() + '/signupinvite'
callback_url: (await getSiteURL()) + "/signupinvite"
}
});
if (!getSmtpConfigured()) {
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}`
if (!(await getSmtpConfigured())) {
completeInviteLink = `${
siteUrl + "/signupinvite"
}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}`;
}
}
await updateSubscriptionOrgQuantity({ organizationId });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to send organization invite'
});
}
return res.status(200).send({
message: `Sent an invite link to ${req.body.inviteeEmail}`,
@@ -212,19 +230,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
* @returns
*/
export const verifyUserToOrganization = async (req: Request, res: Response) => {
let user, token;
try {
const { email, code } = req.body;
let user;
const { email, organizationId, code } = req.body;
user = await User.findOne({ email }).select('+publicKey');
user = await User.findOne({ email }).select("+publicKey");
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
status: INVITED,
organization: new Types.ObjectId(organizationId)
});
if (!membershipOrg)
throw new Error('Failed to find any invitations for email');
if (!membershipOrg) throw new Error("Failed to find any invitations for email");
await TokenService.validateToken({
type: TOKEN_EMAIL_ORG_INVITATION,
@@ -239,9 +256,13 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
membershipOrg.status = ACCEPTED;
await membershipOrg.save();
await updateSubscriptionOrgQuantity({
organizationId
});
return res.status(200).send({
message: 'Successfully verified email',
user,
message: "Successfully verified email",
user
});
}
@@ -253,23 +274,16 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
}
// generate temporary signup token
token = createToken({
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: getJwtSignupLifetime(),
secret: getJwtSignupSecret()
expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret()
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed email magic link verification for organization invitation'
});
}
return res.status(200).send({
message: 'Successfully verified email',
message: "Successfully verified email",
user,
token
});

View File

@@ -1,37 +1,27 @@
import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import Stripe from 'stripe';
import { Request, Response } from "express";
import {
IncidentContactOrg,
Membership,
MembershipOrg,
Organization,
Workspace,
IncidentContactOrg
} from '../../models';
import { createOrganization as create } from '../../helpers/organization';
import { addMembershipsOrg } from '../../helpers/membershipOrg';
import { OWNER, ACCEPTED } from '../../variables';
import _ from 'lodash';
import { getStripeSecretKey, getSiteURL } from '../../config';
} from "../../models";
import { createOrganization as create } from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { ACCEPTED, OWNER } from "../../variables";
import { getLicenseServerUrl, getSiteURL } from "../../config";
import { licenseServerKeyRequest } from "../../config/request";
export const getOrganizations = async (req: Request, res: Response) => {
let organizations;
try {
organizations = (
const organizations = (
await MembershipOrg.find({
user: req.user._id
}).populate('organization')
user: req.user._id,
status: ACCEPTED,
}).populate("organization")
).map((m) => m.organization);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organizations'
});
}
return res.status(200).send({
organizations
organizations,
});
};
@@ -43,36 +33,27 @@ export const getOrganizations = async (req: Request, res: Response) => {
* @returns
*/
export const createOrganization = async (req: Request, res: Response) => {
let organization;
try {
const { organizationName } = req.body;
if (organizationName.length < 1) {
throw new Error('Organization names must be at least 1-character long');
throw new Error("Organization names must be at least 1-character long");
}
// create organization and add user as member
organization = await create({
const organization = await create({
email: req.user.email,
name: organizationName
name: organizationName,
});
await addMembershipsOrg({
userIds: [req.user._id.toString()],
organizationId: organization._id.toString(),
roles: [OWNER],
statuses: [ACCEPTED]
statuses: [ACCEPTED],
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create organization'
});
}
return res.status(200).send({
organization
organization,
});
};
@@ -83,19 +64,9 @@ export const createOrganization = async (req: Request, res: Response) => {
* @returns
*/
export const getOrganization = async (req: Request, res: Response) => {
let organization;
try {
organization = req.membershipOrg.organization;
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to find organization'
});
}
const organization = req.organization
return res.status(200).send({
organization
organization,
});
};
@@ -106,23 +77,14 @@ export const getOrganization = async (req: Request, res: Response) => {
* @returns
*/
export const getOrganizationMembers = async (req: Request, res: Response) => {
let users;
try {
const { organizationId } = req.params;
users = await MembershipOrg.find({
organization: organizationId
}).populate('user', '+publicKey');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization members'
});
}
const users = await MembershipOrg.find({
organization: organizationId,
}).populate("user", "+publicKey");
return res.status(200).send({
users
users,
});
};
@@ -136,38 +98,29 @@ export const getOrganizationWorkspaces = async (
req: Request,
res: Response
) => {
let workspaces;
try {
const { organizationId } = req.params;
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
organization: organizationId,
},
'_id'
"_id"
)
).map((w) => w._id.toString())
);
workspaces = (
const workspaces = (
await Membership.find({
user: req.user._id
}).populate('workspace')
user: req.user._id,
}).populate("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get my workspaces'
});
}
return res.status(200).send({
workspaces
workspaces,
});
};
@@ -178,33 +131,24 @@ export const getOrganizationWorkspaces = async (
* @returns
*/
export const changeOrganizationName = async (req: Request, res: Response) => {
let organization;
try {
const { organizationId } = req.params;
const { name } = req.body;
organization = await Organization.findOneAndUpdate(
const organization = await Organization.findOneAndUpdate(
{
_id: organizationId
_id: organizationId,
},
{
name
name,
},
{
new: true
new: true,
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to change organization name'
});
}
return res.status(200).send({
message: 'Successfully changed organization name',
organization
message: "Successfully changed organization name",
organization,
});
};
@@ -218,23 +162,14 @@ export const getOrganizationIncidentContacts = async (
req: Request,
res: Response
) => {
let incidentContactsOrg;
try {
const { organizationId } = req.params;
incidentContactsOrg = await IncidentContactOrg.find({
organization: organizationId
const incidentContactsOrg = await IncidentContactOrg.find({
organization: organizationId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization incident contacts'
});
}
return res.status(200).send({
incidentContactsOrg
incidentContactsOrg,
});
};
@@ -248,26 +183,17 @@ export const addOrganizationIncidentContact = async (
req: Request,
res: Response
) => {
let incidentContactOrg;
try {
const { organizationId } = req.params;
const { email } = req.body;
incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
{ email, organization: organizationId },
{ email, organization: organizationId },
{ upsert: true, new: true }
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to add incident contact for organization'
});
}
return res.status(200).send({
incidentContactOrg
incidentContactOrg,
});
};
@@ -281,31 +207,22 @@ export const deleteOrganizationIncidentContact = async (
req: Request,
res: Response
) => {
let incidentContactOrg;
try {
const { organizationId } = req.params;
const { email } = req.body;
incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
email,
organization: organizationId
organization: organizationId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete organization incident contact'
});
}
return res.status(200).send({
message: 'Successfully deleted organization incident contact',
incidentContactOrg
message: "Successfully deleted organization incident contact",
incidentContactOrg,
});
};
/**
* Redirect user to (stripe) billing portal or add card page depending on
* Redirect user to billing portal or add card page depending on
* if there is a card on file
* @param req
* @param res
@@ -315,41 +232,31 @@ export const createOrganizationPortalSession = async (
req: Request,
res: Response
) => {
let session;
try {
const stripe = new Stripe(getStripeSecretKey(), {
apiVersion: '2022-08-01'
});
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
);
// check if there is a payment method on file
const paymentMethods = await stripe.paymentMethods.list({
customer: req.membershipOrg.organization.customerId,
type: 'card'
});
if (paymentMethods.data.length < 1) {
// case: no payment method on file
session = await stripe.checkout.sessions.create({
customer: req.membershipOrg.organization.customerId,
mode: 'setup',
payment_method_types: ['card'],
success_url: getSiteURL() + '/dashboard',
cancel_url: getSiteURL() + '/dashboard'
});
} else {
session = await stripe.billingPortal.sessions.create({
customer: req.membershipOrg.organization.customerId,
return_url: getSiteURL() + '/dashboard'
});
if (pmtMethods.length < 1) {
// case: organization has no payment method on file
// -> redirect to add payment method portal
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
{
success_url: (await getSiteURL()) + "/dashboard",
cancel_url: (await getSiteURL()) + "/dashboard"
}
return res.status(200).send({ url: session.url });
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to redirect to organization billing portal'
});
);
return res.status(200).send({ url });
} else {
// case: organization has payment method on file
// -> redirect to billing portal
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/billing-portal`,
{
return_url: (await getSiteURL()) + "/dashboard"
}
);
return res.status(200).send({ url });
}
};
@@ -363,25 +270,8 @@ export const getOrganizationSubscriptions = async (
req: Request,
res: Response
) => {
let subscriptions;
try {
const stripe = new Stripe(getStripeSecretKey(), {
apiVersion: '2022-08-01'
});
subscriptions = await stripe.subscriptions.list({
customer: req.membershipOrg.organization.customerId
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization subscriptions'
});
}
return res.status(200).send({
subscriptions
subscriptions: []
});
};
@@ -401,16 +291,16 @@ export const getOrganizationMembersAndTheirWorkspaces = async (
const workspacesSet = (
await Workspace.find(
{
organization: organizationId
organization: organizationId,
},
'_id'
"_id"
)
).map((w) => w._id.toString());
const memberships = (
await Membership.find({
workspace: { $in: workspacesSet }
}).populate('workspace')
workspace: { $in: workspacesSet },
}).populate("workspace")
);
const userToWorkspaceIds: any = {};

View File

@@ -1,15 +1,18 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Request, Response } from "express";
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jsrp = require('jsrp');
import * as bigintConversion from 'bigint-conversion';
import { User, BackupPrivateKey, LoginSRPDetail } from '../../models';
import { createToken } from '../../helpers/auth';
import { sendMail } from '../../helpers/nodemailer';
import { TokenService } from '../../services';
import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables';
import { BadRequestError } from '../../utils/errors';
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../config';
const jsrp = require("jsrp");
import * as bigintConversion from "bigint-conversion";
import { BackupPrivateKey, LoginSRPDetail, User } from "../../models";
import { clearTokens, createToken, sendMail } from "../../helpers";
import { TokenService } from "../../services";
import { AUTH_MODE_JWT, TOKEN_EMAIL_PASSWORD_RESET } from "../../variables";
import { BadRequestError } from "../../utils/errors";
import {
getHttpsEnabled,
getJwtSignupLifetime,
getJwtSignupSecret,
getSiteURL
} from "../../config";
/**
* Password reset step 1: Send email verification link to email [email]
@@ -19,16 +22,14 @@ import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../conf
* @returns
*/
export const emailPasswordReset = async (req: Request, res: Response) => {
let email: string;
try {
email = req.body.email;
const email: string = req.body.email;
const user = await User.findOne({ email }).select('+publicKey');
const user = await User.findOne({ email }).select("+publicKey");
if (!user || !user?.publicKey) {
// case: user has already completed account
return res.status(403).send({
error: 'Failed to send email verification for password reset'
return res.status(200).send({
message: "If an account exists with this email, a password reset link has been sent"
});
}
@@ -38,27 +39,20 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
});
await sendMail({
template: 'passwordReset.handlebars',
subjectLine: 'Infisical password reset',
template: "passwordReset.handlebars",
subjectLine: "Infisical password reset",
recipients: [email],
substitutions: {
email,
token,
callback_url: getSiteURL() + '/password-reset'
callback_url: (await getSiteURL()) + "/password-reset"
}
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to send email for account recovery'
});
}
return res.status(200).send({
message: `Sent an email for account recovery to ${email}`
message: "If an account exists with this email, a password reset link has been sent"
});
}
};
/**
* Password reset step 2: Verify email verification link sent to email [email]
@@ -67,16 +61,14 @@ export const emailPasswordReset = async (req: Request, res: Response) => {
* @returns
*/
export const emailPasswordResetVerify = async (req: Request, res: Response) => {
let user, token;
try {
const { email, code } = req.body;
user = await User.findOne({ email }).select('+publicKey');
const user = await User.findOne({ email }).select("+publicKey");
if (!user || !user?.publicKey) {
// case: user doesn't exist with email [email] or
// hasn't even completed their account
return res.status(403).send({
error: 'Failed email verification for password reset'
error: "Failed email verification for password reset"
});
}
@@ -87,27 +79,20 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
});
// generate temporary password-reset token
token = createToken({
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: getJwtSignupLifetime(),
secret: getJwtSignupSecret()
expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret()
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed email verification for password reset'
});
}
return res.status(200).send({
message: 'Successfully verified email',
message: "Successfully verified email",
user,
token
});
}
};
/**
* Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
@@ -117,13 +102,13 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
*/
export const srp1 = async (req: Request, res: Response) => {
// return salt, serverPublicKey as part of first step of SRP protocol
try {
const { clientPublicKey } = req.body;
const user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
}).select("+salt +verifier");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
const server = new jsrp.server();
server.init(
@@ -135,11 +120,15 @@ export const srp1 = async (req: Request, res: Response) => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, {
await LoginSRPDetail.findOneAndReplace(
{ email: req.user.email },
{
email: req.user.email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false })
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({
serverPublicKey,
@@ -147,13 +136,6 @@ export const srp1 = async (req: Request, res: Response) => {
});
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to start change password process'
});
}
};
/**
@@ -165,7 +147,6 @@ export const srp1 = async (req: Request, res: Response) => {
* @returns
*/
export const changePassword = async (req: Request, res: Response) => {
try {
const {
clientProof,
protectedKey,
@@ -180,14 +161,18 @@ export const changePassword = async (req: Request, res: Response) => {
const user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
}).select("+salt +verifier");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email });
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
return BadRequestError(
Error(
"It looks like some details from the first login are not found. Please try login one again"
)
);
}
const server = new jsrp.server();
@@ -222,23 +207,33 @@ export const changePassword = async (req: Request, res: Response) => {
}
);
if (
req.authData.authMode === AUTH_MODE_JWT &&
req.authData.authPayload instanceof User &&
req.authData.tokenVersionId
) {
await clearTokens(req.authData.tokenVersionId);
}
// clear httpOnly cookie
res.cookie("jid", "", {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: (await getHttpsEnabled()) as boolean
});
return res.status(200).send({
message: 'Successfully changed password'
message: "Successfully changed password"
});
}
return res.status(400).send({
error: 'Failed to change password. Try again?'
error: "Failed to change password. Try again?"
});
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to change password. Try again?'
});
}
};
/**
@@ -252,19 +247,21 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
// requires verifying [clientProof] as part of second step of SRP protocol
// as initiated in /srp1
try {
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } =
req.body;
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = req.body;
const user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
}).select("+salt +verifier");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email })
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: req.user.email });
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
return BadRequestError(
Error(
"It looks like some details from the first login are not found. Please try login one again"
)
);
}
const server = new jsrp.server();
@@ -275,9 +272,7 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(
loginSRPDetailFromDB.clientPublicKey
);
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
@@ -294,27 +289,20 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
verifier
},
{ upsert: true, new: true }
).select('+user, encryptedPrivateKey');
).select("+user, encryptedPrivateKey");
// issue tokens
return res.status(200).send({
message: 'Successfully updated backup private key',
message: "Successfully updated backup private key",
backupPrivateKey
});
}
return res.status(400).send({
message: 'Failed to update backup private key'
message: "Failed to update backup private key"
});
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update backup private key'
});
}
};
/**
@@ -324,28 +312,18 @@ export const createBackupPrivateKey = async (req: Request, res: Response) => {
* @returns
*/
export const getBackupPrivateKey = async (req: Request, res: Response) => {
let backupPrivateKey;
try {
backupPrivateKey = await BackupPrivateKey.findOne({
const backupPrivateKey = await BackupPrivateKey.findOne({
user: req.user._id
}).select('+encryptedPrivateKey +iv +tag');
}).select("+encryptedPrivateKey +iv +tag");
if (!backupPrivateKey) throw new Error('Failed to find backup private key');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get backup private key'
});
}
if (!backupPrivateKey) throw new Error("Failed to find backup private key");
return res.status(200).send({
backupPrivateKey
});
}
};
export const resetPassword = async (req: Request, res: Response) => {
try {
const {
protectedKey,
protectedKeyIV,
@@ -354,7 +332,7 @@ export const resetPassword = async (req: Request, res: Response) => {
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
verifier
} = req.body;
await User.findByIdAndUpdate(
@@ -374,15 +352,8 @@ export const resetPassword = async (req: Request, res: Response) => {
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get backup private key'
});
}
return res.status(200).send({
message: 'Successfully reset password'
message: "Successfully reset password"
});
}
};

View File

@@ -1,15 +1,15 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Key, Secret } from '../../models';
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Key } from "../../models";
import {
v1PushSecrets as push,
pullSecrets as pull,
v1PushSecrets as push,
reformatPullSecrets
} from '../../helpers/secret';
import { pushKeys } from '../../helpers/key';
import { eventPushSecrets } from '../../events';
import { EventService } from '../../services';
import { TelemetryService } from '../../services';
} from "../../helpers/secret";
import { pushKeys } from "../../helpers/key";
import { eventPushSecrets } from "../../events";
import { EventService } from "../../services";
import { TelemetryService } from "../../services";
interface PushSecret {
ciphertextKey: string;
@@ -24,7 +24,7 @@ interface PushSecret {
ivComment: string;
tagComment: string;
hashComment: string;
type: 'shared' | 'personal';
type: "shared" | "personal";
}
/**
@@ -36,9 +36,7 @@ interface PushSecret {
*/
export const pushSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId]
try {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
let { secrets }: { secrets: PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
const { workspaceId } = req.params;
@@ -46,13 +44,11 @@ export const pushSecrets = async (req: Request, res: Response) => {
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
throw new Error("Failed to validate environment");
}
// sanitize secrets
secrets = secrets.filter(
(s: PushSecret) => s.ciphertextKey !== '' && s.ciphertextValue !== ''
);
secrets = secrets.filter((s: PushSecret) => s.ciphertextKey !== "" && s.ciphertextValue !== "");
await push({
userId: req.user._id,
@@ -67,16 +63,15 @@ export const pushSecrets = async (req: Request, res: Response) => {
keys
});
if (postHogClient) {
postHogClient.capture({
event: 'secrets pushed',
event: "secrets pushed",
distinctId: req.user.email,
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
channel: channel ? channel : "cli"
}
});
}
@@ -84,20 +79,14 @@ export const pushSecrets = async (req: Request, res: Response) => {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath: "/"
})
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to upload workspace secrets'
});
}
return res.status(200).send({
message: 'Successfully uploaded workspace secrets'
message: "Successfully uploaded workspace secrets"
});
};
@@ -110,9 +99,8 @@ export const pushSecrets = async (req: Request, res: Response) => {
*/
export const pullSecrets = async (req: Request, res: Response) => {
let secrets;
let key;
try {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
@@ -120,25 +108,25 @@ export const pullSecrets = async (req: Request, res: Response) => {
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
throw new Error("Failed to validate environment");
}
secrets = await pull({
userId: req.user._id.toString(),
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.ip
channel: channel ? channel : "cli",
ipAddress: req.realIP
});
key = await Key.findOne({
const key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate('sender', '+publicKey');
.populate("sender", "+publicKey");
if (channel !== 'cli') {
if (channel !== "cli") {
secrets = reformatPullSecrets({ secrets });
}
@@ -146,22 +134,15 @@ export const pullSecrets = async (req: Request, res: Response) => {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: 'secrets pulled',
event: "secrets pulled",
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
channel: channel ? channel : "cli"
}
});
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to pull workspace secrets'
});
}
return res.status(200).send({
secrets,
@@ -178,10 +159,7 @@ export const pullSecrets = async (req: Request, res: Response) => {
* @returns
*/
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
let secrets;
let key;
try {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
@@ -189,18 +167,18 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
throw new Error("Failed to validate environment");
}
secrets = await pull({
const secrets = await pull({
userId: req.serviceToken.user._id.toString(),
workspaceId,
environment,
channel: 'cli',
ipAddress: req.ip
channel: "cli",
ipAddress: req.realIP
});
key = {
const key = {
encryptedKey: req.serviceToken.encryptedKey,
nonce: req.serviceToken.nonce,
sender: {
@@ -214,22 +192,15 @@ export const pullSecretsServiceToken = async (req: Request, res: Response) => {
// capture secrets pulled event in production
postHogClient.capture({
distinctId: req.serviceToken.user.email,
event: 'secrets pulled',
event: "secrets pulled",
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
channel: channel ? channel : "cli"
}
});
}
} catch (err) {
Sentry.setUser({ email: req.serviceToken.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to pull workspace secrets'
});
}
return res.status(200).send({
secrets: reformatPullSecrets({ secrets }),

View File

@@ -0,0 +1,116 @@
import { Request, Response } from "express";
import { validateMembership } from "../../helpers";
import SecretImport from "../../models/secretImports";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import { BadRequestError } from "../../utils/errors";
import { ADMIN, MEMBER } from "../../variables";
export const createSecretImport = async (req: Request, res: Response) => {
const { workspaceId, environment, folderId, secretImport } = req.body;
const importSecDoc = await SecretImport.findOne({
workspace: workspaceId,
environment,
folderId
});
if (!importSecDoc) {
const doc = new SecretImport({
workspace: workspaceId,
environment,
folderId,
imports: [{ environment: secretImport.environment, secretPath: secretImport.secretPath }]
});
await doc.save();
return res.status(200).json({ message: "successfully created secret import" });
}
const doesImportExist = importSecDoc.imports.find(
(el) => el.environment === secretImport.environment && el.secretPath === secretImport.secretPath
);
if (doesImportExist) {
throw BadRequestError({ message: "Secret import already exist" });
}
importSecDoc.imports.push({
environment: secretImport.environment,
secretPath: secretImport.secretPath
});
await importSecDoc.save();
return res.status(200).json({ message: "successfully created secret import" });
};
// to keep the ordering, you must pass all the imports in here not the only updated one
// this is because the order decide which import gets overriden
export const updateSecretImport = async (req: Request, res: Response) => {
const { id } = req.params;
const { secretImports } = req.body;
const importSecDoc = await SecretImport.findById(id);
if (!importSecDoc) {
throw BadRequestError({ message: "Import not found" });
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: importSecDoc.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
importSecDoc.imports = secretImports;
await importSecDoc.save();
return res.status(200).json({ message: "successfully updated secret import" });
};
export const deleteSecretImport = async (req: Request, res: Response) => {
const { id } = req.params;
const { secretImportEnv, secretImportPath } = req.body;
const importSecDoc = await SecretImport.findById(id);
if (!importSecDoc) {
throw BadRequestError({ message: "Import not found" });
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: importSecDoc.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
importSecDoc.imports = importSecDoc.imports.filter(
({ environment, secretPath }) =>
!(environment === secretImportEnv && secretPath === secretImportPath)
);
await importSecDoc.save();
return res.status(200).json({ message: "successfully delete secret import" });
};
export const getSecretImports = async (req: Request, res: Response) => {
const { workspaceId, environment, folderId } = req.query;
const importSecDoc = await SecretImport.findOne({
workspace: workspaceId,
environment,
folderId
});
if (!importSecDoc) {
return res.status(200).json({ secretImport: {} });
}
return res.status(200).json({ secretImport: importSecDoc });
};
export const getAllSecretsFromImport = async (req: Request, res: Response) => {
const { workspaceId, environment, folderId } = req.query as {
workspaceId: string;
environment: string;
folderId: string;
};
const importSecDoc = await SecretImport.findOne({
workspace: workspaceId,
environment,
folderId
});
if (!importSecDoc) {
return res.status(200).json({ secrets: {} });
}
const secrets = await getAllImportedSecrets(workspaceId, environment, folderId);
return res.status(200).json({ secrets });
};

View File

@@ -0,0 +1,91 @@
import { Request, Response } from "express";
import GitAppInstallationSession from "../../models/gitAppInstallationSession";
import crypto from "crypto";
import { Types } from "mongoose";
import { UnauthorizedRequestError } from "../../utils/errors";
import GitAppOrganizationInstallation from "../../models/gitAppOrganizationInstallation";
import { MembershipOrg } from "../../models";
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../models/gitRisks";
export const createInstallationSession = async (req: Request, res: Response) => {
const sessionId = crypto.randomBytes(16).toString("hex");
await GitAppInstallationSession.findByIdAndUpdate(
req.organization,
{
organization: new Types.ObjectId(req.organization),
sessionId: sessionId,
user: new Types.ObjectId(req.user._id)
},
{ upsert: true }
).lean();
res.send({
sessionId: sessionId
})
}
export const linkInstallationToOrganization = async (req: Request, res: Response) => {
const { installationId, sessionId } = req.body
const installationSession = await GitAppInstallationSession.findOneAndDelete({ sessionId: sessionId })
if (!installationSession) {
throw UnauthorizedRequestError()
}
const userMembership = await MembershipOrg.find({ user: req.user._id, organization: installationSession.organization })
if (!userMembership) {
throw UnauthorizedRequestError()
}
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate({
organizationId: installationSession.organization,
}, {
installationId: installationId,
organizationId: installationSession.organization,
user: installationSession.user
}, {
upsert: true
}).lean()
res.json(installationLink)
}
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
const { organizationId } = req.params
try {
const appInstallation = await GitAppOrganizationInstallation.findOne({ organizationId: organizationId }).lean()
if (!appInstallation) {
res.json({
appInstallationComplete: false
})
}
res.json({
appInstallationComplete: true
})
} catch {
res.json({
appInstallationComplete: false
})
}
}
export const getRisksForOrganization = async (req: Request, res: Response) => {
const { organizationId } = req.params
const risks = await GitRisks.find({ organization: organizationId }).sort({ createdAt: -1 }).lean()
res.json({
risks: risks
})
}
export const updateRisksStatus = async (req: Request, res: Response) => {
const { riskId } = req.params
const { status } = req.body
const isRiskResolved = status == STATUS_RESOLVED_FALSE_POSITIVE || status == STATUS_RESOLVED_REVOKED || status == STATUS_RESOLVED_NOT_REVOKED ? true : false
const risk = await GitRisks.findByIdAndUpdate(riskId, {
status: status,
isResolved: isRiskResolved
}).lean()
res.json(risk)
}

View File

@@ -0,0 +1,235 @@
import { Request, Response } from "express";
import { Secret } from "../../models";
import Folder from "../../models/folder";
import { BadRequestError } from "../../utils/errors";
import {
appendFolder,
deleteFolderById,
generateFolderId,
getAllFolderIds,
getFolderByPath,
getParentFromFolderId,
searchByFolderId,
searchByFolderIdWithDir,
validateFolderName,
} from "../../services/FolderService";
import { ADMIN, MEMBER } from "../../variables";
import { validateMembership } from "../../helpers/membership";
import { FolderVersion } from "../../ee/models";
import { EESecretService } from "../../ee/services";
// TODO
// verify workspace id/environment
export const createFolder = async (req: Request, res: Response) => {
const { workspaceId, environment, folderName, parentFolderId } = req.body;
if (!validateFolderName(folderName)) {
throw BadRequestError({
message: "Folder name cannot contain spaces. Only underscore and dashes",
});
}
const folders = await Folder.findOne({
workspace: workspaceId,
environment,
}).lean();
// space has no folders initialized
if (!folders) {
const id = generateFolderId();
const folder = new Folder({
workspace: workspaceId,
environment,
nodes: {
id: "root",
name: "root",
version: 1,
children: [{ id, name: folderName, children: [], version: 1 }],
},
});
await folder.save();
const folderVersion = new FolderVersion({
workspace: workspaceId,
environment,
nodes: folder.nodes,
});
await folderVersion.save();
await EESecretService.takeSecretSnapshot({
workspaceId,
environment,
});
return res.json({ folder: { id, name: folderName } });
}
const folder = appendFolder(folders.nodes, { folderName, parentFolderId });
await Folder.findByIdAndUpdate(folders._id, folders);
const parentFolder = searchByFolderId(folders.nodes, parentFolderId);
const folderVersion = new FolderVersion({
workspace: workspaceId,
environment,
nodes: parentFolder,
});
await folderVersion.save();
await EESecretService.takeSecretSnapshot({
workspaceId,
environment,
folderId: parentFolderId,
});
return res.json({ folder });
};
export const updateFolderById = async (req: Request, res: Response) => {
const { folderId } = req.params;
const { name, workspaceId, environment } = req.body;
const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (!folders) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId,
acceptedRoles: [ADMIN, MEMBER],
});
const parentFolder = getParentFromFolderId(folders.nodes, folderId);
if (!parentFolder) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
const folder = parentFolder.children.find(({ id }) => id === folderId);
if (!folder) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
parentFolder.version += 1;
folder.name = name;
await Folder.findByIdAndUpdate(folders._id, folders);
const folderVersion = new FolderVersion({
workspace: workspaceId,
environment,
nodes: parentFolder,
});
await folderVersion.save();
await EESecretService.takeSecretSnapshot({
workspaceId,
environment,
folderId: parentFolder.id,
});
return res.json({
message: "Successfully updated folder",
folder: { name: folder.name, id: folder.id },
});
};
export const deleteFolder = async (req: Request, res: Response) => {
const { folderId } = req.params;
const { workspaceId, environment } = req.body;
const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (!folders) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId,
acceptedRoles: [ADMIN, MEMBER],
});
const delOp = deleteFolderById(folders.nodes, folderId);
if (!delOp) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
const { deletedNode: delFolder, parent: parentFolder } = delOp;
parentFolder.version += 1;
const delFolderIds = getAllFolderIds(delFolder);
await Folder.findByIdAndUpdate(folders._id, folders);
const folderVersion = new FolderVersion({
workspace: workspaceId,
environment,
nodes: parentFolder,
});
await folderVersion.save();
if (delFolderIds.length) {
await Secret.deleteMany({
folder: { $in: delFolderIds.map(({ id }) => id) },
workspace: workspaceId,
environment,
});
}
await EESecretService.takeSecretSnapshot({
workspaceId,
environment,
folderId: parentFolder.id,
});
res.send({ message: "successfully deleted folders", folders: delFolderIds });
};
// TODO: validate workspace
export const getFolders = async (req: Request, res: Response) => {
const { workspaceId, environment, parentFolderId, parentFolderPath } =
req.query as {
workspaceId: string;
environment: string;
parentFolderId?: string;
parentFolderPath?: string;
};
const folders = await Folder.findOne({ workspace: workspaceId, environment });
if (!folders) {
res.send({ folders: [], dir: [] });
return;
}
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId,
acceptedRoles: [ADMIN, MEMBER],
});
// if instead of parentFolderId given a path like /folder1/folder2
if (parentFolderPath) {
const folder = getFolderByPath(folders.nodes, parentFolderPath);
if (!folder) {
res.send({ folders: [], dir: [] });
return;
}
// dir is not needed at present as this is only used in overview section of secrets
res.send({
folders: folder.children.map(({ id, name }) => ({ id, name })),
dir: [{ name: folder.name, id: folder.id }],
});
}
if (!parentFolderId) {
const rootFolders = folders.nodes.children.map(({ id, name }) => ({
id,
name,
}));
res.send({ folders: rootFolders });
return;
}
const folderBySearch = searchByFolderIdWithDir(folders.nodes, parentFolderId);
if (!folderBySearch) {
throw BadRequestError({ message: "The folder doesn't exist" });
}
const { folder, dir } = folderBySearch;
res.send({
folders: folder.children.map(({ id, name }) => ({ id, name })),
dir,
});
};

View File

@@ -1,7 +1,7 @@
import { Request, Response } from 'express';
import { ServiceToken } from '../../models';
import { createToken } from '../../helpers/auth';
import { getJwtServiceSecret } from '../../config';
import { Request, Response } from "express";
import { ServiceToken } from "../../models";
import { createToken } from "../../helpers/auth";
import { getJwtServiceSecret } from "../../config";
/**
* Return service token on request
@@ -11,7 +11,7 @@ import { getJwtServiceSecret } from '../../config';
*/
export const getServiceToken = async (req: Request, res: Response) => {
return res.status(200).send({
serviceToken: req.serviceToken
serviceToken: req.serviceToken,
});
};
@@ -31,13 +31,13 @@ export const createServiceToken = async (req: Request, res: Response) => {
expiresIn,
publicKey,
encryptedKey,
nonce
nonce,
} = req.body;
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
throw new Error("Failed to validate environment");
}
// compute access token expiration date
@@ -52,24 +52,24 @@ export const createServiceToken = async (req: Request, res: Response) => {
expiresAt,
publicKey,
encryptedKey,
nonce
nonce,
}).save();
token = createToken({
payload: {
serviceTokenId: serviceToken._id.toString(),
workspaceId
workspaceId,
},
expiresIn: expiresIn,
secret: getJwtServiceSecret()
secret: await getJwtServiceSecret(),
});
} catch (err) {
return res.status(400).send({
message: 'Failed to create service token'
message: "Failed to create service token",
});
}
return res.status(200).send({
token
token,
});
};

View File

@@ -1,13 +1,15 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { User } from '../../models';
import { Request, Response } from "express";
import { User } from "../../models";
import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup";
import { createToken } from "../../helpers/auth";
import { BadRequestError } from "../../utils/errors";
import {
sendEmailVerification,
checkEmailVerification,
} from '../../helpers/signup';
import { createToken } from '../../helpers/auth';
import { BadRequestError } from '../../utils/errors';
import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
getInviteOnlySignup,
getJwtSignupLifetime,
getJwtSignupSecret,
getSmtpConfigured
} from "../../config";
import { validateUserEmail } from "../../validation";
/**
* Signup step 1: Initialize account for user under email [email] and send a verification code
@@ -17,36 +19,22 @@ import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpC
* @returns
*/
export const beginEmailSignup = async (req: Request, res: Response) => {
let email: string;
try {
email = req.body.email;
const email: string = req.body.email;
if (getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
const userCount = await User.countDocuments({})
if (userCount != 0) {
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." })
}
}
// validate that email is not disposable
validateUserEmail(email);
const user = await User.findOne({ email }).select('+publicKey');
const user = await User.findOne({ email }).select("+publicKey");
if (user && user?.publicKey) {
// case: user has already completed account
return res.status(403).send({
error: 'Failed to send email verification code for complete account'
error: "Failed to send email verification code for complete account"
});
}
// send send verification email
await sendEmailVerification({ email });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to send email verification code'
});
}
return res.status(200).send({
message: `Sent an email verification code to ${email}`
@@ -61,21 +49,30 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
* @returns
*/
export const verifyEmailSignup = async (req: Request, res: Response) => {
let user, token;
try {
let user;
const { email, code } = req.body;
// initialize user account
user = await User.findOne({ email }).select('+publicKey');
user = await User.findOne({ email }).select("+publicKey");
if (user && user?.publicKey) {
// case: user has already completed account
return res.status(403).send({
error: 'Failed email verification for complete user'
error: "Failed email verification for complete user"
});
}
if (await getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
const userCount = await User.countDocuments({});
if (userCount != 0) {
throw BadRequestError({
message: "New user sign ups are not allowed at this time. You must be invited to sign up."
});
}
}
// verify email
if (getSmtpConfigured()) {
if (await getSmtpConfigured()) {
await checkEmailVerification({
email,
code
@@ -89,23 +86,16 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
}
// generate temporary signup token
token = createToken({
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: getJwtSignupLifetime(),
secret: getJwtSignupSecret()
expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret()
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed email verification'
});
}
return res.status(200).send({
message: 'Successfuly verified email',
message: "Successfuly verified email",
user,
token
});

View File

@@ -1,41 +0,0 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import Stripe from 'stripe';
import { getStripeSecretKey, getStripeWebhookSecret } from '../../config';
/**
* Handle service provisioning/un-provisioning via Stripe
* @param req
* @param res
* @returns
*/
export const handleWebhook = async (req: Request, res: Response) => {
let event;
try {
// check request for valid stripe signature
const stripe = new Stripe(getStripeSecretKey(), {
apiVersion: '2022-08-01'
});
const sig = req.headers['stripe-signature'] as string;
event = stripe.webhooks.constructEvent(
req.body,
sig,
getStripeWebhookSecret()
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to process webhook'
});
}
switch (event.type) {
case '':
break;
default:
}
return res.json({ received: true });
};

View File

@@ -1,6 +1,5 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { UserAction } from '../../models';
import { Request, Response } from "express";
import { UserAction } from "../../models";
/**
* Add user action [action]
@@ -11,32 +10,23 @@ import { UserAction } from '../../models';
export const addUserAction = async (req: Request, res: Response) => {
// add/record new action [action] for user with id [req.user._id]
let userAction;
try {
const { action } = req.body;
userAction = await UserAction.findOneAndUpdate(
const userAction = await UserAction.findOneAndUpdate(
{
user: req.user._id,
action
action,
},
{ user: req.user._id, action },
{
new: true,
upsert: true
upsert: true,
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to record user action'
});
}
return res.status(200).send({
message: 'Successfully recorded user action',
userAction
message: "Successfully recorded user action",
userAction,
});
};
@@ -48,23 +38,14 @@ export const addUserAction = async (req: Request, res: Response) => {
*/
export const getUserAction = async (req: Request, res: Response) => {
// get user action [action] for user with id [req.user._id]
let userAction;
try {
const action: string = req.query.action as string;
userAction = await UserAction.findOne({
const userAction = await UserAction.findOne({
user: req.user._id,
action
action,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get user action'
});
}
return res.status(200).send({
userAction
userAction,
});
};

View File

@@ -1,4 +1,4 @@
import { Request, Response } from 'express';
import { Request, Response } from "express";
/**
* Return user on request
@@ -8,6 +8,6 @@ import { Request, Response } from 'express';
*/
export const getUser = async (req: Request, res: Response) => {
return res.status(200).send({
user: req.user
user: req.user,
});
};

View File

@@ -0,0 +1,140 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config";
import { validateMembership } from "../../helpers";
import Webhook from "../../models/webhooks";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError } from "../../utils/errors";
import { ADMIN, ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, MEMBER } from "../../variables";
export const createWebhook = async (req: Request, res: Response) => {
const { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath } = req.body;
const webhook = new Webhook({
workspace: workspaceId,
environment,
secretPath,
url: webhookUrl,
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_BASE64
});
if (webhookSecretKey) {
const rootEncryptionKey = await getRootEncryptionKey();
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
webhook.iv = iv;
webhook.tag = tag;
webhook.encryptedSecretKey = ciphertext;
}
await webhook.save();
return res.status(200).send({
webhook,
message: "successfully created webhook"
});
};
export const updateWebhook = async (req: Request, res: Response) => {
const { webhookId } = req.params;
const { isDisabled } = req.body;
const webhook = await Webhook.findById(webhookId);
if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" });
}
// check that user is a member of the workspace
await validateMembership({
userId: req.user._id.toString(),
workspaceId: webhook.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
if (typeof isDisabled !== undefined) {
webhook.isDisabled = isDisabled;
}
await webhook.save();
return res.status(200).send({
webhook,
message: "successfully updated webhook"
});
};
export const deleteWebhook = async (req: Request, res: Response) => {
const { webhookId } = req.params;
const webhook = await Webhook.findById(webhookId);
if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" });
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: webhook.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
await webhook.remove();
return res.status(200).send({
message: "successfully removed webhook"
});
};
export const testWebhook = async (req: Request, res: Response) => {
const { webhookId } = req.params;
const webhook = await Webhook.findById(webhookId);
if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" });
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: webhook.workspace,
acceptedRoles: [ADMIN, MEMBER]
});
try {
await triggerWebhookRequest(
webhook,
getWebhookPayload(
"test",
webhook.workspace.toString(),
webhook.environment,
webhook.secretPath
)
);
await Webhook.findByIdAndUpdate(webhookId, {
lastStatus: "success",
lastRunErrorMessage: null
});
} catch (err) {
await Webhook.findByIdAndUpdate(webhookId, {
lastStatus: "failed",
lastRunErrorMessage: (err as Error).message
});
return res.status(400).send({
message: "Failed to receive response",
error: (err as Error).message
});
}
return res.status(200).send({
message: "Successfully received response"
});
};
export const listWebhooks = async (req: Request, res: Response) => {
const { environment, workspaceId, secretPath } = req.query;
const optionalFilters: Record<string, string> = {};
if (environment) optionalFilters.environment = environment as string;
if (secretPath) optionalFilters.secretPath = secretPath as string;
const webhooks = await Webhook.find({
workspace: new Types.ObjectId(workspaceId as string),
...optionalFilters
});
return res.status(200).send({
webhooks
});
};

View File

@@ -1,19 +1,18 @@
import { Request, Response } from "express";
import * as Sentry from "@sentry/node";
import {
Workspace,
Membership,
MembershipOrg,
IUser,
Integration,
IntegrationAuth,
IUser,
Membership,
MembershipOrg,
ServiceToken,
ServiceTokenData,
Workspace,
} from "../../models";
import {
createWorkspace as create,
deleteWorkspace as deleteWork,
} from "../../helpers/workspace";
import { EELicenseService } from "../../ee/services";
import { addMemberships } from "../../helpers/membership";
import { ADMIN } from "../../variables";
@@ -24,11 +23,9 @@ import { ADMIN } from "../../variables";
* @returns
*/
export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
let publicKeys;
try {
const { workspaceId } = req.params;
publicKeys = (
const publicKeys = (
await Membership.find({
workspace: workspaceId,
}).populate<{ user: IUser }>("user", "publicKey")
@@ -38,13 +35,6 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
userId: member.user._id,
};
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get workspace member public keys",
});
}
return res.status(200).send({
publicKeys,
@@ -58,20 +48,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
* @returns
*/
export const getWorkspaceMemberships = async (req: Request, res: Response) => {
let users;
try {
const { workspaceId } = req.params;
users = await Membership.find({
const users = await Membership.find({
workspace: workspaceId,
}).populate("user", "+publicKey");
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get workspace members",
});
}
return res.status(200).send({
users,
@@ -85,20 +66,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
* @returns
*/
export const getWorkspaces = async (req: Request, res: Response) => {
let workspaces;
try {
workspaces = (
const workspaces = (
await Membership.find({
user: req.user._id,
}).populate("workspace")
).map((m) => m.workspace);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get workspaces",
});
}
return res.status(200).send({
workspaces,
@@ -112,20 +84,11 @@ export const getWorkspaces = async (req: Request, res: Response) => {
* @returns
*/
export const getWorkspace = async (req: Request, res: Response) => {
let workspace;
try {
const { workspaceId } = req.params;
workspace = await Workspace.findOne({
const workspace = await Workspace.findOne({
_id: workspaceId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get workspace",
});
}
return res.status(200).send({
workspace,
@@ -140,8 +103,6 @@ export const getWorkspace = async (req: Request, res: Response) => {
* @returns
*/
export const createWorkspace = async (req: Request, res: Response) => {
let workspace;
try {
const { workspaceName, organizationId } = req.body;
// validate organization membership
@@ -154,12 +115,24 @@ export const createWorkspace = async (req: Request, res: Response) => {
throw new Error("Failed to validate organization membership");
}
const plan = await EELicenseService.getPlan(organizationId);
if (plan.workspaceLimit !== null) {
// case: limit imposed on number of workspaces allowed
if (plan.workspacesUsed >= plan.workspaceLimit) {
// case: number of workspaces used exceeds the number of workspaces allowed
return res.status(400).send({
message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces.",
});
}
}
if (workspaceName.length < 1) {
throw new Error("Workspace names must be at least 1-character long");
}
// create workspace and add user as member
workspace = await create({
const workspace = await create({
name: workspaceName,
organizationId,
});
@@ -169,13 +142,6 @@ export const createWorkspace = async (req: Request, res: Response) => {
workspaceId: workspace._id.toString(),
roles: [ADMIN],
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to create workspace",
});
}
return res.status(200).send({
workspace,
@@ -189,20 +155,12 @@ export const createWorkspace = async (req: Request, res: Response) => {
* @returns
*/
export const deleteWorkspace = async (req: Request, res: Response) => {
try {
const { workspaceId } = req.params;
// delete workspace
await deleteWork({
id: workspaceId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to delete workspace",
});
}
return res.status(200).send({
message: "Successfully deleted workspace",
@@ -216,12 +174,10 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
* @returns
*/
export const changeWorkspaceName = async (req: Request, res: Response) => {
let workspace;
try {
const { workspaceId } = req.params;
const { name } = req.body;
workspace = await Workspace.findOneAndUpdate(
const workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId,
},
@@ -232,13 +188,6 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
new: true,
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to change workspace name",
});
}
return res.status(200).send({
message: "Successfully changed workspace name",
@@ -253,20 +202,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
* @returns
*/
export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
let integrations;
try {
const { workspaceId } = req.params;
integrations = await Integration.find({
const integrations = await Integration.find({
workspace: workspaceId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get workspace integrations",
});
}
return res.status(200).send({
integrations,
@@ -283,20 +223,11 @@ export const getWorkspaceIntegrationAuthorizations = async (
req: Request,
res: Response
) => {
let authorizations;
try {
const { workspaceId } = req.params;
authorizations = await IntegrationAuth.find({
const authorizations = await IntegrationAuth.find({
workspace: workspaceId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get workspace integration authorizations",
});
}
return res.status(200).send({
authorizations,
@@ -313,21 +244,12 @@ export const getWorkspaceServiceTokens = async (
req: Request,
res: Response
) => {
let serviceTokens;
try {
const { workspaceId } = req.params;
// ?? FIX.
serviceTokens = await ServiceToken.find({
const serviceTokens = await ServiceToken.find({
user: req.user._id,
workspace: workspaceId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get workspace service tokens",
});
}
return res.status(200).send({
serviceTokens,

View File

@@ -1,104 +0,0 @@
import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
APIKeyData
} from '../../models';
import { getSaltRounds } from '../../config';
/**
* Return API key data for user with id [req.user_id]
* @param req
* @param res
* @returns
*/
export const getAPIKeyData = async (req: Request, res: Response) => {
let apiKeyData;
try {
apiKeyData = await APIKeyData.find({
user: req.user._id
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get API key data'
});
}
return res.status(200).send({
apiKeyData
});
}
/**
* Create new API key data for user with id [req.user._id]
* @param req
* @param res
*/
export const createAPIKeyData = async (req: Request, res: Response) => {
let apiKey, apiKeyData;
try {
const { name, expiresIn } = req.body;
const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, getSaltRounds());
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
apiKeyData = await new APIKeyData({
name,
lastUsed: new Date(),
expiresAt,
user: req.user._id,
secretHash
}).save();
// return api key data without sensitive data
apiKeyData = await APIKeyData.findById(apiKeyData._id);
if (!apiKeyData) throw new Error('Failed to find API key data');
apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to API key data'
});
}
return res.status(200).send({
apiKey,
apiKeyData
});
}
/**
* Delete API key data with id [apiKeyDataId].
* @param req
* @param res
* @returns
*/
export const deleteAPIKeyData = async (req: Request, res: Response) => {
let apiKeyData;
try {
const { apiKeyDataId } = req.params;
apiKeyData = await APIKeyData.findByIdAndDelete(apiKeyDataId);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete API key data'
});
}
return res.status(200).send({
apiKeyData
});
}

View File

@@ -1,28 +1,27 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import * as Sentry from '@sentry/node';
import * as bigintConversion from 'bigint-conversion';
const jsrp = require('jsrp');
import { User, LoginSRPDetail } from '../../models';
import { issueAuthTokens, createToken } from '../../helpers/auth';
import { checkUserDevice } from '../../helpers/user';
import { sendMail } from '../../helpers/nodemailer';
import { TokenService } from '../../services';
import { EELogService } from '../../ee/services';
import { BadRequestError, InternalServerError } from '../../utils/errors';
import { Request, Response } from "express";
import jwt from "jsonwebtoken";
import * as bigintConversion from "bigint-conversion";
const jsrp = require("jsrp");
import { LoginSRPDetail, User } from "../../models";
import { createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors";
import {
ACTION_LOGIN,
TOKEN_EMAIL_MFA,
ACTION_LOGIN
} from '../../variables';
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
} from "../../variables";
import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this
import {
getHttpsEnabled,
getJwtMfaLifetime,
getJwtMfaSecret,
getHttpsEnabled
} from '../../config';
} from "../../config";
declare module 'jsonwebtoken' {
declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
}
@@ -35,23 +34,22 @@ declare module 'jsonwebtoken' {
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
try {
const {
email,
clientPublicKey
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier');
email,
}).select("+salt +verifier");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
verifier: user.verifier,
},
async () => {
// generate server-side public key
@@ -65,17 +63,11 @@ export const login1 = async (req: Request, res: Response) => {
return res.status(200).send({
serverPublicKey,
salt: user.salt
salt: user.salt,
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to start authentication process'
});
}
};
/**
@@ -86,16 +78,14 @@ export const login1 = async (req: Request, res: Response) => {
* @returns
*/
export const login2 = async (req: Request, res: Response) => {
try {
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' });
if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" });
const { email, clientProof } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
email,
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
@@ -108,65 +98,68 @@ export const login2 = async (req: Request, res: Response) => {
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetail.serverBInt
b: loginSRPDetail.serverBInt,
},
async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
if (user.isMfaEnabled) {
// case: user has MFA enabled
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString()
userId: user._id.toString(),
},
expiresIn: getJwtMfaLifetime(),
secret: getJwtMfaSecret()
expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret(),
});
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email
email,
});
// send MFA code [code] to [email]
await sendMail({
template: 'emailMfa.handlebars',
subjectLine: 'Infisical MFA code',
template: "emailMfa.handlebars",
subjectLine: "Infisical MFA code",
recipients: [email],
substitutions: {
code
}
code,
},
});
return res.status(200).send({
mfaEnabled: true,
token
token,
});
}
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// issue tokens
const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: getHttpsEnabled()
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// case: user does not have MFA enablgged
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
// case: user does not have MFA enabled
// return (access) token in response
interface ResponseData {
@@ -189,7 +182,7 @@ export const login2 = async (req: Request, res: Response) => {
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
tag: user.tag,
}
if (
@@ -204,31 +197,24 @@ export const login2 = async (req: Request, res: Response) => {
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
userId: user._id,
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.ip,
});
return res.status(200).send(response);
}
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
message: "Failed to authenticate. Try again?",
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
};
/**
@@ -237,33 +223,25 @@ export const login2 = async (req: Request, res: Response) => {
* @param res
*/
export const sendMfaToken = async (req: Request, res: Response) => {
try {
const { email } = req.body;
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email
email,
});
// send MFA code [code] to [email]
await sendMail({
template: 'emailMfa.handlebars',
subjectLine: 'Infisical MFA code',
template: "emailMfa.handlebars",
subjectLine: "Infisical MFA code",
recipients: [email],
substitutions: {
code
}
code,
},
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to send MFA code'
});
}
return res.status(200).send({
message: 'Successfully sent new MFA code'
message: "Successfully sent new MFA code",
});
}
@@ -279,30 +257,36 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
await TokenService.validateToken({
type: TOKEN_EMAIL_MFA,
email,
token: mfaToken
token: mfaToken,
});
const user = await User.findOne({
email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
email,
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
if (!user) throw new Error('Failed to find user');
if (!user) throw new Error("Failed to find user");
await LoginSRPDetail.deleteOne({ userId: user.id })
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// issue tokens
const tokens = await issueAuthTokens({ userId: user._id.toString() });
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: getHttpsEnabled()
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
interface VerifyMfaTokenRes {
@@ -335,7 +319,7 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string
tag: user.tag as string,
}
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
@@ -346,14 +330,14 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
userId: user._id,
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP,
});
return res.status(200).send(resObj);

View File

@@ -1,17 +1,17 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Request, Response } from "express";
import {
Integration,
Membership,
Secret,
ServiceToken,
Workspace,
Integration,
ServiceTokenData,
Membership,
} from '../../models';
import { SecretVersion } from '../../ee/models';
import { BadRequestError } from '../../utils/errors';
import _ from 'lodash';
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
Workspace,
} from "../../models";
import { SecretVersion } from "../../ee/models";
import { EELicenseService } from "../../ee/services";
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
import _ from "lodash";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
/**
* Create new workspace environment named [environmentName] under workspace with id
@@ -23,17 +23,33 @@ export const createWorkspaceEnvironment = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environmentName, environmentSlug } = req.body;
try {
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) throw WorkspaceNotFoundError();
const plan = await EELicenseService.getPlan(workspace.organization.toString());
if (plan.environmentLimit !== null) {
// case: limit imposed on number of environments allowed
if (workspace.environments.length >= plan.environmentLimit) {
// case: number of environments used exceeds the number of environments allowed
return res.status(400).send({
message: "Failed to create environment due to environment limit reached. Upgrade plan to create more environments.",
});
}
}
if (
!workspace ||
workspace?.environments.find(
({ name, slug }) => slug === environmentSlug || environmentName === name
)
) {
throw new Error('Failed to create workspace environment');
throw new Error("Failed to create workspace environment");
}
workspace?.environments.push({
@@ -41,16 +57,11 @@ export const createWorkspaceEnvironment = async (
slug: environmentSlug.toLowerCase(),
});
await workspace.save();
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create new workspace environment',
});
}
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
return res.status(200).send({
message: 'Successfully created new environment',
message: "Successfully created new environment",
workspace: workspaceId,
environment: {
name: environmentName,
@@ -72,16 +83,15 @@ export const renameWorkspaceEnvironment = async (
) => {
const { workspaceId } = req.params;
const { environmentName, environmentSlug, oldEnvironmentSlug } = req.body;
try {
// user should pass both new slug and env name
if (!environmentSlug || !environmentName) {
throw new Error('Invalid environment given.');
throw new Error("Invalid environment given.");
}
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error('Failed to create workspace environment');
throw new Error("Failed to create workspace environment");
}
const isEnvExist = workspace.environments.some(
@@ -90,14 +100,14 @@ export const renameWorkspaceEnvironment = async (
(name === environmentName || slug === environmentSlug)
);
if (isEnvExist) {
throw new Error('Invalid environment given');
throw new Error("Invalid environment given");
}
const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === oldEnvironmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
throw new Error("Invalid environment given");
}
workspace.environments[envIndex].name = environmentName;
@@ -127,22 +137,15 @@ export const renameWorkspaceEnvironment = async (
await Membership.updateMany(
{
workspace: workspaceId,
"deniedPermissions.environmentSlug": oldEnvironmentSlug
"deniedPermissions.environmentSlug": oldEnvironmentSlug,
},
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
)
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update workspace environment',
});
}
return res.status(200).send({
message: 'Successfully update environment',
message: "Successfully update environment",
workspace: workspaceId,
environment: {
name: environmentName,
@@ -163,18 +166,17 @@ export const deleteWorkspaceEnvironment = async (
) => {
const { workspaceId } = req.params;
const { environmentSlug } = req.body;
try {
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error('Failed to create workspace environment');
throw new Error("Failed to create workspace environment");
}
const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === environmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
throw new Error("Invalid environment given");
}
workspace.environments.splice(envIndex, 1);
@@ -189,14 +191,21 @@ export const deleteWorkspaceEnvironment = async (
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceToken.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceTokenData.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
// await ServiceToken.deleteMany({
// workspace: workspaceId,
// environment: environmentSlug,
// });
const result = await ServiceTokenData.updateMany(
{ workspace: workspaceId },
{ $pull: { scopes: { environment: environmentSlug } } }
);
if (result.modifiedCount > 0) {
await ServiceTokenData.deleteMany({ workspace: workspaceId, scopes: { $size: 0 } });
}
await Integration.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
@@ -204,18 +213,12 @@ export const deleteWorkspaceEnvironment = async (
await Membership.updateMany(
{ workspace: workspaceId },
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
)
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete workspace environment',
});
}
await EELicenseService.refreshPlan(workspace.organization.toString(), workspaceId);
return res.status(200).send({
message: 'Successfully deleted environment',
message: "Successfully deleted environment",
workspace: workspaceId,
environment: environmentSlug,
});
@@ -229,7 +232,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
const { workspaceId } = req.params;
const workspacesUserIsMemberOf = await Membership.findOne({
workspace: workspaceId,
user: req.user
user: req.user,
})
if (!workspacesUserIsMemberOf) {
@@ -253,7 +256,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
name: environment.name,
slug: environment.slug,
isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked
isReadDenied: isReadBlocked,
})
}
})

View File

@@ -1,15 +1,14 @@
import * as authController from './authController';
import * as signupController from './signupController';
import * as usersController from './usersController';
import * as organizationsController from './organizationsController';
import * as workspaceController from './workspaceController';
import * as serviceTokenDataController from './serviceTokenDataController';
import * as apiKeyDataController from './apiKeyDataController';
import * as secretController from './secretController';
import * as secretsController from './secretsController';
import * as serviceAccountsController from './serviceAccountsController';
import * as environmentController from './environmentController';
import * as tagController from './tagController';
import * as authController from "./authController";
import * as signupController from "./signupController";
import * as usersController from "./usersController";
import * as organizationsController from "./organizationsController";
import * as workspaceController from "./workspaceController";
import * as serviceTokenDataController from "./serviceTokenDataController";
import * as secretController from "./secretController";
import * as secretsController from "./secretsController";
import * as serviceAccountsController from "./serviceAccountsController";
import * as environmentController from "./environmentController";
import * as tagController from "./tagController";
export {
authController,
@@ -18,10 +17,9 @@ export {
organizationsController,
workspaceController,
serviceTokenDataController,
apiKeyDataController,
secretController,
secretsController,
serviceAccountsController,
environmentController,
tagController
tagController,
}

View File

@@ -1,14 +1,13 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Request, Response } from "express";
import { Types } from "mongoose";
import {
MembershipOrg,
Membership,
MembershipOrg,
ServiceAccount,
Workspace,
ServiceAccount
} from '../../models';
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
} from "../../models";
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
/**
* Return memberships for organization with id [organizationId]
@@ -49,23 +48,14 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
}
}
*/
let memberships;
try {
const { organizationId } = req.params;
memberships = await MembershipOrg.find({
organization: organizationId
}).populate('user', '+publicKey');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get organization memberships'
});
}
const memberships = await MembershipOrg.find({
organization: organizationId,
}).populate("user", "+publicKey");
return res.status(200).send({
memberships
memberships,
});
}
@@ -128,29 +118,20 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
}
}
*/
let membership;
try {
const { membershipId } = req.params;
const { role } = req.body;
membership = await MembershipOrg.findByIdAndUpdate(
const membership = await MembershipOrg.findByIdAndUpdate(
membershipId,
{
role
role,
}, {
new: true
new: true,
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update organization membership'
});
}
return res.status(200).send({
membership
membership,
});
}
@@ -197,28 +178,19 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
}
}
*/
let membership;
try {
const { membershipId } = req.params;
// delete organization membership
membership = await deleteMembershipOrg({
membershipOrgId: membershipId
const membership = await deleteMembershipOrg({
membershipOrgId: membershipId,
});
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString()
organizationId: membership.organization.toString(),
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete organization membership'
});
}
return res.status(200).send({
membership
membership,
});
}
@@ -268,23 +240,23 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
(
await Workspace.find(
{
organization: organizationId
organization: organizationId,
},
'_id'
"_id"
)
).map((w) => w._id.toString())
);
const workspaces = (
await Membership.find({
user: req.user._id
}).populate('workspace')
user: req.user._id,
}).populate("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
return res.status(200).send({
workspaces
workspaces,
});
}
@@ -297,10 +269,10 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response
const { organizationId } = req.params;
const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId)
organization: new Types.ObjectId(organizationId),
});
return res.status(200).send({
serviceAccounts
serviceAccounts,
});
}

View File

@@ -1,13 +1,27 @@
import to from "await-to-js";
import { Request, Response } from "express";
import mongoose, { Types } from "mongoose";
import Secret, { ISecret } from "../../models/secret";
import { CreateSecretRequestBody, ModifySecretRequestBody, SanitizedSecretForCreate, SanitizedSecretModify } from "../../types/secret";
import {
CreateSecretRequestBody,
ModifySecretRequestBody,
SanitizedSecretForCreate,
SanitizedSecretModify
} from "../../types/secret";
const { ValidationError } = mongoose.Error;
import { BadRequestError, InternalServerError, UnauthorizedRequestError, ValidationError as RouteValidationError } from '../../utils/errors';
import { AnyBulkWriteOperation } from 'mongodb';
import { SECRET_PERSONAL, SECRET_SHARED } from "../../variables";
import { TelemetryService } from '../../services';
import {
ValidationError as RouteValidationError,
UnauthorizedRequestError
} from "../../utils/errors";
import { AnyBulkWriteOperation } from "mongodb";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED
} from "../../variables";
import { TelemetryService } from "../../services";
import { User } from "../../models";
import { AccountNotFoundError } from "../../utils/errors";
/**
* Create secret for workspace with id [workspaceId] and environment [environment]
@@ -15,9 +29,9 @@ import { TelemetryService } from '../../services';
* @param res
*/
export const createSecret = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
const secretToCreate: CreateSecretRequestBody = req.body.secret;
const { workspaceId, environment } = req.params
const { workspaceId, environment } = req.params;
const sanitizedSecret: SanitizedSecretForCreate = {
secretKeyCiphertext: secretToCreate.secretKeyCiphertext,
secretKeyIV: secretToCreate.secretKeyIV,
@@ -34,46 +48,44 @@ export const createSecret = async (req: Request, res: Response) => {
workspace: new Types.ObjectId(workspaceId),
environment,
type: secretToCreate.type,
user: new Types.ObjectId(req.user._id)
}
user: new Types.ObjectId(req.user._id),
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
};
const [error, secret] = await to(Secret.create(sanitizedSecret).then())
if (error instanceof ValidationError) {
throw RouteValidationError({ message: error.message, stack: error.stack })
}
const secret = await new Secret(sanitizedSecret).save();
if (postHogClient) {
postHogClient.capture({
event: 'secrets added',
event: "secrets added",
distinctId: req.user.email,
properties: {
numberOfSecrets: 1,
workspaceId,
environment,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
userAgent: req.headers?.['user-agent']
channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.["user-agent"]
}
});
}
res.status(200).send({
secret
})
}
});
};
/**
* Create many secrets for workspace wiht id [workspaceId] and environment [environment]
* Create many secrets for workspace with id [workspaceId] and environment [environment]
* @param req
* @param res
*/
export const createSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
const secretsToCreate: CreateSecretRequestBody[] = req.body.secrets;
const { workspaceId, environment } = req.params
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = []
const { workspaceId, environment } = req.params;
const sanitizedSecretesToCreate: SanitizedSecretForCreate[] = [];
secretsToCreate.forEach(rawSecret => {
secretsToCreate.forEach((rawSecret) => {
const safeUpdateFields: SanitizedSecretForCreate = {
secretKeyCiphertext: rawSecret.secretKeyCiphertext,
secretKeyIV: rawSecret.secretKeyIV,
@@ -90,39 +102,34 @@ export const createSecrets = async (req: Request, res: Response) => {
workspace: new Types.ObjectId(workspaceId),
environment,
type: rawSecret.type,
user: new Types.ObjectId(req.user._id)
}
user: new Types.ObjectId(req.user._id),
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_UTF8
};
sanitizedSecretesToCreate.push(safeUpdateFields)
})
sanitizedSecretesToCreate.push(safeUpdateFields);
});
const [bulkCreateError, secrets] = await to(Secret.insertMany(sanitizedSecretesToCreate).then())
if (bulkCreateError) {
if (bulkCreateError instanceof ValidationError) {
throw RouteValidationError({ message: bulkCreateError.message, stack: bulkCreateError.stack })
}
throw InternalServerError({ message: "Unable to process your batch create request. Please try again", stack: bulkCreateError.stack })
}
const secrets = await Secret.insertMany(sanitizedSecretesToCreate);
if (postHogClient) {
postHogClient.capture({
event: 'secrets added',
event: "secrets added",
distinctId: req.user.email,
properties: {
numberOfSecrets: (secretsToCreate ?? []).length,
workspaceId,
environment,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
userAgent: req.headers?.['user-agent']
channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.["user-agent"]
}
});
}
res.status(200).send({
secrets
})
}
});
};
/**
* Delete secrets in workspace with id [workspaceId] and environment [environment]
@@ -130,53 +137,50 @@ export const createSecrets = async (req: Request, res: Response) => {
* @param res
*/
export const deleteSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params
const secretIdsToDelete: string[] = req.body.secretIds
const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params;
const secretIdsToDelete: string[] = req.body.secretIds;
const [secretIdsUserCanDeleteError, secretIdsUserCanDelete] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
if (secretIdsUserCanDeleteError) {
throw InternalServerError({ message: `Unable to fetch secrets you own: [error=${secretIdsUserCanDeleteError.message}]` })
}
const secretIdsUserCanDelete = await Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 });
const secretsUserCanDeleteSet: Set<string> = new Set(secretIdsUserCanDelete.map(objectId => objectId._id.toString()));
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = []
const secretsUserCanDeleteSet: Set<string> = new Set(
secretIdsUserCanDelete.map((objectId) => objectId._id.toString())
);
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = [];
let numSecretsDeleted = 0;
secretIdsToDelete.forEach(secretIdToDelete => {
secretIdsToDelete.forEach((secretIdToDelete) => {
if (secretsUserCanDeleteSet.has(secretIdToDelete)) {
const deleteOperation = { deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } } }
deleteOperationsToPerform.push(deleteOperation)
const deleteOperation = {
deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } }
};
deleteOperationsToPerform.push(deleteOperation);
numSecretsDeleted++;
} else {
throw RouteValidationError({ message: "You cannot delete secrets that you do not have access to" })
throw RouteValidationError({
message: "You cannot delete secrets that you do not have access to"
});
}
})
});
const [bulkDeleteError, bulkDelete] = await to(Secret.bulkWrite(deleteOperationsToPerform).then())
if (bulkDeleteError) {
if (bulkDeleteError instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkDeleteError.stack })
}
throw InternalServerError()
}
await Secret.bulkWrite(deleteOperationsToPerform);
if (postHogClient) {
postHogClient.capture({
event: 'secrets deleted',
event: "secrets deleted",
distinctId: req.user.email,
properties: {
numberOfSecrets: numSecretsDeleted,
environment: environmentName,
workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
userAgent: req.headers?.['user-agent']
channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.["user-agent"]
}
});
}
res.status(200).send()
}
res.status(200).send();
};
/**
* Delete secret with id [secretId]
@@ -184,27 +188,27 @@ export const deleteSecrets = async (req: Request, res: Response) => {
* @param res
*/
export const deleteSecret = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient();
await Secret.findByIdAndDelete(req._secret._id)
const postHogClient = await TelemetryService.getPostHogClient();
await Secret.findByIdAndDelete(req._secret._id);
if (postHogClient) {
postHogClient.capture({
event: 'secrets deleted',
event: "secrets deleted",
distinctId: req.user.email,
properties: {
numberOfSecrets: 1,
workspaceId: req._secret.workspace.toString(),
environment: req._secret.environment,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
userAgent: req.headers?.['user-agent']
channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.["user-agent"]
}
});
}
res.status(200).send({
secret: req._secret
})
}
});
};
/**
* Update secrets for workspace with id [workspaceId] and environment [environment]
@@ -213,18 +217,17 @@ export const deleteSecret = async (req: Request, res: Response) => {
* @returns
*/
export const updateSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params
const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params;
const secretsModificationsRequested: ModifySecretRequestBody[] = req.body.secrets;
const [secretIdsUserCanModifyError, secretIdsUserCanModify] = await to(Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
if (secretIdsUserCanModifyError) {
throw InternalServerError({ message: "Unable to fetch secrets you own" })
}
const secretIdsUserCanModify = await Secret.find({ workspace: workspaceId, environment: environmentName }, { _id: 1 });
const secretsUserCanModifySet: Set<string> = new Set(secretIdsUserCanModify.map(objectId => objectId._id.toString()));
const updateOperationsToPerform: any = []
const secretsUserCanModifySet: Set<string> = new Set(
secretIdsUserCanModify.map((objectId) => objectId._id.toString())
);
const updateOperationsToPerform: any = [];
secretsModificationsRequested.forEach(userModifiedSecret => {
secretsModificationsRequested.forEach((userModifiedSecret) => {
if (secretsUserCanModifySet.has(userModifiedSecret._id.toString())) {
const sanitizedSecret: SanitizedSecretModify = {
secretKeyCiphertext: userModifiedSecret.secretKeyCiphertext,
@@ -238,41 +241,41 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretCommentCiphertext: userModifiedSecret.secretCommentCiphertext,
secretCommentIV: userModifiedSecret.secretCommentIV,
secretCommentTag: userModifiedSecret.secretCommentTag,
secretCommentHash: userModifiedSecret.secretCommentHash,
}
secretCommentHash: userModifiedSecret.secretCommentHash
};
const updateOperation = { updateOne: { filter: { _id: userModifiedSecret._id, workspace: workspaceId }, update: { $inc: { version: 1 }, $set: sanitizedSecret } } }
updateOperationsToPerform.push(updateOperation)
const updateOperation = {
updateOne: {
filter: { _id: userModifiedSecret._id, workspace: workspaceId },
update: { $inc: { version: 1 }, $set: sanitizedSecret }
}
};
updateOperationsToPerform.push(updateOperation);
} else {
throw UnauthorizedRequestError({ message: "You do not have permission to modify one or more of the requested secrets" })
throw UnauthorizedRequestError({
message: "You do not have permission to modify one or more of the requested secrets"
});
}
})
});
const [bulkModificationInfoError, bulkModificationInfo] = await to(Secret.bulkWrite(updateOperationsToPerform).then())
if (bulkModificationInfoError) {
if (bulkModificationInfoError instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: bulkModificationInfoError.stack })
}
throw InternalServerError()
}
await Secret.bulkWrite(updateOperationsToPerform);
if (postHogClient) {
postHogClient.capture({
event: 'secrets modified',
event: "secrets modified",
distinctId: req.user.email,
properties: {
numberOfSecrets: (secretsModificationsRequested ?? []).length,
environment: environmentName,
workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
userAgent: req.headers?.['user-agent']
channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.["user-agent"]
}
});
}
return res.status(200).send()
}
return res.status(200).send();
};
/**
* Update a secret within workspace with id [workspaceId] and environment [environment]
@@ -281,14 +284,11 @@ export const updateSecrets = async (req: Request, res: Response) => {
* @returns
*/
export const updateSecret = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params
const postHogClient = await TelemetryService.getPostHogClient();
const { workspaceId, environmentName } = req.params;
const secretModificationsRequested: ModifySecretRequestBody = req.body.secret;
const [secretIdUserCanModifyError, secretIdUserCanModify] = await to(Secret.findOne({ workspace: workspaceId, environment: environmentName }, { _id: 1 }).then())
if (secretIdUserCanModifyError && !secretIdUserCanModify) {
throw BadRequestError()
}
await Secret.findOne({ workspace: workspaceId, environment: environmentName }, { _id: 1 });
const sanitizedSecret: SanitizedSecretModify = {
secretKeyCiphertext: secretModificationsRequested.secretKeyCiphertext,
@@ -302,30 +302,40 @@ export const updateSecret = async (req: Request, res: Response) => {
secretCommentCiphertext: secretModificationsRequested.secretCommentCiphertext,
secretCommentIV: secretModificationsRequested.secretCommentIV,
secretCommentTag: secretModificationsRequested.secretCommentTag,
secretCommentHash: secretModificationsRequested.secretCommentHash,
secretCommentHash: secretModificationsRequested.secretCommentHash
};
const singleModificationUpdate = await Secret.updateOne(
{ _id: secretModificationsRequested._id, workspace: workspaceId },
{ $inc: { version: 1 }, $set: sanitizedSecret }
)
.catch((error) => {
if (error instanceof ValidationError) {
throw RouteValidationError({
message: "Unable to apply modifications, please try again",
stack: error.stack
});
}
const [error, singleModificationUpdate] = await to(Secret.updateOne({ _id: secretModificationsRequested._id, workspace: workspaceId }, { $inc: { version: 1 }, $set: sanitizedSecret }).then())
if (error instanceof ValidationError) {
throw RouteValidationError({ message: "Unable to apply modifications, please try again", stack: error.stack })
}
throw error;
});
if (postHogClient) {
postHogClient.capture({
event: 'secrets modified',
event: "secrets modified",
distinctId: req.user.email,
properties: {
numberOfSecrets: 1,
environment: environmentName,
workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
userAgent: req.headers?.['user-agent']
channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.["user-agent"]
}
});
}
return res.status(200).send(singleModificationUpdate)
}
return res.status(200).send(singleModificationUpdate);
};
/**
* Return secrets for workspace with id [workspaceId], environment [environment] and user
@@ -335,51 +345,54 @@ export const updateSecret = async (req: Request, res: Response) => {
* @returns
*/
export const getSecrets = async (req: Request, res: Response) => {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
const { environment } = req.query;
const { workspaceId } = req.params;
let userId: Types.ObjectId | undefined = undefined // used for getting personal secrets for user
let userEmail: Types.ObjectId | undefined = undefined // used for posthog
let userId: Types.ObjectId | undefined = undefined; // used for getting personal secrets for user
let userEmail: string | undefined = undefined; // used for posthog
if (req.user) {
userId = req.user._id;
userEmail = req.user.email;
}
if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
userEmail = req.serviceTokenData.user.email;
userId = req.serviceTokenData.user;
const user = await User.findById(req.serviceTokenData.user, "email");
if (!user) throw AccountNotFoundError();
userEmail = user.email;
}
const [err, secrets] = await to(Secret.find(
{
const secrets = await Secret.find({
workspace: workspaceId,
environment,
$or: [{ user: userId }, { user: { $exists: false } }],
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
}
).then())
if (err) {
throw RouteValidationError({ message: "Failed to get secrets, please try again", stack: err.stack })
}
})
.catch((err) => {
throw RouteValidationError({
message: "Failed to get secrets, please try again",
stack: err.stack
});
})
if (postHogClient) {
postHogClient.capture({
event: 'secrets pulled',
event: "secrets pulled",
distinctId: userEmail,
properties: {
numberOfSecrets: (secrets ?? []).length,
environment,
workspaceId,
channel: req.headers?.['user-agent']?.toLowerCase().includes('mozilla') ? 'web' : 'cli',
userAgent: req.headers?.['user-agent']
channel: req.headers?.["user-agent"]?.toLowerCase().includes("mozilla") ? "web" : "cli",
userAgent: req.headers?.["user-agent"]
}
});
}
return res.json(secrets)
}
return res.json(secrets);
};
/**
* Return secret with id [secretId]
@@ -405,4 +418,4 @@ export const getSecret = async (req: Request, res: Response) => {
return res.status(200).send({
secret: req._secret
});
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,18 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import { Request, Response } from "express";
import { Types } from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt";
import {
ServiceAccount,
ServiceAccountKey,
ServiceAccountOrganizationPermission,
ServiceAccountWorkspacePermission
} from '../../models';
ServiceAccountWorkspacePermission,
} from "../../models";
import {
CreateServiceAccountDto
} from '../../interfaces/serviceAccounts/dto';
import { BadRequestError, ServiceAccountNotFoundError } from '../../utils/errors';
import { getSaltRounds } from '../../config';
CreateServiceAccountDto,
} from "../../interfaces/serviceAccounts/dto";
import { BadRequestError, ServiceAccountNotFoundError } from "../../utils/errors";
import { getSaltRounds } from "../../config";
/**
* Return service account tied to the request (service account) client
@@ -23,11 +23,11 @@ export const getCurrentServiceAccount = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findById(req.serviceAccount._id);
if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' });
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
}
return res.status(200).send({
serviceAccount
serviceAccount,
});
}
@@ -42,11 +42,11 @@ export const getServiceAccountById = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: 'Failed to find service account' });
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
}
return res.status(200).send({
serviceAccount
serviceAccount,
});
}
@@ -71,8 +71,8 @@ export const createServiceAccount = async (req: Request, res: Response) => {
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}
const secret = crypto.randomBytes(16).toString('base64');
const secretHash = await bcrypt.hash(secret, getSaltRounds());
const secret = crypto.randomBytes(16).toString("base64");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
// create service account
const serviceAccount = await new ServiceAccount({
@@ -82,7 +82,7 @@ export const createServiceAccount = async (req: Request, res: Response) => {
publicKey,
lastUsed: new Date(),
expiresAt,
secretHash
secretHash,
}).save();
const serviceAccountObj = serviceAccount.toObject();
@@ -91,14 +91,14 @@ export const createServiceAccount = async (req: Request, res: Response) => {
// provision default org-level permission for service account
await new ServiceAccountOrganizationPermission({
serviceAccount: serviceAccount._id
serviceAccount: serviceAccount._id,
}).save();
const secretId = Buffer.from(serviceAccount._id.toString(), 'hex').toString('base64');
const secretId = Buffer.from(serviceAccount._id.toString(), "hex").toString("base64");
return res.status(200).send({
serviceAccountAccessKey: `sa.${secretId}.${secret}`,
serviceAccount: serviceAccountObj
serviceAccount: serviceAccountObj,
});
}
@@ -114,18 +114,18 @@ export const changeServiceAccountName = async (req: Request, res: Response) => {
const serviceAccount = await ServiceAccount.findOneAndUpdate(
{
_id: new Types.ObjectId(serviceAccountId)
_id: new Types.ObjectId(serviceAccountId),
},
{
name
name,
},
{
new: true
new: true,
}
);
return res.status(200).send({
serviceAccount
serviceAccount,
});
}
@@ -140,7 +140,7 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
const {
workspaceId,
encryptedKey,
nonce
nonce,
} = req.body;
const serviceAccountKey = await new ServiceAccountKey({
@@ -148,7 +148,7 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
nonce,
sender: req.user._id,
serviceAccount: req.serviceAccount._d,
workspace: new Types.ObjectId(workspaceId)
workspace: new Types.ObjectId(workspaceId),
}).save();
return serviceAccountKey;
@@ -161,11 +161,11 @@ export const addServiceAccountKey = async (req: Request, res: Response) => {
*/
export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => {
const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({
serviceAccount: req.serviceAccount._id
}).populate('workspace');
serviceAccount: req.serviceAccount._id,
}).populate("workspace");
return res.status(200).send({
serviceAccountWorkspacePermissions
serviceAccountWorkspacePermissions,
});
}
@@ -182,34 +182,34 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
read = false,
write = false,
encryptedKey,
nonce
nonce,
} = req.body;
if (!req.membership.workspace.environments.some((e: { name: string; slug: string }) => e.slug === environment)) {
return res.status(400).send({
message: 'Failed to validate workspace environment'
message: "Failed to validate workspace environment",
});
}
const existingPermission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId),
environment
environment,
});
if (existingPermission) throw BadRequestError({ message: 'Failed to add workspace permission to service account due to already-existing ' });
if (existingPermission) throw BadRequestError({ message: "Failed to add workspace permission to service account due to already-existing " });
const serviceAccountWorkspacePermission = await new ServiceAccountWorkspacePermission({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId),
environment,
read,
write
write,
}).save();
const existingServiceAccountKey = await ServiceAccountKey.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId)
workspace: new Types.ObjectId(workspaceId),
});
if (!existingServiceAccountKey) {
@@ -218,12 +218,12 @@ export const addServiceAccountWorkspacePermission = async (req: Request, res: Re
nonce,
sender: req.user._id,
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId)
workspace: new Types.ObjectId(workspaceId),
}).save();
}
return res.status(200).send({
serviceAccountWorkspacePermission
serviceAccountWorkspacePermission,
});
}
@@ -240,19 +240,19 @@ export const deleteServiceAccountWorkspacePermission = async (req: Request, res:
const { serviceAccount, workspace } = serviceAccountWorkspacePermission;
const count = await ServiceAccountWorkspacePermission.countDocuments({
serviceAccount,
workspace
workspace,
});
if (count === 0) {
await ServiceAccountKey.findOneAndDelete({
serviceAccount,
workspace
workspace,
});
}
}
return res.status(200).send({
serviceAccountWorkspacePermission
serviceAccountWorkspacePermission,
});
}
@@ -269,20 +269,20 @@ export const deleteServiceAccount = async (req: Request, res: Response) => {
if (serviceAccount) {
await ServiceAccountKey.deleteMany({
serviceAccount: serviceAccount._id
serviceAccount: serviceAccount._id,
});
await ServiceAccountOrganizationPermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId)
serviceAccount: new Types.ObjectId(serviceAccountId),
});
await ServiceAccountWorkspacePermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId)
serviceAccount: new Types.ObjectId(serviceAccountId),
});
}
return res.status(200).send({
serviceAccount
serviceAccount,
});
}
@@ -297,10 +297,10 @@ export const getServiceAccountKeys = async (req: Request, res: Response) => {
const serviceAccountKeys = await ServiceAccountKey.find({
serviceAccount: req.serviceAccount._id,
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {})
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {}),
});
return res.status(200).send({
serviceAccountKeys
serviceAccountKeys,
});
}

View File

@@ -1,19 +1,10 @@
import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
User,
ServiceAccount,
ServiceTokenData
} from '../../models';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
import {
PERMISSION_READ_SECRETS,
AUTH_MODE_JWT,
AUTH_MODE_SERVICE_ACCOUNT
} from '../../variables';
import { getSaltRounds } from '../../config';
import { Request, Response } from "express";
import crypto from "crypto";
import bcrypt from "bcrypt";
import { ServiceAccount, ServiceTokenData, User } from "../../models";
import { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT } from "../../variables";
import { getSaltRounds } from "../../config";
import { BadRequestError } from "../../utils/errors";
/**
* Return service token data associated with service token on request
@@ -48,8 +39,18 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
}
*/
return res.status(200).json(req.serviceTokenData);
}
if (!(req.authData.authPayload instanceof ServiceTokenData))
throw BadRequestError({
message: "Failed accepted client validation for service token data"
});
const serviceTokenData = await ServiceTokenData.findById(req.authData.authPayload._id)
.select("+encryptedKey +iv +tag")
.populate("user")
.lean();
return res.status(200).json(serviceTokenData);
};
/**
* Create new service token data for workspace with id [workspaceId] and
@@ -61,23 +62,14 @@ export const getServiceTokenData = async (req: Request, res: Response) => {
export const createServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData;
const {
name,
workspaceId,
environment,
encryptedKey,
iv,
tag,
expiresIn,
permissions
} = req.body;
const { name, workspaceId, encryptedKey, iv, tag, expiresIn, permissions, scopes } = req.body;
const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, getSaltRounds());
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
let expiresAt;
if (!!expiresIn) {
expiresAt = new Date()
if (expiresIn) {
expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}
@@ -87,16 +79,19 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
user = req.authData.authPayload._id;
}
if (req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && req.authData.authPayload instanceof ServiceAccount) {
if (
req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
req.authData.authPayload instanceof ServiceAccount
) {
serviceAccount = req.authData.authPayload._id;
}
serviceTokenData = await new ServiceTokenData({
name,
workspace: workspaceId,
environment,
user,
serviceAccount,
scopes,
lastUsed: new Date(),
expiresAt,
secretHash,
@@ -109,7 +104,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
// return service token data without sensitive data
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
if (!serviceTokenData) throw new Error('Failed to find service token data');
if (!serviceTokenData) throw new Error("Failed to find service token data");
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
@@ -117,7 +112,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
serviceToken,
serviceTokenData
});
}
};
/**
* Delete service token data with id [serviceTokenDataId].
@@ -133,4 +128,4 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
return res.status(200).send({
serviceTokenData
});
}
};

View File

@@ -1,14 +1,14 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { User, MembershipOrg } from '../../models';
import { completeAccount } from '../../helpers/user';
import { Request, Response } from "express";
import { MembershipOrg, User } from "../../models";
import { completeAccount } from "../../helpers/user";
import {
initializeDefaultOrg
} from '../../helpers/signup';
import { issueAuthTokens } from '../../helpers/auth';
import { INVITED, ACCEPTED } from '../../variables';
import request from '../../config/request';
import { getLoopsApiKey, getHttpsEnabled } from '../../config';
initializeDefaultOrg,
} from "../../helpers/signup";
import { issueAuthTokens } from "../../helpers/auth";
import { ACCEPTED, INVITED } from "../../variables";
import { standardRequest } from "../../config/request";
import { getHttpsEnabled, getLoopsApiKey } from "../../config";
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
/**
* Complete setting up user by adding their personal and auth information as part of the
@@ -18,8 +18,7 @@ import { getLoopsApiKey, getHttpsEnabled } from '../../config';
* @returns
*/
export const completeAccountSignup = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
let user;
const {
email,
firstName,
@@ -33,7 +32,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
encryptedPrivateKeyTag,
salt,
verifier,
organizationName
organizationName,
}: {
email: string;
firstName: string;
@@ -57,7 +56,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
error: "Failed to complete account for complete user",
});
}
@@ -75,16 +74,29 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
verifier,
});
if (!user)
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user
user,
});
// update organization membership statuses that are
// invited to completed with user attached
const membershipsToUpdate = await MembershipOrg.find({
inviteEmail: email,
status: INVITED,
});
membershipsToUpdate.forEach(async (membership) => {
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString(),
});
});
// update organization membership statuses that are
@@ -92,55 +104,50 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
status: INVITED,
},
{
user,
status: ACCEPTED
status: ACCEPTED,
}
);
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id.toString()
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
token = tokens.token;
const token = tokens.token;
// sending a welcome email to new users
if (getLoopsApiKey()) {
await request.post("https://app.loops.so/api/v1/events/send", {
if (await getLoopsApiKey()) {
await standardRequest.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName
"lastName": lastName,
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + getLoopsApiKey()
"Authorization": "Bearer " + (await getLoopsApiKey()),
},
});
}
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: getHttpsEnabled()
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
message: "Successfully set up account",
user,
token
token,
});
};
@@ -152,8 +159,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
* @returns
*/
export const completeAccountInvite = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
let user;
const {
email,
firstName,
@@ -166,7 +172,7 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
verifier,
} = req.body;
// get user
@@ -176,16 +182,16 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: 'Failed to complete account for complete user'
error: "Failed to complete account for complete user",
});
}
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
status: INVITED,
});
if (!membershipOrg) throw new Error('Failed to find invitations for email');
if (!membershipOrg) throw new Error("Failed to find invitations for email");
// complete setting up user's account
user = await completeAccount({
@@ -201,50 +207,56 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
verifier,
});
if (!user)
throw new Error('Failed to complete account for non-existent user');
throw new Error("Failed to complete account for non-existent user");
// update organization membership statuses that are
// invited to completed with user attached
const membershipsToUpdate = await MembershipOrg.find({
inviteEmail: email,
status: INVITED,
});
membershipsToUpdate.forEach(async (membership) => {
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString(),
});
});
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
status: INVITED,
},
{
user,
status: ACCEPTED
status: ACCEPTED,
}
);
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id.toString()
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
token = tokens.token;
const token = tokens.token;
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: getHttpsEnabled()
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}
return res.status(200).send({
message: 'Successfully set up account',
message: "Successfully set up account",
user,
token
token,
});
};

View File

@@ -1,45 +1,31 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import {
Membership, Secret,
} from '../../models';
import Tag, { ITag } from '../../models/tag';
import { Builder } from "builder-pattern"
import to from 'await-to-js';
import { BadRequestError, UnauthorizedRequestError } from '../../utils/errors';
import { MongoError } from 'mongodb';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Membership, Secret } from "../../models";
import Tag from "../../models/tag";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
export const createWorkspaceTag = async (req: Request, res: Response) => {
const { workspaceId } = req.params
const { name, slug } = req.body
const sanitizedTagToCreate = Builder<ITag>()
.name(name)
.workspace(new Types.ObjectId(workspaceId))
.slug(slug)
.user(new Types.ObjectId(req.user._id))
.build();
const { workspaceId } = req.params;
const { name, slug } = req.body;
const [err, createdTag] = await to(Tag.create(sanitizedTagToCreate))
const tagToCreate = {
name,
workspace: new Types.ObjectId(workspaceId),
slug,
user: new Types.ObjectId(req.user._id),
};
if (err) {
if ((err as MongoError).code === 11000) {
throw BadRequestError({ message: "Tags must be unique in a workspace" })
}
const createdTag = await new Tag(tagToCreate).save();
throw err
}
res.json(createdTag)
}
res.json(createdTag);
};
export const deleteWorkspaceTag = async (req: Request, res: Response) => {
const { tagId } = req.params
const { tagId } = req.params;
const tagFromDB = await Tag.findById(tagId)
const tagFromDB = await Tag.findById(tagId);
if (!tagFromDB) {
throw BadRequestError()
throw BadRequestError();
}
// can only delete if the request user is one that belongs to the same workspace as the tag
@@ -49,24 +35,25 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
});
if (!membership) {
UnauthorizedRequestError({ message: 'Failed to validate membership' });
UnauthorizedRequestError({ message: "Failed to validate membership" });
}
const result = await Tag.findByIdAndDelete(tagId);
// remove the tag from secrets
await Secret.updateMany(
{ tags: { $in: [tagId] } },
{ $pull: { tags: tagId } }
);
await Secret.updateMany({ tags: { $in: [tagId] } }, { $pull: { tags: tagId } });
res.json(result);
}
};
export const getWorkspaceTags = async (req: Request, res: Response) => {
const { workspaceId } = req.params
const workspaceTags = await Tag.find({ workspace: workspaceId })
const { workspaceId } = req.params;
const workspaceTags = await Tag.find({
workspace: new Types.ObjectId(workspaceId)
});
return res.json({
workspaceTags
})
}
});
};

View File

@@ -1,9 +1,15 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Request, Response } from "express";
import { Types } from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt";
import {
User,
MembershipOrg
} from '../../models';
APIKeyData,
AuthProvider,
MembershipOrg,
TokenVersion,
User
} from "../../models";
import { getSaltRounds } from "../../config";
/**
* Return the current user.
@@ -37,21 +43,12 @@ export const getMe = async (req: Request, res: Response) => {
}
}
*/
let user;
try {
user = await User
const user = await User
.findById(req.user._id)
.select('+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get current user'
});
}
.select("+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag");
return res.status(200).send({
user
user,
});
}
@@ -64,29 +61,81 @@ export const getMe = async (req: Request, res: Response) => {
* @returns
*/
export const updateMyMfaEnabled = async (req: Request, res: Response) => {
let user;
try {
const { isMfaEnabled }: { isMfaEnabled: boolean } = req.body;
req.user.isMfaEnabled = isMfaEnabled;
if (isMfaEnabled) {
// TODO: adapt this route/controller
// to work for different forms of MFA
req.user.mfaMethods = ['email'];
req.user.mfaMethods = ["email"];
} else {
req.user.mfaMethods = [];
}
await req.user.save();
user = req.user;
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to update current user's MFA status"
const user = req.user;
return res.status(200).send({
user,
});
}
/**
* Update name of the current user to [firstName, lastName].
* @param req
* @param res
* @returns
*/
export const updateName = async (req: Request, res: Response) => {
const {
firstName,
lastName
}: {
firstName: string;
lastName: string;
} = req.body;
const user = await User.findByIdAndUpdate(
req.user._id.toString(),
{
firstName,
lastName: lastName ?? ""
},
{
new: true
}
);
return res.status(200).send({
user,
});
}
/**
* Update auth provider of the current user to [authProvider]
* @param req
* @param res
* @returns
*/
export const updateAuthProvider = async (req: Request, res: Response) => {
const {
authProvider
} = req.body;
if (req.user?.authProvider === AuthProvider.OKTA_SAML) return res.status(400).send({
message: "Failed to update user authentication method because SAML SSO is enforced"
});
const user = await User.findByIdAndUpdate(
req.user._id.toString(),
{
authProvider
},
{
new: true
}
);
return res.status(200).send({
user
@@ -126,22 +175,116 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
}
}
*/
let organizations;
try {
organizations = (
const organizations = (
await MembershipOrg.find({
user: req.user._id
}).populate('organization')
user: req.user._id,
}).populate("organization")
).map((m) => m.organization);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get current user's organizations"
});
}
return res.status(200).send({
organizations
organizations,
});
}
/**
* Return API keys belonging to current user.
* @param req
* @param res
* @returns
*/
export const getMyAPIKeys = async (req: Request, res: Response) => {
const apiKeyData = await APIKeyData.find({
user: req.user._id,
});
return res.status(200).send(apiKeyData);
}
/**
* Create new API key for current user.
* @param req
* @param res
* @returns
*/
export const createAPIKey = async (req: Request, res: Response) => {
const { name, expiresIn } = req.body;
const secret = crypto.randomBytes(16).toString("hex");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
let apiKeyData = await new APIKeyData({
name,
lastUsed: new Date(),
expiresAt,
user: req.user._id,
secretHash,
}).save();
// return api key data without sensitive data
apiKeyData = (await APIKeyData.findById(apiKeyData._id)) as any;
if (!apiKeyData) throw new Error("Failed to find API key data");
const apiKey = `ak.${apiKeyData._id.toString()}.${secret}`;
return res.status(200).send({
apiKey,
apiKeyData,
});
}
/**
* Delete API key with id [apiKeyDataId] belonging to current user
* @param req
* @param res
*/
export const deleteAPIKey = async (req: Request, res: Response) => {
const { apiKeyDataId } = req.params;
const apiKeyData = await APIKeyData.findOneAndDelete({
_id: new Types.ObjectId(apiKeyDataId),
user: req.user._id
});
return res.status(200).send({
apiKeyData
});
}
/**
* Return active sessions (TokenVersion) belonging to user
* @param req
* @param res
* @returns
*/
export const getMySessions = async (req: Request, res: Response) => {
const tokenVersions = await TokenVersion.find({
user: req.user._id
});
return res.status(200).send(tokenVersions);
}
/**
* Revoke all active sessions belong to user
* @param req
* @param res
* @returns
*/
export const deleteMySessions = async (req: Request, res: Response) => {
await TokenVersion.updateMany({
user: req.user._id,
}, {
$inc: {
refreshVersion: 1,
accessVersion: 1,
},
});
return res.status(200).send({
message: "Successfully revoked all sessions"
});
}

View File

@@ -1,26 +1,14 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Key, Membership, ServiceTokenData, Workspace } from "../../models";
import {
Workspace,
Secret,
Membership,
MembershipOrg,
Integration,
IntegrationAuth,
Key,
IUser,
ServiceToken,
ServiceTokenData
} from '../../models';
import {
v2PushSecrets as push,
pullSecrets as pull,
v2PushSecrets as push,
reformatPullSecrets
} from '../../helpers/secret';
import { pushKeys } from '../../helpers/key';
import { TelemetryService, EventService } from '../../services';
import { eventPushSecrets } from '../../events';
} from "../../helpers/secret";
import { pushKeys } from "../../helpers/key";
import { EventService, TelemetryService } from "../../services";
import { eventPushSecrets } from "../../events";
interface V2PushSecret {
type: string; // personal or shared
@@ -47,8 +35,7 @@ interface V2PushSecret {
*/
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId]
try {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
let { secrets }: { secrets: V2PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
const { workspaceId } = req.params;
@@ -56,12 +43,12 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
throw new Error("Failed to validate environment");
}
// sanitize secrets
secrets = secrets.filter(
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
(s: V2PushSecret) => s.secretKeyCiphertext !== "" && s.secretValueCiphertext !== ""
);
await push({
@@ -69,8 +56,8 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
workspaceId,
environment,
secrets,
channel: channel ? channel : 'cli',
ipAddress: req.ip
channel: channel ? channel : "cli",
ipAddress: req.realIP
});
await pushKeys({
@@ -81,13 +68,13 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
if (postHogClient) {
postHogClient.capture({
event: 'secrets pushed',
event: "secrets pushed",
distinctId: req.user.email,
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
channel: channel ? channel : "cli"
}
});
}
@@ -95,20 +82,14 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath: "/"
})
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to upload workspace secrets'
});
}
return res.status(200).send({
message: 'Successfully uploaded workspace secrets'
message: "Successfully uploaded workspace secrets"
});
};
@@ -121,8 +102,7 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
*/
export const pullSecrets = async (req: Request, res: Response) => {
let secrets;
try {
const postHogClient = TelemetryService.getPostHogClient();
const postHogClient = await TelemetryService.getPostHogClient();
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
@@ -131,23 +111,23 @@ export const pullSecrets = async (req: Request, res: Response) => {
if (req.user) {
userId = req.user._id.toString();
} else if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
userId = req.serviceTokenData.user.toString();
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
throw new Error("Failed to validate environment");
}
secrets = await pull({
userId,
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.ip
channel: channel ? channel : "cli",
ipAddress: req.realIP
});
if (channel !== 'cli') {
if (channel !== "cli") {
secrets = reformatPullSecrets({ secrets });
}
@@ -155,22 +135,15 @@ export const pullSecrets = async (req: Request, res: Response) => {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: 'secrets pulled',
event: "secrets pulled",
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
channel: channel ? channel : "cli"
}
});
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to pull workspace secrets'
});
}
return res.status(200).send({
secrets
@@ -206,52 +179,28 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
}
}
*/
let key;
try {
const { workspaceId } = req.params;
key = await Key.findOne({
const key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
}).populate('sender', '+publicKey');
}).populate("sender", "+publicKey");
if (!key) throw new Error('Failed to find workspace key');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get workspace key'
});
}
if (!key) throw new Error("Failed to find workspace key");
return res.status(200).json(key);
}
export const getWorkspaceServiceTokenData = async (
req: Request,
res: Response
) => {
let serviceTokenData;
try {
};
export const getWorkspaceServiceTokenData = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
serviceTokenData = await ServiceTokenData
.find({
const serviceTokenData = await ServiceTokenData.find({
workspace: workspaceId
})
.select('+encryptedKey +iv +tag');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get workspace service token data'
});
}
}).select("+encryptedKey +iv +tag");
return res.status(200).send({
serviceTokenData
});
}
};
/**
* Return memberships for workspace with id [workspaceId]
@@ -293,25 +242,16 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
}
}
*/
let memberships;
try {
const { workspaceId } = req.params;
memberships = await Membership.find({
const memberships = await Membership.find({
workspace: workspaceId
}).populate('user', '+publicKey');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get workspace memberships'
});
}
}).populate("user", "+publicKey");
return res.status(200).send({
memberships
});
}
};
/**
* Update role of membership with id [membershipId] to role [role]
@@ -373,33 +313,23 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
}
}
*/
let membership;
try {
const {
membershipId
} = req.params;
const { membershipId } = req.params;
const { role } = req.body;
membership = await Membership.findByIdAndUpdate(
const membership = await Membership.findByIdAndUpdate(
membershipId,
{
role
}, {
},
{
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update workspace membership'
});
}
return res.status(200).send({
membership
});
}
};
/**
* Delete workspace membership with id [membershipId]
@@ -444,32 +374,21 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
}
}
*/
let membership;
try {
const {
membershipId
} = req.params;
const { membershipId } = req.params;
membership = await Membership.findByIdAndDelete(membershipId);
const membership = await Membership.findByIdAndDelete(membershipId);
if (!membership) throw new Error('Failed to delete workspace membership');
if (!membership) throw new Error("Failed to delete workspace membership");
await Key.deleteMany({
receiver: membership.user,
workspace: membership.workspace
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete workspace membership'
});
}
return res.status(200).send({
membership
});
}
};
/**
* Change autoCapitilzation Rule of workspace
@@ -478,12 +397,10 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
* @returns
*/
export const toggleAutoCapitalization = async (req: Request, res: Response) => {
let workspace;
try {
const { workspaceId } = req.params;
const { autoCapitalization } = req.body;
workspace = await Workspace.findOneAndUpdate(
const workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId
},
@@ -494,16 +411,9 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to change autoCapitalization setting'
});
}
return res.status(200).send({
message: 'Successfully changed autoCapitalization setting',
message: "Successfully changed autoCapitalization setting",
workspace
});
};

View File

@@ -0,0 +1,264 @@
/* eslint-disable @typescript-eslint/no-var-requires */
import { Request, Response } from "express";
import jwt from "jsonwebtoken";
import * as Sentry from "@sentry/node";
import * as bigintConversion from "bigint-conversion";
const jsrp = require("jsrp");
import { LoginSRPDetail, User } from "../../models";
import { createToken, issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors";
import {
ACTION_LOGIN,
TOKEN_EMAIL_MFA,
} from "../../variables";
import { getChannelFromUserAgent } from "../../utils/posthog"; // TODO: move this
import {
getHttpsEnabled,
getJwtMfaLifetime,
getJwtMfaSecret,
} from "../../config";
import { AuthProvider } from "../../models/user";
declare module "jsonwebtoken" {
export interface ProviderAuthJwtPayload extends jwt.JwtPayload {
userId: string;
email: string;
authProvider: AuthProvider;
isUserCompleted: boolean,
}
}
/**
* Log in user step 1: Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
* @param req
* @param res
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
try {
const {
email,
providerAuthToken,
clientPublicKey,
}: {
email: string;
clientPublicKey: string,
providerAuthToken?: string;
} = req.body;
const user = await User.findOne({
email,
}).select("+salt +verifier");
if (!user) throw new Error("Failed to find user");
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
await validateProviderAuthToken({
email,
user,
providerAuthToken,
})
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({
email: email,
}, {
email,
userId: user.id,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false });
return res.status(200).send({
serverPublicKey,
salt: user.salt,
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to start authentication process",
});
}
};
/**
* Log in user step 2: complete step 2 of SRP protocol and return token and their (encrypted)
* private key
* @param req
* @param res
* @returns
*/
export const login2 = async (req: Request, res: Response) => {
try {
if (!req.headers["user-agent"]) throw InternalServerError({ message: "User-Agent header is required" });
const { email, clientProof, providerAuthToken } = req.body;
const user = await User.findOne({
email,
}).select("+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices");
if (!user) throw new Error("Failed to find user");
if (user.authProvider && user.authProvider !== AuthProvider.EMAIL) {
await validateProviderAuthToken({
email,
user,
providerAuthToken,
})
}
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
if (!loginSRPDetail) {
return BadRequestError(Error("Failed to find login details for SRP"))
}
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetail.serverBInt,
},
async () => {
server.setClientPublicKey(loginSRPDetail.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
if (user.isMfaEnabled) {
// case: user has MFA enabled
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString(),
},
expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret(),
});
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email,
});
// send MFA code [code] to [email]
await sendMail({
template: "emailMfa.handlebars",
subjectLine: "Infisical MFA code",
recipients: [user.email],
substitutions: {
code,
},
});
return res.status(200).send({
mfaEnabled: true,
token,
});
}
await checkUserDevice({
user,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
// case: user does not have MFA enablgged
// return (access) token in response
interface ResponseData {
mfaEnabled: boolean;
encryptionVersion: any;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey?: string;
encryptedPrivateKey?: string;
iv?: string;
tag?: string;
}
const response: ResponseData = {
mfaEnabled: false,
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
}
if (
user?.protectedKey &&
user?.protectedKeyIV &&
user?.protectedKeyTag
) {
response.protectedKey = user.protectedKey;
response.protectedKeyIV = user.protectedKeyIV
response.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id,
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP,
});
return res.status(200).send(response);
}
return res.status(400).send({
message: "Failed to authenticate. Try again?",
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to authenticate. Try again?",
});
}
};

View File

@@ -0,0 +1,11 @@
import * as secretsController from "./secretsController";
import * as workspacesController from "./workspacesController";
import * as authController from "./authController";
import * as signupController from "./signupController";
export {
authController,
secretsController,
signupController,
workspacesController,
}

View File

@@ -0,0 +1,477 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { EventService, SecretService } from "../../services";
import { eventPushSecrets } from "../../events";
import { BotService } from "../../services";
import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
import { getAllImportedSecrets } from "../../services/SecretImportService";
import Folder from "../../models/folder";
import { getFolderByPath } from "../../services/FolderService";
import { BadRequestError } from "../../utils/errors";
import { IServiceTokenData } from "../../models";
import { requireWorkspaceAuth } from "../../middleware";
import { ADMIN, MEMBER, PERMISSION_READ_SECRETS } from "../../variables";
/**
* Return secrets for workspace with id [workspaceId] and environment
* [environment] in plaintext
* @param req
* @param res
*/
export const getSecretsRaw = async (req: Request, res: Response) => {
let workspaceId = req.query.workspaceId as string;
let environment = req.query.environment as string;
let secretPath = req.query.secretPath as string;
const includeImports = req.query.include_imports as string;
// if the service token has single scope, it will get all secrets for that scope by default
const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData;
if (serviceTokenDetails) {
if (
serviceTokenDetails.scopes.length == 1 &&
!containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath)
) {
const scope = serviceTokenDetails.scopes[0];
secretPath = scope.secretPath;
environment = scope.environment;
workspaceId = serviceTokenDetails.workspace.toString();
} else {
requireWorkspaceAuth({
acceptedRoles: [ADMIN, MEMBER],
locationWorkspaceId: "query",
locationEnvironment: "query",
requiredPermissions: [PERMISSION_READ_SECRETS],
requireBlindIndicesEnabled: true,
requireE2EEOff: true
});
}
}
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
authData: req.authData
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
if (includeImports === "true") {
const folders = await Folder.findOne({ workspace: workspaceId, environment });
let folderId = "root";
// if folder exist get it and replace folderid with new one
if (folders) {
const folder = getFolderByPath(folders.nodes, secretPath as string);
if (!folder) {
throw BadRequestError({ message: "Folder not found" });
}
folderId = folder.id;
}
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId);
return res.status(200).send({
secrets: secrets.map((secret) =>
repackageSecretToRaw({
secret,
key
})
),
imports: importedSecrets.map((el) => ({
...el,
secrets: el.secrets.map((secret) => repackageSecretToRaw({ secret, key }))
}))
});
}
return res.status(200).send({
secrets: secrets.map((secret) => {
const rep = repackageSecretToRaw({
secret,
key
});
return rep;
})
});
};
/**
* Return secret with name [secretName] in plaintext
* @param req
* @param res
*/
export const getSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const type = req.query.type as "shared" | "personal" | undefined;
const secret = await SecretService.getSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
secretPath,
authData: req.authData
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
secret: repackageSecretToRaw({
secret,
key
})
});
};
/**
* Create secret with name [secretName] in plaintext
* @param req
* @param res
*/
export const createSecretRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretValue, secretComment, secretPath = "/" } = req.body;
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretName,
key
});
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretValue,
key
});
const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretComment,
key
});
const secret = await SecretService.createSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
secretKeyCiphertext: secretKeyEncrypted.ciphertext,
secretKeyIV: secretKeyEncrypted.iv,
secretKeyTag: secretKeyEncrypted.tag,
secretValueCiphertext: secretValueEncrypted.ciphertext,
secretValueIV: secretValueEncrypted.iv,
secretValueTag: secretValueEncrypted.tag,
secretPath,
secretCommentCiphertext: secretCommentEncrypted.ciphertext,
secretCommentIV: secretCommentEncrypted.iv,
secretCommentTag: secretCommentEncrypted.tag
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath
})
});
const secretWithoutBlindIndex = secret.toObject();
delete secretWithoutBlindIndex.secretBlindIndex;
return res.status(200).send({
secret: repackageSecretToRaw({
secret: secretWithoutBlindIndex,
key
})
});
};
/**
* Update secret with name [secretName]
* @param req
* @param res
*/
export const updateSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretValue, secretPath = "/" } = req.body;
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8({
plaintext: secretValue,
key
});
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretValueCiphertext: secretValueEncrypted.ciphertext,
secretValueIV: secretValueEncrypted.iv,
secretValueTag: secretValueEncrypted.tag,
secretPath
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath
})
});
return res.status(200).send({
secret: repackageSecretToRaw({
secret,
key
})
});
};
/**
* Delete secret with name [secretName]
* @param req
* @param res
*/
export const deleteSecretByNameRaw = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretPath = "/" } = req.body;
const { secret } = await SecretService.deleteSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretPath
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath
})
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
secret: repackageSecretToRaw({
secret,
key
})
});
};
/**
* Get secrets for workspace with id [workspaceId] and environment
* [environment]
* @param req
* @param res
*/
export const getSecrets = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const includeImports = req.query.include_imports as string;
const secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
authData: req.authData
});
if (includeImports === "true") {
const folders = await Folder.findOne({ workspace: workspaceId, environment });
let folderId = "root";
// if folder exist get it and replace folderid with new one
if (folders) {
const folder = getFolderByPath(folders.nodes, secretPath as string);
if (!folder) {
throw BadRequestError({ message: "Folder not found" });
}
folderId = folder.id;
}
const importedSecrets = await getAllImportedSecrets(workspaceId, environment, folderId);
return res.status(200).send({
secrets,
imports: importedSecrets
});
}
return res.status(200).send({
secrets
});
};
/**
* Return secret with name [secretName]
* @param req
* @param res
*/
export const getSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const workspaceId = req.query.workspaceId as string;
const environment = req.query.environment as string;
const secretPath = req.query.secretPath as string;
const type = req.query.type as "shared" | "personal" | undefined;
const secret = await SecretService.getSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
secretPath,
authData: req.authData
});
return res.status(200).send({
secret
});
};
/**
* Create secret with name [secretName]
* @param req
* @param res
*/
export const createSecret = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag,
secretPath = "/"
} = req.body;
const secret = await SecretService.createSecret({
secretName,
workspaceId: new Types.ObjectId(workspaceId),
environment,
type,
authData: req.authData,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath,
secretCommentCiphertext,
secretCommentIV,
secretCommentTag
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath
})
});
const secretWithoutBlindIndex = secret.toObject();
delete secretWithoutBlindIndex.secretBlindIndex;
return res.status(200).send({
secret: secretWithoutBlindIndex
});
};
/**
* Update secret with name [secretName]
* @param req
* @param res
*/
export const updateSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const {
workspaceId,
environment,
type,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath = "/"
} = req.body;
const secret = await SecretService.updateSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretPath
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath
})
});
return res.status(200).send({
secret
});
};
/**
* Delete secret with name [secretName]
* @param req
* @param res
*/
export const deleteSecretByName = async (req: Request, res: Response) => {
const { secretName } = req.params;
const { workspaceId, environment, type, secretPath = "/" } = req.body;
const { secret } = await SecretService.deleteSecret({
secretName,
workspaceId,
environment,
type,
authData: req.authData,
secretPath
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath
})
});
return res.status(200).send({
secret
});
};

View File

@@ -0,0 +1,197 @@
import jwt from "jsonwebtoken";
import { Request, Response } from "express";
import * as Sentry from "@sentry/node";
import { MembershipOrg, User } from "../../models";
import { completeAccount } from "../../helpers/user";
import {
initializeDefaultOrg,
} from "../../helpers/signup";
import { issueAuthTokens, validateProviderAuthToken } from "../../helpers/auth";
import { ACCEPTED, INVITED } from "../../variables";
import { standardRequest } from "../../config/request";
import { getHttpsEnabled, getJwtSignupSecret, getLoopsApiKey } from "../../config";
import { BadRequestError } from "../../utils/errors";
import { TelemetryService } from "../../services";
import { AuthProvider } from "../../models";
/**
* Complete setting up user by adding their personal and auth information as part of the
* signup flow
* @param req
* @param res
* @returns
*/
export const completeAccountSignup = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName,
providerAuthToken,
attributionSource,
}: {
email: string;
firstName: string;
lastName: string;
protectedKey: string;
protectedKeyIV: string;
protectedKeyTag: string;
publicKey: string;
encryptedPrivateKey: string;
encryptedPrivateKeyIV: string;
encryptedPrivateKeyTag: string;
salt: string;
verifier: string;
organizationName: string;
providerAuthToken?: string;
attributionSource?: string;
} = req.body;
user = await User.findOne({ email });
if (!user || (user && user?.publicKey)) {
// case 1: user doesn't exist.
// case 2: user has already completed account
return res.status(403).send({
error: "Failed to complete account for complete user",
});
}
if (providerAuthToken) {
await validateProviderAuthToken({
email,
providerAuthToken,
user,
});
} else {
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>req.headers["authorization"]?.split(" ", 2) ?? [null, null]
if (AUTH_TOKEN_TYPE === null) {
throw BadRequestError({ message: "Missing Authorization Header in the request header." });
}
if (AUTH_TOKEN_TYPE.toLowerCase() !== "bearer") {
throw BadRequestError({ message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.` })
}
if (AUTH_TOKEN_VALUE === null) {
throw BadRequestError({
message: "Missing Authorization Body in the request header",
})
}
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(AUTH_TOKEN_VALUE, await getJwtSignupSecret())
);
if (decodedToken.userId !== user.id) {
throw BadRequestError();
}
}
// complete setting up user's account
user = await completeAccount({
userId: user._id.toString(),
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
});
if (!user)
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
if (user.authProvider !== AuthProvider.OKTA_SAML) {
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user,
});
}
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED,
},
{
user,
status: ACCEPTED,
}
);
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
token = tokens.token;
// sending a welcome email to new users
if (await getLoopsApiKey()) {
await standardRequest.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName,
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + (await getLoopsApiKey()),
},
});
}
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "User Signed Up",
distinctId: email,
properties: {
email,
...(attributionSource ? { attributionSource } : {})
},
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to complete account setup",
});
}
return res.status(200).send({
message: "Successfully set up account",
user,
token,
});
};

View File

@@ -0,0 +1,90 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { Secret } from "../../models";
import { SecretService } from"../../services";
/**
* Return whether or not all secrets in workspace with id [workspaceId]
* are blind-indexed
* @param req
* @param res
* @returns
*/
export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId),
secretBlindIndex: {
$exists: false,
},
});
return res.status(200).send(secretsWithoutBlindIndex === 0);
}
/**
* Get all secrets for workspace with id [workspaceId]
*/
export const getWorkspaceSecrets = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const secrets = await Secret.find({
workspace: new Types.ObjectId (workspaceId),
});
return res.status(200).send({
secrets,
});
}
/**
* Update blind indices for secrets in workspace with id [workspaceId]
* @param req
* @param res
*/
export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
interface SecretToUpdate {
secretName: string;
_id: string;
}
const { workspaceId } = req.params;
const {
secretsToUpdate,
}: {
secretsToUpdate: SecretToUpdate[];
} = req.body;
// get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({
workspaceId: new Types.ObjectId(workspaceId),
});
// update secret blind indices
const operations = await Promise.all(
secretsToUpdate.map(async (secretToUpdate: SecretToUpdate) => {
const secretBlindIndex = await SecretService.generateSecretBlindIndexWithSalt({
secretName: secretToUpdate.secretName,
salt,
});
return ({
updateOne: {
filter: {
_id: new Types.ObjectId(secretToUpdate._id),
},
update: {
secretBlindIndex,
},
},
});
})
);
await Secret.bulkWrite(operations);
return res.status(200).send({
message: "Successfully named workspace secrets",
});
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Action, SecretVersion } from '../../models';
import { ActionNotFoundError } from '../../../utils/errors';
import { Request, Response } from "express";
import { Action } from "../../models";
import { ActionNotFoundError } from "../../../utils/errors";
export const getAction = async (req: Request, res: Response) => {
let action;
@@ -11,21 +10,21 @@ export const getAction = async (req: Request, res: Response) => {
action = await Action
.findById(actionId)
.populate([
'payload.secretVersions.oldSecretVersion',
'payload.secretVersions.newSecretVersion'
"payload.secretVersions.oldSecretVersion",
"payload.secretVersions.newSecretVersion",
]);
if (!action) throw ActionNotFoundError({
message: 'Failed to find action'
message: "Failed to find action",
});
} catch (err) {
throw ActionNotFoundError({
message: 'Failed to find action'
message: "Failed to find action",
});
}
return res.status(200).send({
action
action,
});
}

View File

@@ -0,0 +1,28 @@
import { Request, Response } from "express";
import { EELicenseService } from "../../services";
import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from "../../../config/request";
/**
* Return available cloud product information.
* Note: Nicely formatted to easily construct a table from
* @param req
* @param res
* @returns
*/
export const getCloudProducts = async (req: Request, res: Response) => {
const billingCycle = req.query["billing-cycle"] as string;
if (EELicenseService.instanceType === "cloud") {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
return res.status(200).send(data);
}
return res.status(200).send({
head: [],
rows: [],
});
}

View File

@@ -1,15 +1,21 @@
import * as stripeController from './stripeController';
import * as secretController from './secretController';
import * as secretSnapshotController from './secretSnapshotController';
import * as workspaceController from './workspaceController';
import * as actionController from './actionController';
import * as membershipController from './membershipController';
import * as secretController from "./secretController";
import * as secretSnapshotController from "./secretSnapshotController";
import * as organizationsController from "./organizationsController";
import * as ssoController from "./ssoController";
import * as usersController from "./usersController";
import * as workspaceController from "./workspaceController";
import * as actionController from "./actionController";
import * as membershipController from "./membershipController";
import * as cloudProductsController from "./cloudProductsController";
export {
stripeController,
secretController,
secretSnapshotController,
organizationsController,
ssoController,
usersController,
workspaceController,
actionController,
membershipController
membershipController,
cloudProductsController,
}

View File

@@ -3,8 +3,7 @@ import { Membership, Workspace } from "../../../models";
import { IMembershipPermission } from "../../../models/membership";
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
import { ADMIN, MEMBER } from "../../../variables/organization";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../../variables';
import { Builder } from "builder-pattern"
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../../variables";
import _ from "lodash";
export const denyMembershipPermissions = async (req: Request, res: Response) => {
@@ -15,10 +14,10 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
throw BadRequestError({ message: "One or more required fields are missing from the request or have incorrect type" })
}
return Builder<IMembershipPermission>()
.environmentSlug(permission.environmentSlug)
.ability(permission.ability)
.build();
return {
environmentSlug: permission.environmentSlug,
ability: permission.ability
}
})
const sanitizedMembershipPermissionsUnique = _.uniqWith(sanitizedMembershipPermissions, _.isEqual)
@@ -39,7 +38,7 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
throw BadRequestError({ message: "Something went wrong when locating the related workspace" })
}
const uniqueEnvironmentSlugs = new Set(_.uniq(_.map(relatedWorkspace.environments, 'slug')));
const uniqueEnvironmentSlugs = new Set(_.uniq(_.map(relatedWorkspace.environments, "slug")));
sanitizedMembershipPermissionsUnique.forEach(permission => {
if (!uniqueEnvironmentSlugs.has(permission.environmentSlug)) {
@@ -59,6 +58,6 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
}
res.send({
permissionsDenied: updatedMembershipWithPermissions.deniedPermissions
permissionsDenied: updatedMembershipWithPermissions.deniedPermissions,
})
}

View File

@@ -0,0 +1,209 @@
import { Request, Response } from "express";
import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from "../../../config/request";
import { EELicenseService } from "../../services";
export const getOrganizationPlansTable = async (req: Request, res: Response) => {
const billingCycle = req.query.billingCycle as string;
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
return res.status(200).send(data);
}
/**
* Return the organization current plan's feature set
*/
export const getOrganizationPlan = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const workspaceId = req.query.workspaceId as string;
const plan = await EELicenseService.getPlan(organizationId, workspaceId);
return res.status(200).send({
plan,
});
}
/**
* Return checkout url for pro trial
* @param req
* @param res
* @returns
*/
export const startOrganizationTrial = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const { success_url } = req.body;
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/session/trial`,
{
success_url
}
);
EELicenseService.delPlan(organizationId);
return res.status(200).send({
url
});
}
/**
* Return the organization's current plan's billing info
* @param req
* @param res
* @returns
*/
export const getOrganizationPlanBillingInfo = async (req: Request, res: Response) => {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/billing`
);
return res.status(200).send(data);
}
/**
* Return the organization's current plan's feature table
* @param req
* @param res
* @returns
*/
export const getOrganizationPlanTable = async (req: Request, res: Response) => {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/table`
);
return res.status(200).send(data);
}
export const getOrganizationBillingDetails = async (req: Request, res: Response) => {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details`
);
return res.status(200).send(data);
}
export const updateOrganizationBillingDetails = async (req: Request, res: Response) => {
const {
name,
email
} = req.body;
const { data } = await licenseServerKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details`,
{
...(name ? { name } : {}),
...(email ? { email } : {})
}
);
return res.status(200).send(data);
}
/**
* Return the organization's payment methods on file
*/
export const getOrganizationPmtMethods = async (req: Request, res: Response) => {
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`
);
return res.status(200).send(pmtMethods);
}
/**
* Return URL to add payment method for organization
*/
export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
const {
success_url,
cancel_url,
} = req.body;
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
{
success_url,
cancel_url,
}
);
return res.status(200).send({
url,
});
}
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
const { pmtMethodId } = req.params;
const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods/${pmtMethodId}`,
);
return res.status(200).send(data);
}
/**
* Return the organization's tax ids on file
*/
export const getOrganizationTaxIds = async (req: Request, res: Response) => {
const { data: { tax_ids } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids`
);
return res.status(200).send(tax_ids);
}
/**
* Add tax id to organization
*/
export const addOrganizationTaxId = async (req: Request, res: Response) => {
const {
type,
value
} = req.body;
const { data } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids`,
{
type,
value
}
);
return res.status(200).send(data);
}
/**
* Delete tax id with id [taxId] from organization tax ids on file
* @param req
* @param res
* @returns
*/
export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
const { taxId } = req.params;
const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids/${taxId}`,
);
return res.status(200).send(data);
}
/**
* Return organization's invoices on file
* @param req
* @param res
* @returns
*/
export const getOrganizationInvoices = async (req: Request, res: Response) => {
const { data: { invoices } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/invoices`
);
return res.status(200).send(invoices);
}

View File

@@ -1,15 +1,14 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Secret } from '../../../models';
import { SecretVersion } from '../../models';
import { EESecretService } from '../../services';
import { Request, Response } from "express";
import { Secret } from "../../../models";
import { SecretVersion } from "../../models";
import { EESecretService } from "../../services";
/**
* Return secret versions for secret with id [secretId]
* @param req
* @param res
*/
export const getSecretVersions = async (req: Request, res: Response) => {
export const getSecretVersions = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return secret versions'
#swagger.description = 'Return secret versions'
@@ -55,32 +54,22 @@ import { EESecretService } from '../../services';
}
}
*/
let secretVersions;
try {
const { secretId } = req.params;
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
secretVersions = await SecretVersion.find({
const secretVersions = await SecretVersion.find({
secret: secretId
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get secret versions'
});
}
return res.status(200).send({
secretVersions
});
}
};
/**
* Roll back secret with id [secretId] to version [version]
@@ -137,8 +126,6 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
}
}
*/
let secret;
try {
const { secretId } = req.params;
const { version } = req.body;
@@ -146,25 +133,29 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
const oldSecretVersion = await SecretVersion.findOne({
secret: secretId,
version
});
}).select("+secretBlindIndex");
if (!oldSecretVersion) throw new Error('Failed to find secret version');
if (!oldSecretVersion) throw new Error("Failed to find secret version");
const {
workspace,
type,
user,
environment,
secretBlindIndex,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
algorithm,
folder,
keyEncoding
} = oldSecretVersion;
// update secret
secret = await Secret.findByIdAndUpdate(
const secret = await Secret.findByIdAndUpdate(
secretId,
{
$inc: {
@@ -174,19 +165,23 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
type,
user,
environment,
...(secretBlindIndex ? { secretBlindIndex } : {}),
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
folderId: folder,
algorithm,
keyEncoding
},
{
new: true
}
);
if (!secret) throw new Error('Failed to find and update secret');
if (!secret) throw new Error("Failed to find and update secret");
// add new secret version
await new SecretVersion({
@@ -197,28 +192,26 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
user,
environment,
isDeleted: false,
...(secretBlindIndex ? { secretBlindIndex } : {}),
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
secretValueTag,
folder,
algorithm,
keyEncoding
}).save();
// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: secret.workspace.toString()
workspaceId: secret.workspace,
environment,
folderId: folder
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to roll back secret version'
});
}
return res.status(200).send({
secret
});
}
};

View File

@@ -1,6 +1,9 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { SecretSnapshot } from '../../models';
import { Request, Response } from "express";
import {
ISecretVersion,
SecretSnapshot,
TFolderRootVersionSchema,
} from "../../models";
/**
* Return secret snapshot with id [secretSnapshotId]
@@ -9,31 +12,34 @@ import { SecretSnapshot } from '../../models';
* @returns
*/
export const getSecretSnapshot = async (req: Request, res: Response) => {
let secretSnapshot;
try {
const { secretSnapshotId } = req.params;
secretSnapshot = await SecretSnapshot
.findById(secretSnapshotId)
.populate({
path: 'secretVersions',
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId)
.lean()
.populate<{ secretVersions: ISecretVersion[] }>({
path: "secretVersions",
populate: {
path: 'tags',
model: 'Tag',
}
});
path: "tags",
model: "Tag",
},
})
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get secret snapshot'
});
}
const folderId = secretSnapshot.folderId;
// to show only the folder required secrets
secretSnapshot.secretVersions = secretSnapshot.secretVersions.filter(
({ folder }) => folder === folderId
);
secretSnapshot.folderVersion =
secretSnapshot?.folderVersion?.nodes?.children?.map(({ id, name }) => ({
id,
name,
})) as any;
return res.status(200).send({
secretSnapshot
secretSnapshot,
});
}
};

View File

@@ -0,0 +1,267 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { BotOrgService } from "../../../services";
import { SSOConfig } from "../../models";
import {
MembershipOrg,
User
} from "../../../models";
import { getSSOConfigHelper } from "../../helpers/organizations";
import { client } from "../../../config";
import { ResourceNotFoundError } from "../../../utils/errors";
import { getSiteURL } from "../../../config";
import { EELicenseService } from "../../services";
/**
* Redirect user to appropriate SSO endpoint after successful authentication
* to finish inputting their master key for logging in or signing up
* @param req
* @param res
* @returns
*/
export const redirectSSO = async (req: Request, res: Response) => {
if (req.isUserCompleted) {
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
}
return res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
}
/**
* Return organization SAML SSO configuration
* @param req
* @param res
* @returns
*/
export const getSSOConfig = async (req: Request, res: Response) => {
const organizationId = req.query.organizationId as string;
const data = await getSSOConfigHelper({
organizationId: new Types.ObjectId(organizationId)
});
return res.status(200).send(data);
}
/**
* Update organization SAML SSO configuration
* @param req
* @param res
* @returns
*/
export const updateSSOConfig = async (req: Request, res: Response) => {
const {
organizationId,
authProvider,
isActive,
entryPoint,
issuer,
cert,
audience
} = req.body;
const plan = await EELicenseService.getPlan(organizationId);
if (!plan.samlSSO) return res.status(400).send({
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
});
interface PatchUpdate {
authProvider?: string;
isActive?: boolean;
encryptedEntryPoint?: string;
entryPointIV?: string;
entryPointTag?: string;
encryptedIssuer?: string;
issuerIV?: string;
issuerTag?: string;
encryptedCert?: string;
certIV?: string;
certTag?: string;
encryptedAudience?: string;
audienceIV?: string;
audienceTag?: string;
}
const update: PatchUpdate = {};
if (authProvider) {
update.authProvider = authProvider;
}
if (isActive !== undefined) {
update.isActive = isActive;
}
const key = await BotOrgService.getSymmetricKey(
new Types.ObjectId(organizationId)
);
if (entryPoint) {
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = client.encryptSymmetric(entryPoint, key);
update.encryptedEntryPoint = encryptedEntryPoint;
update.entryPointIV = entryPointIV;
update.entryPointTag = entryPointTag;
}
if (issuer) {
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = client.encryptSymmetric(issuer, key);
update.encryptedIssuer = encryptedIssuer;
update.issuerIV = issuerIV;
update.issuerTag = issuerTag;
}
if (cert) {
const {
ciphertext: encryptedCert,
iv: certIV,
tag: certTag
} = client.encryptSymmetric(cert, key);
update.encryptedCert = encryptedCert;
update.certIV = certIV;
update.certTag = certTag;
}
if (audience) {
const {
ciphertext: encryptedAudience,
iv: audienceIV,
tag: audienceTag
} = client.encryptSymmetric(audience, key);
update.encryptedAudience = encryptedAudience;
update.audienceIV = audienceIV;
update.audienceTag = audienceTag;
}
const ssoConfig = await SSOConfig.findOneAndUpdate(
{
organization: new Types.ObjectId(organizationId)
},
update,
{
new: true
}
);
if (!ssoConfig) throw ResourceNotFoundError({
message: "Failed to find SSO config to update"
});
if (update.isActive !== undefined) {
const membershipOrgs = await MembershipOrg.find({
organization: new Types.ObjectId(organizationId)
}).select("user");
if (update.isActive) {
await User.updateMany(
{
_id: {
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
}
},
{
authProvider: ssoConfig.authProvider
}
);
} else {
await User.updateMany(
{
_id: {
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
}
},
{
$unset: {
authProvider: 1
}
}
);
}
}
return res.status(200).send(ssoConfig);
}
/**
* Create organization SAML SSO configuration
* @param req
* @param res
* @returns
*/
export const createSSOConfig = async (req: Request, res: Response) => {
const {
organizationId,
authProvider,
isActive,
entryPoint,
issuer,
cert,
audience
} = req.body;
const plan = await EELicenseService.getPlan(organizationId);
if (!plan.samlSSO) return res.status(400).send({
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
});
const key = await BotOrgService.getSymmetricKey(
new Types.ObjectId(organizationId)
);
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = client.encryptSymmetric(entryPoint, key);
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = client.encryptSymmetric(issuer, key);
const {
ciphertext: encryptedCert,
iv: certIV,
tag: certTag
} = client.encryptSymmetric(cert, key);
const {
ciphertext: encryptedAudience,
iv: audienceIV,
tag: audienceTag
} = client.encryptSymmetric(audience, key);
const ssoConfig = await new SSOConfig({
organization: new Types.ObjectId(organizationId),
authProvider,
isActive,
encryptedEntryPoint,
entryPointIV,
entryPointTag,
encryptedIssuer,
issuerIV,
issuerTag,
encryptedCert,
certIV,
certTag,
encryptedAudience,
audienceIV,
audienceTag
}).save();
return res.status(200).send(ssoConfig);
}

View File

@@ -1,41 +0,0 @@
import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import Stripe from 'stripe';
import { getStripeSecretKey, getStripeWebhookSecret } from '../../../config';
/**
* Handle service provisioning/un-provisioning via Stripe
* @param req
* @param res
* @returns
*/
export const handleWebhook = async (req: Request, res: Response) => {
let event;
try {
const stripe = new Stripe(getStripeSecretKey(), {
apiVersion: '2022-08-01'
});
// check request for valid stripe signature
const sig = req.headers['stripe-signature'] as string;
event = stripe.webhooks.constructEvent(
req.body,
sig,
getStripeWebhookSecret()
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to process webhook'
});
}
switch (event.type) {
case '':
break;
default:
}
return res.json({ received: true });
};

View File

@@ -0,0 +1,13 @@
import { Request, Response } from "express";
/**
* Return the ip address of the current user
* @param req
* @param res
* @returns
*/
export const getMyIp = (req: Request, res: Response) => {
return res.status(200).send({
ip: req.authData.authIP
});
}

View File

@@ -1,24 +1,32 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Request, Response } from "express";
import { PipelineStage, Types } from "mongoose";
import { Secret } from "../../../models";
import {
Secret
} from '../../../models';
import {
SecretSnapshot,
FolderVersion,
IPType,
ISecretVersion,
Log,
SecretSnapshot,
SecretVersion,
ISecretVersion
} from '../../models';
import { EESecretService } from '../../services';
import { getLatestSecretVersionIds } from '../../helpers/secretVersion';
TFolderRootVersionSchema,
TrustedIP
} from "../../models";
import { EESecretService } from "../../services";
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
import Folder, { TFolderSchema } from "../../../models/folder";
import { searchByFolderId } from "../../../services/FolderService";
import { EELicenseService } from "../../services";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
/**
* Return secret snapshots for workspace with id [workspaceId]
* @param req
* @param res
*/
export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) => {
export const getWorkspaceSecretSnapshots = async (
req: Request,
res: Response
) => {
/*
#swagger.summary = 'Return project secret snapshot ids'
#swagger.description = 'Return project secret snapshots ids'
@@ -64,57 +72,48 @@ import { getLatestSecretVersionIds } from '../../helpers/secretVersion';
}
}
*/
let secretSnapshots;
try {
const { workspaceId } = req.params;
const { environment, folderId } = req.query;
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
secretSnapshots = await SecretSnapshot.find({
workspace: workspaceId
const secretSnapshots = await SecretSnapshot.find({
workspace: workspaceId,
environment,
folderId: folderId || "root",
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get secret snapshots'
});
}
return res.status(200).send({
secretSnapshots
secretSnapshots,
});
}
};
/**
* Return count of secret snapshots for workspace with id [workspaceId]
* @param req
* @param res
*/
export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Response) => {
let count;
try {
export const getWorkspaceSecretSnapshotsCount = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
count = await SecretSnapshot.countDocuments({
workspace: workspaceId
const { environment, folderId } = req.query;
const count = await SecretSnapshot.countDocuments({
workspace: workspaceId,
environment,
folderId: folderId || "root",
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to count number of secret snapshots'
});
}
return res.status(200).send({
count
count,
});
}
};
/**
* Rollback secret snapshot with id [secretSnapshotId] to version [version]
@@ -122,7 +121,10 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
* @param res
* @returns
*/
export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Response) => {
export const rollbackWorkspaceSecretSnapshot = async (
req: Request,
res: Response
) => {
/*
#swagger.summary = 'Roll back project secrets to those captured in a secret snapshot version.'
#swagger.description = 'Roll back project secrets to those captured in a secret snapshot version.'
@@ -173,106 +175,256 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
}
}
*/
let secrets;
try {
const { workspaceId } = req.params;
const { version } = req.body;
const { version, environment, folderId = "root" } = req.body;
// validate secret snapshot
const secretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId,
version
}).populate<{ secretVersions: ISecretVersion[]}>('secretVersions');
version,
environment,
folderId: folderId,
})
.populate<{ secretVersions: ISecretVersion[] }>({
path: "secretVersions",
select: "+secretBlindIndex",
})
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
const snapshotFolderTree = secretSnapshot.folderVersion;
const latestFolderTree = await Folder.findOne({
workspace: workspaceId,
environment,
});
const latestFolderVersion = await FolderVersion.findOne({
environment,
workspace: workspaceId,
"nodes.id": folderId,
}).sort({ "nodes.version": -1 });
const oldSecretVersionsObj: Record<string, ISecretVersion> = {};
const secretIds: Types.ObjectId[] = [];
const folderIds: string[] = [folderId];
secretSnapshot.secretVersions.forEach((snapSecVer) => {
oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer;
secretIds.push(snapSecVer.secret);
});
// the parent node from current latest one
// this will be modified according to the snapshot and latest snapshots
const newFolderTree =
latestFolderTree && searchByFolderId(latestFolderTree.nodes, folderId);
if (newFolderTree) {
newFolderTree.children = snapshotFolderTree?.nodes?.children || [];
const queue = [newFolderTree];
// a bfs algorithm in which we take the latest snapshots of all the folders in a level
while (queue.length) {
const groupByFolderId: Record<string, TFolderSchema> = {};
// the original queue is popped out completely to get what ever in a level
// subqueue is filled with all the children thus next level folders
// subQueue will then be transfered to the oriinal queue
const subQueue: TFolderSchema[] = [];
// get everything inside a level
while (queue.length) {
const folder = queue.pop() as TFolderSchema;
folder.children.forEach((el) => {
folderIds.push(el.id); // push ids and data into queu
subQueue.push(el);
// to modify the original tree very fast we keep a reference object
// key with folder id and pointing to the various nodes
groupByFolderId[el.id] = el;
});
}
// get latest snapshots of all the folder
const matchWsFoldersPipeline = {
$match: {
workspace: new Types.ObjectId(workspaceId),
environment,
folderId: {
$in: Object.keys(groupByFolderId),
},
},
};
const sortByFolderIdAndVersion: PipelineStage = {
$sort: { folderId: 1, version: -1 },
};
const pickLatestVersionOfEachFolder = {
$group: {
_id: "$folderId",
latestVersion: { $first: "$version" },
doc: {
$first: "$$ROOT",
},
},
};
const populateSecVersion = {
$lookup: {
from: SecretVersion.collection.name,
localField: "doc.secretVersions",
foreignField: "_id",
as: "doc.secretVersions",
},
};
const populateFolderVersion = {
$lookup: {
from: FolderVersion.collection.name,
localField: "doc.folderVersion",
foreignField: "_id",
as: "doc.folderVersion",
},
};
const unwindFolderVerField = {
$unwind: {
path: "$doc.folderVersion",
preserveNullAndEmptyArrays: true,
},
};
const latestSnapshotsByFolders: Array<{ doc: typeof secretSnapshot }> =
await SecretSnapshot.aggregate([
matchWsFoldersPipeline,
sortByFolderIdAndVersion,
pickLatestVersionOfEachFolder,
populateSecVersion,
populateFolderVersion,
unwindFolderVerField,
]);
// recursive snapshotting each level
latestSnapshotsByFolders.forEach((snap) => {
// mutate the folder tree to update the nodes to the latest version tree
// we are reconstructing the folder tree by latest snapshots here
if (groupByFolderId[snap.doc.folderId]) {
groupByFolderId[snap.doc.folderId].children =
snap.doc?.folderVersion?.nodes?.children || [];
}
// push all children of next level snapshots
if (snap.doc.folderVersion?.nodes?.children) {
queue.push(...snap.doc.folderVersion.nodes.children);
}
snap.doc.secretVersions.forEach((snapSecVer) => {
// record all the secrets
oldSecretVersionsObj[snapSecVer.secret.toString()] = snapSecVer;
secretIds.push(snapSecVer.secret);
});
});
queue.push(...subQueue);
}
}
// TODO: fix any
const oldSecretVersionsObj: any = secretSnapshot.secretVersions
.reduce((accumulator, s) => ({
...accumulator,
[`${s.secret.toString()}`]: s
}), {});
const latestSecretVersionIds = await getLatestSecretVersionIds({
secretIds: secretSnapshot.secretVersions.map((sv) => sv.secret)
secretIds,
});
// TODO: fix any
const latestSecretVersions: any = (await SecretVersion.find({
const latestSecretVersions: any = (
await SecretVersion.find(
{
_id: {
$in: latestSecretVersionIds.map((s) => s.versionId)
}
}, 'secret version'))
.reduce((accumulator, s) => ({
$in: latestSecretVersionIds.map((s) => s.versionId),
},
},
"secret version"
)
).reduce(
(accumulator, s) => ({
...accumulator,
[`${s.secret.toString()}`]: s
}), {});
[`${s.secret.toString()}`]: s,
}),
{}
);
const secDelQuery: Record<string, unknown> = {
workspace: workspaceId,
environment,
// undefined means root thus collect all secrets
};
if (folderId !== "root" && folderIds.length)
secDelQuery.folder = { $in: folderIds };
// delete existing secrets
await Secret.deleteMany({
workspace: workspaceId
await Secret.deleteMany(secDelQuery);
await Folder.deleteOne({
workspace: workspaceId,
environment,
});
// add secrets
secrets = await Secret.insertMany(
secretSnapshot.secretVersions.map((sv) => {
const secretId = sv.secret;
const secrets = await Secret.insertMany(
Object.keys(oldSecretVersionsObj).map((sv) => {
const {
secret: secretId,
workspace,
type,
user,
environment,
secretBlindIndex,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
createdAt
} = oldSecretVersionsObj[secretId.toString()];
createdAt,
algorithm,
keyEncoding,
folder: secFolderId,
} = oldSecretVersionsObj[sv];
return ({
return {
_id: secretId,
version: latestSecretVersions[secretId.toString()].version + 1,
workspace,
type,
user,
environment,
secretBlindIndex: secretBlindIndex ?? undefined,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
secretCommentCiphertext: '',
secretCommentIV: '',
secretCommentTag: '',
createdAt
});
secretCommentCiphertext: "",
secretCommentIV: "",
secretCommentTag: "",
createdAt,
algorithm,
keyEncoding,
folder: secFolderId,
};
})
);
// add secret versions
await SecretVersion.insertMany(
secrets.map(({
const secretV = await SecretVersion.insertMany(
secrets.map(
({
_id,
version,
workspace,
type,
user,
environment,
secretBlindIndex,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
algorithm,
keyEncoding,
folder: secFolderId,
}) => ({
_id: new Types.ObjectId(),
secret: _id,
@@ -282,42 +434,61 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
user,
environment,
isDeleted: false,
secretBlindIndex: secretBlindIndex ?? undefined,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
}))
algorithm,
keyEncoding,
folder: secFolderId,
})
)
);
// update secret versions of restored secrets as not deleted
await SecretVersion.updateMany({
secret: {
$in: secretSnapshot.secretVersions.map((sv) => sv.secret)
}
}, {
isDeleted: false
if (newFolderTree && latestFolderTree) {
// save the updated folder tree to the present one
newFolderTree.version = (latestFolderVersion?.nodes?.version || 0) + 1;
latestFolderTree._id = new Types.ObjectId();
latestFolderTree.isNew = true;
await latestFolderTree.save();
// create new folder version
const newFolderVersion = new FolderVersion({
workspace: workspaceId,
environment,
nodes: newFolderTree,
});
await newFolderVersion.save();
}
// update secret versions of restored secrets as not deleted
await SecretVersion.updateMany(
{
secret: {
$in: Object.keys(oldSecretVersionsObj).map(
(sv) => oldSecretVersionsObj[sv].secret
),
},
},
{
isDeleted: false,
}
);
// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
workspaceId: new Types.ObjectId(workspaceId),
environment,
folderId,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to roll back secret snapshot'
});
}
return res.status(200).send({
secrets
secrets,
});
}
};
/**
* Return (audit) logs for workspace with id [workspaceId]
@@ -392,8 +563,6 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
}
}
*/
let logs
try {
const { workspaceId } = req.params;
const offset: number = parseInt(req.query.offset as string);
@@ -402,33 +571,168 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
const userId: string = req.query.userId as string;
const actionNames: string = req.query.actionNames as string;
logs = await Log.find({
const logs = await Log.find({
workspace: workspaceId,
...( userId ? { user: userId } : {}),
...(
actionNames
...(userId ? { user: userId } : {}),
...(actionNames
? {
actionNames: {
$in: actionNames.split(',')
$in: actionNames.split(","),
},
}
} : {}
)
: {}),
})
.sort({ createdAt: sortBy === 'recent' ? -1 : 1 })
.sort({ createdAt: sortBy === "recent" ? -1 : 1 })
.skip(offset)
.limit(limit)
.populate('actions')
.populate('user serviceAccount serviceTokenData');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get workspace logs'
});
}
.populate("actions")
.populate("user serviceAccount serviceTokenData");
return res.status(200).send({
logs
logs,
});
};
/**
* Return trusted ips for workspace with id [workspaceId]
* @param req
* @param res
*/
export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const trustedIps = await TrustedIP.find({
workspace: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
trustedIps
});
}
/**
* Add a trusted ip to workspace with id [workspaceId]
* @param req
* @param res
*/
export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const {
ipAddress: ip,
comment,
isActive
} = req.body;
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
if (!plan.ipAllowlisting) return res.status(400).send({
message: "Failed to add IP access range due to plan restriction. Upgrade plan to add IP access range."
});
const isValidIPOrCidr = isValidIpOrCidr(ip);
if (!isValidIPOrCidr) return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
const { ipAddress, type, prefix } = extractIPDetails(ip);
const trustedIp = await new TrustedIP({
workspace: new Types.ObjectId(workspaceId),
ipAddress,
type,
prefix,
isActive,
comment,
}).save();
return res.status(200).send({
trustedIp
});
}
/**
* Update trusted ip with id [trustedIpId] workspace with id [workspaceId]
* @param req
* @param res
*/
export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId, trustedIpId } = req.params;
const {
ipAddress: ip,
comment
} = req.body;
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
if (!plan.ipAllowlisting) return res.status(400).send({
message: "Failed to update IP access range due to plan restriction. Upgrade plan to update IP access range."
});
const isValidIPOrCidr = isValidIpOrCidr(ip);
if (!isValidIPOrCidr) return res.status(400).send({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
const { ipAddress, type, prefix } = extractIPDetails(ip);
const updateObject: {
ipAddress: string;
type: IPType;
comment: string;
prefix?: number;
$unset?: {
prefix: number;
}
} = {
ipAddress,
type,
comment
};
if (prefix !== undefined) {
updateObject.prefix = prefix;
} else {
updateObject.$unset = { prefix: 1 };
}
const trustedIp = await TrustedIP.findOneAndUpdate(
{
_id: new Types.ObjectId(trustedIpId),
workspace: new Types.ObjectId(workspaceId),
},
updateObject,
{
new: true
}
);
return res.status(200).send({
trustedIp
});
}
/**
* Delete IP access range from workspace with id [workspaceId]
* @param req
* @param res
*/
export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
const { workspaceId, trustedIpId } = req.params;
const plan = await EELicenseService.getPlan(req.workspace.organization.toString());
if (!plan.ipAllowlisting) return res.status(400).send({
message: "Failed to delete IP access range due to plan restriction. Upgrade plan to delete IP access range."
});
const trustedIp = await TrustedIP.findOneAndDelete({
_id: new Types.ObjectId(trustedIpId),
workspace: new Types.ObjectId(workspaceId)
});
return res.status(200).send({
trustedIp
});
}

View File

@@ -1,18 +1,17 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Action } from '../models';
import { Types } from "mongoose";
import { Action } from "../models";
import {
getLatestNSecretSecretVersionIds,
getLatestSecretVersionIds,
getLatestNSecretSecretVersionIds
} from '../helpers/secretVersion';
} from "../helpers/secretVersion";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_UPDATE_SECRETS,
} from '../../variables';
} from "../../variables";
/**
* Create an (audit) action for updating secrets
@@ -27,7 +26,7 @@ const createActionUpdateSecret = async ({
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
@@ -36,34 +35,26 @@ const createActionUpdateSecret = async ({
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
let action;
try {
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds,
n: 2
n: 2,
}))
.map((s) => ({
oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id
newSecretVersion: s.versions[1]._id,
}));
action = await new Action({
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
secretVersions: latestSecretVersions,
},
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create update secret action');
}
return action;
}
@@ -81,7 +72,7 @@ const createActionSecret = async ({
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
@@ -90,34 +81,26 @@ const createActionSecret = async ({
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
let action;
try {
// case: action is adding, deleting, or reading secrets
// -> add new secret versions
const latestSecretVersions = (await getLatestSecretVersionIds({
secretIds
secretIds,
}))
.map((s) => ({
newSecretVersion: s.versionId
newSecretVersion: s.versionId,
}));
action = await new Action({
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
secretVersions: latestSecretVersions,
},
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action create/read/delete secret action');
}
return action;
}
@@ -133,26 +116,19 @@ const createActionClient = ({
name,
userId,
serviceAccountId,
serviceTokenDataId
serviceTokenDataId,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
}) => {
let action;
try {
action = new Action({
const action = new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId
serviceTokenData: serviceTokenDataId,
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create client action');
}
return action;
}
@@ -181,45 +157,39 @@ const createActionHelper = async ({
secretIds?: Types.ObjectId[];
}) => {
let action;
try {
switch (name) {
case ACTION_LOGIN:
case ACTION_LOGOUT:
action = await createActionClient({
name,
userId
userId,
});
break;
case ACTION_ADD_SECRETS:
case ACTION_READ_SECRETS:
case ACTION_DELETE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
if (!workspaceId || !secretIds) throw new Error("Missing required params workspace id or secret ids to create action secret");
action = await createActionSecret({
name,
userId,
workspaceId,
secretIds
secretIds,
});
break;
case ACTION_UPDATE_SECRETS:
if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret');
if (!workspaceId || !secretIds) throw new Error("Missing required params workspace id or secret ids to create action secret");
action = await createActionUpdateSecret({
name,
userId,
workspaceId,
secretIds
secretIds,
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action');
}
return action;
}
export {
createActionHelper
createActionHelper,
};

View File

@@ -1,7 +1,7 @@
import { Types } from 'mongoose';
import { Types } from "mongoose";
import _ from "lodash";
import { Membership } from "../../models";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from '../../variables';
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => {
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })

View File

@@ -1,9 +1,9 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { Types } from "mongoose";
import {
IAction,
Log,
IAction
} from '../models';
} from "../models";
/**
* Create an (audit) log
* @param {Object} obj
@@ -21,7 +21,7 @@ const createLogHelper = async ({
workspaceId,
actions,
channel,
ipAddress
ipAddress,
}: {
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
@@ -31,9 +31,7 @@ const createLogHelper = async ({
channel: string;
ipAddress: string;
}) => {
let log;
try {
log = await new Log({
const log = await new Log({
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
@@ -41,17 +39,12 @@ const createLogHelper = async ({
actionNames: actions.map((a) => a.name),
actions,
channel,
ipAddress
ipAddress,
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create log');
}
return log;
}
export {
createLogHelper
createLogHelper,
}

View File

@@ -0,0 +1,72 @@
import { Types } from "mongoose";
import {
SSOConfig
} from "../models";
import {
BotOrgService
} from "../../services";
import { client } from "../../config";
import { ValidationError } from "../../utils/errors";
export const getSSOConfigHelper = async ({
organizationId,
ssoConfigId
}: {
organizationId?: Types.ObjectId;
ssoConfigId?: Types.ObjectId;
}) => {
if (!organizationId && !ssoConfigId) throw ValidationError({
message: "Getting SSO data requires either id of organization or SSO data"
});
const ssoConfig = await SSOConfig.findOne({
...(organizationId ? { organization: organizationId } : {}),
...(ssoConfigId ? { _id: ssoConfigId } : {})
});
if (!ssoConfig) throw new Error("Failed to find organization SSO data");
const key = await BotOrgService.getSymmetricKey(
ssoConfig.organization
);
const entryPoint = client.decryptSymmetric(
ssoConfig.encryptedEntryPoint,
key,
ssoConfig.entryPointIV,
ssoConfig.entryPointTag
);
const issuer = client.decryptSymmetric(
ssoConfig.encryptedIssuer,
key,
ssoConfig.issuerIV,
ssoConfig.issuerTag
);
const cert = client.decryptSymmetric(
ssoConfig.encryptedCert,
key,
ssoConfig.certIV,
ssoConfig.certTag
);
const audience = client.decryptSymmetric(
ssoConfig.encryptedAudience,
key,
ssoConfig.audienceIV,
ssoConfig.audienceTag
);
return ({
_id: ssoConfig._id,
organization: ssoConfig.organization,
authProvider: ssoConfig.authProvider,
isActive: ssoConfig.isActive,
entryPoint,
issuer,
cert,
audience
});
}

View File

@@ -1,14 +1,11 @@
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Secret,
ISecret
} from '../../models';
import { Types } from "mongoose";
import { Secret } from "../../models";
import {
FolderVersion,
ISecretVersion,
SecretSnapshot,
SecretVersion,
ISecretVersion
} from '../models';
} from "../models";
/**
* Save a secret snapshot that is a copy of the current state of secrets in workspace with id
@@ -19,56 +16,70 @@ import {
* @returns {SecretSnapshot} secretSnapshot - new secret snapshot
*/
const takeSecretSnapshotHelper = async ({
workspaceId
workspaceId,
environment,
folderId = "root",
}: {
workspaceId: string;
workspaceId: Types.ObjectId;
environment: string;
folderId?: string;
}) => {
// get all folder ids
const secretIds = (
await Secret.find(
{
workspace: workspaceId,
environment,
folder: folderId,
},
"_id"
).lean()
).map((s) => s._id);
let secretSnapshot;
try {
const secretIds = (await Secret.find({
workspace: workspaceId
}, '_id')).map((s) => s._id);
const latestSecretVersions = (await SecretVersion.aggregate([
const latestSecretVersions = (
await SecretVersion.aggregate([
{
$match: {
environment,
workspace: new Types.ObjectId(workspaceId),
secret: {
$in: secretIds
}
}
$in: secretIds,
},
},
},
{
$group: {
_id: '$secret',
version: { $max: '$version' },
versionId: { $max: '$_id' } // secret version id
}
_id: "$secret",
version: { $max: "$version" },
versionId: { $max: "$_id" }, // secret version id
},
},
{
$sort: { version: -1 }
}
])
.exec())
.map((s) => s.versionId);
$sort: { version: -1 },
},
]).exec()
).map((s) => s.versionId);
const latestFolderVersion = await FolderVersion.findOne({
environment,
workspace: workspaceId,
"nodes.id": folderId,
}).sort({ "nodes.version": -1 });
const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId
workspace: workspaceId,
}).sort({ version: -1 });
secretSnapshot = await new SecretSnapshot({
const secretSnapshot = await new SecretSnapshot({
workspace: workspaceId,
environment,
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
secretVersions: latestSecretVersions
secretVersions: latestSecretVersions,
folderId,
folderVersion: latestFolderVersion,
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to take a secret snapshot');
}
return secretSnapshot;
}
};
/**
* Add secret versions [secretVersions] to the SecretVersion collection.
@@ -77,93 +88,35 @@ const takeSecretSnapshotHelper = async ({
* @returns {SecretVersion[]} newSecretVersions - new secret versions
*/
const addSecretVersionsHelper = async ({
secretVersions
secretVersions,
}: {
secretVersions: ISecretVersion[]
secretVersions: ISecretVersion[];
}) => {
let newSecretVersions;
try {
newSecretVersions = await SecretVersion.insertMany(secretVersions);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error(`Failed to add secret versions [err=${err}]`);
}
const newSecretVersions = await SecretVersion.insertMany(secretVersions);
return newSecretVersions;
}
};
const markDeletedSecretVersionsHelper = async ({
secretIds
secretIds,
}: {
secretIds: Types.ObjectId[];
}) => {
try {
await SecretVersion.updateMany({
secret: { $in: secretIds }
}, {
isDeleted: true
}, {
new: true
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to mark secret versions as deleted');
await SecretVersion.updateMany(
{
secret: { $in: secretIds },
},
{
isDeleted: true,
},
{
new: true,
}
}
/**
* Initialize secret versioning by setting previously unversioned
* secrets to version 1 and begin populating secret versions.
*/
const initSecretVersioningHelper = async () => {
try {
await Secret.updateMany(
{ version: { $exists: false } },
{ $set: { version: 1 } }
);
const unversionedSecrets: ISecret[] = await Secret.aggregate([
{
$lookup: {
from: 'secretversions',
localField: '_id',
foreignField: 'secret',
as: 'versions',
},
},
{
$match: {
versions: { $size: 0 },
},
},
]);
if (unversionedSecrets.length > 0) {
await addSecretVersionsHelper({
secretVersions: unversionedSecrets.map((s, idx) => ({
...s,
secret: s._id,
version: s.version ? s.version : 1,
isDeleted: false,
workspace: s.workspace,
environment: s.environment
}))
});
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to ensure that secrets are versioned');
}
}
};
export {
takeSecretSnapshotHelper,
addSecretVersionsHelper,
markDeletedSecretVersionsHelper,
initSecretVersioningHelper
}
};

View File

@@ -1,6 +1,5 @@
import * as Sentry from '@sentry/node';
import { Types } from 'mongoose';
import { SecretVersion } from '../models';
import { Types } from "mongoose";
import { SecretVersion } from "../models";
/**
* Return latest secret versions for secrets with ids [secretIds]
@@ -9,48 +8,32 @@ import { SecretVersion } from '../models';
* @returns
*/
const getLatestSecretVersionIds = async ({
secretIds
secretIds,
}: {
secretIds: Types.ObjectId[];
}) => {
interface LatestSecretVersionId {
_id: Types.ObjectId;
version: number;
versionId: Types.ObjectId;
}
let latestSecretVersionIds: LatestSecretVersionId[];
try {
latestSecretVersionIds = (await SecretVersion.aggregate([
const latestSecretVersionIds = await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds
}
}
$in: secretIds,
},
},
},
{
$group: {
_id: '$secret',
version: { $max: '$version' },
versionId: { $max: '$_id' } // id of latest secret version
}
_id: "$secret",
version: { $max: "$version" },
versionId: { $max: "$_id" }, // id of latest secret version
},
},
{
$sort: { version: -1 }
}
])
.exec());
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get latest secret versions');
}
$sort: { version: -1 },
},
]).exec();
return latestSecretVersionIds;
}
};
/**
* Return latest [n] secret versions for secrets with ids [secretIds]
@@ -61,16 +44,13 @@ const getLatestSecretVersionIds = async ({
*/
const getLatestNSecretSecretVersionIds = async ({
secretIds,
n
n,
}: {
secretIds: Types.ObjectId[];
n: number;
}) => {
// TODO: optimize query
let latestNSecretVersions;
try {
latestNSecretVersions = (await SecretVersion.aggregate([
const latestNSecretVersions = await SecretVersion.aggregate([
{
$match: {
secret: {
@@ -93,18 +73,10 @@ const getLatestNSecretSecretVersionIds = async ({
secret: "$_id",
versions: { $slice: ["$versions", n] },
},
}
]));
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to get latest n secret versions');
}
},
]);
return latestNSecretVersions;
}
};
export {
getLatestSecretVersionIds,
getLatestNSecretSecretVersionIds
}
export { getLatestSecretVersionIds, getLatestNSecretSecretVersionIds };

View File

@@ -1,7 +1,5 @@
import requireLicenseAuth from './requireLicenseAuth';
import requireSecretSnapshotAuth from './requireSecretSnapshotAuth';
import requireSecretSnapshotAuth from "./requireSecretSnapshotAuth";
export {
requireLicenseAuth,
requireSecretSnapshotAuth
requireSecretSnapshotAuth,
}

View File

@@ -1,23 +0,0 @@
import { Request, Response, NextFunction } from 'express';
/**
* Validate if organization hosting meets license requirements to
* access a license-specific route.
* @param {Object} obj
* @param {String[]} obj.acceptedTiers
*/
const requireLicenseAuth = ({
acceptedTiers
}: {
acceptedTiers: string[];
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
} catch (err) {
}
}
}
export default requireLicenseAuth;

View File

@@ -1,9 +1,9 @@
import { Request, Response, NextFunction } from 'express';
import { UnauthorizedRequestError, SecretSnapshotNotFoundError } from '../../utils/errors';
import { SecretSnapshot } from '../models';
import { NextFunction, Request, Response } from "express";
import { SecretSnapshotNotFoundError } from "../../utils/errors";
import { SecretSnapshot } from "../models";
import {
validateMembership
} from '../../helpers/membership';
validateMembership,
} from "../../helpers/membership";
/**
* Validate if user on request has proper membership for secret snapshot
@@ -15,7 +15,7 @@ import {
const requireSecretSnapshotAuth = ({
acceptedRoles,
}: {
acceptedRoles: Array<'admin' | 'member'>;
acceptedRoles: Array<"admin" | "member">;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { secretSnapshotId } = req.params;
@@ -24,14 +24,14 @@ const requireSecretSnapshotAuth = ({
if (!secretSnapshot) {
return next(SecretSnapshotNotFoundError({
message: 'Failed to find secret snapshot'
message: "Failed to find secret snapshot",
}));
}
await validateMembership({
userId: req.user._id,
workspaceId: secretSnapshot.workspace,
acceptedRoles
acceptedRoles,
});
req.secretSnapshot = secretSnapshot as any;

View File

@@ -1,12 +1,12 @@
import { Schema, model, Types } from 'mongoose';
import { Schema, Types, model } from "mongoose";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS
} from '../../variables';
ACTION_UPDATE_SECRETS,
} from "../../variables";
export interface IAction {
name: string;
@@ -30,42 +30,40 @@ const actionSchema = new Schema<IAction>(
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS
]
ACTION_DELETE_SECRETS,
],
},
user: {
type: Schema.Types.ObjectId,
ref: 'User'
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: 'ServiceAccount'
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
ref: 'ServiceTokenData'
ref: "ServiceTokenData",
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace'
ref: "Workspace",
},
payload: {
secretVersions: [{
oldSecretVersion: {
type: Schema.Types.ObjectId,
ref: 'SecretVersion'
ref: "SecretVersion",
},
newSecretVersion: {
type: Schema.Types.ObjectId,
ref: 'SecretVersion'
}
}]
}
ref: "SecretVersion",
},
}],
},
}, {
timestamps: true
timestamps: true,
}
);
const Action = model<IAction>('Action', actionSchema);
export default Action;
export const Action = model<IAction>("Action", actionSchema);

View File

@@ -0,0 +1,58 @@
import { Schema, Types, model } from "mongoose";
export type TFolderRootVersionSchema = {
_id: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
nodes: TFolderVersionSchema;
};
export type TFolderVersionSchema = {
id: string;
name: string;
version: number;
children: TFolderVersionSchema[];
};
const folderVersionSchema = new Schema<TFolderVersionSchema>({
id: {
required: true,
type: String,
default: "root",
},
name: {
required: true,
type: String,
default: "root",
},
version: {
required: true,
type: Number,
default: 1,
},
});
folderVersionSchema.add({ children: [folderVersionSchema] });
const folderRootVersionSchema = new Schema<TFolderRootVersionSchema>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
nodes: folderVersionSchema,
},
{
timestamps: true,
}
);
export const FolderVersion = model<TFolderRootVersionSchema>(
"FolderVersion",
folderRootVersionSchema
);

View File

@@ -1,15 +1,7 @@
import SecretSnapshot, { ISecretSnapshot } from './secretSnapshot';
import SecretVersion, { ISecretVersion } from './secretVersion';
import Log, { ILog } from './log';
import Action, { IAction } from './action';
export {
SecretSnapshot,
ISecretSnapshot,
SecretVersion,
ISecretVersion,
Log,
ILog,
Action,
IAction
}
export * from "./secretSnapshot";
export * from "./secretVersion";
export * from "./folderVersion";
export * from "./log";
export * from "./action";
export * from "./ssoConfig";
export * from "./trustedIp";

View File

@@ -1,12 +1,12 @@
import { Schema, model, Types } from 'mongoose';
import { Schema, Types, model } from "mongoose";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS
} from '../../variables';
ACTION_UPDATE_SECRETS,
} from "../../variables";
export interface ILog {
_id: Types.ObjectId;
@@ -24,19 +24,19 @@ const logSchema = new Schema<ILog>(
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: 'ServiceAccount'
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
ref: 'ServiceTokenData'
ref: "ServiceTokenData",
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace'
ref: "Workspace",
},
actionNames: {
type: [String],
@@ -46,28 +46,27 @@ const logSchema = new Schema<ILog>(
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS
ACTION_DELETE_SECRETS,
],
required: true
required: true,
},
actions: [{
type: Schema.Types.ObjectId,
ref: 'Action',
required: true
ref: "Action",
required: true,
}],
channel: {
type: String,
enum: ['web', 'cli', 'auto', 'k8-operator', 'other'],
required: true
enum: ["web", "cli", "auto", "k8-operator", "other"],
required: true,
},
ipAddress: {
type: String
type: String,
},
},
{
timestamps: true,
}
}, {
timestamps: true
}
);
const Log = model<ILog>('Log', logSchema);
export default Log;
export const Log = model<ILog>("Log", logSchema);

View File

@@ -1,33 +1,52 @@
import { Schema, model, Types } from 'mongoose';
import { Schema, Types, model } from "mongoose";
export interface ISecretSnapshot {
workspace: Types.ObjectId;
environment: string;
folderId: string | "root";
version: number;
secretVersions: Types.ObjectId[];
folderVersion: Types.ObjectId;
}
const secretSnapshotSchema = new Schema<ISecretSnapshot>(
{
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
ref: "Workspace",
required: true,
},
environment: {
type: String,
required: true,
},
folderId: {
type: String,
default: "root",
},
version: {
type: Number,
required: true
default: 1,
required: true,
},
secretVersions: [{
secretVersions: [
{
type: Schema.Types.ObjectId,
ref: 'SecretVersion',
required: true
}]
ref: "SecretVersion",
required: true,
},
],
folderVersion: {
type: Schema.Types.ObjectId,
ref: "FolderVersion",
},
},
{
timestamps: true
timestamps: true,
}
);
const SecretSnapshot = model<ISecretSnapshot>('SecretSnapshot', secretSnapshotSchema);
export default SecretSnapshot;
export const SecretSnapshot = model<ISecretSnapshot>(
"SecretSnapshot",
secretSnapshotSchema
);

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