Compare commits

..

1080 Commits

Author SHA1 Message Date
8a19cfe0c6 removed secret scanning from the menu 2023-07-13 10:31:54 -07:00
a00fec9bca trigger standalone docker img too 2023-07-13 11:23:41 -04:00
209f224517 Merge pull request #745 from Infisical/docs-sdk
Remove individual SDK pages from docs
2023-07-13 17:10:26 +07:00
0b7f2b7d4b Remove individual SDK pages from docs in favor of each SDKs README on GitHub 2023-07-13 17:08:32 +07:00
eff15fc3d0 Merge pull request #744 from Infisical/usage-billing
Fix subscription context get organization from useOrganization
2023-07-13 17:07:42 +07:00
2614459772 Fix subscription context get organization from useOrganization 2023-07-13 17:01:53 +07:00
4e926746cf fixing the pro trial bug 2023-07-12 15:46:42 -07:00
edd5afa13b remove secret engine from main 2023-07-12 15:50:36 -04:00
442f572acc Merge branch 'infisical-radar-app' into main 2023-07-12 12:12:24 -07:00
be58f3c429 removed the learning item from sidebar 2023-07-12 11:50:36 -07:00
3eea5d9322 Merge pull request #735 from Infisical/new-sidebars
fixing the bugs with sidebars
2023-07-12 11:23:26 -07:00
e4e87163e8 removed org member section 2023-07-12 11:19:56 -07:00
d3aeb729e0 fixing ui/ux bugs 2023-07-12 11:18:42 -07:00
2e7c7cf1da fix typo in folder docs 2023-07-12 01:41:14 -04:00
5d39416532 replace cli quick start 2023-07-12 01:38:59 -04:00
af95adb589 Update usage.mdx 2023-07-12 01:31:09 -04:00
0fc4f96773 Merge pull request #736 from Infisical/revamp-docs
Revamp core docs
2023-07-12 01:29:10 -04:00
0a9adf33c8 revamp core docs 2023-07-12 01:23:28 -04:00
f9110cedfa fixing the bug with switching orgs 2023-07-11 22:13:54 -07:00
88ec55fc49 Merge pull request #700 from Infisical/new-sidebars
new sidebars
2023-07-11 17:29:48 -07:00
98b2a2a5c1 adding trial to the sidebar 2023-07-11 17:26:36 -07:00
27eeafbf36 Merge pull request #730 from Infisical/main
Catching up the branch
2023-07-11 16:19:39 -07:00
0cf63028df fixing style and solving merge conflicts 2023-07-11 16:19:07 -07:00
0b52b3cf58 Update mint.json 2023-07-11 14:14:23 -07:00
e1764880a2 Update overview.mdx 2023-07-11 14:09:57 -07:00
d3a47ffcdd Update mint.json 2023-07-11 13:56:24 -07:00
9c1f88bb9c Update mint.json 2023-07-11 13:49:55 -07:00
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
3f1db47c30 Merge pull request #731 from Infisical/office-365-smtp
Add support for Office365 SMTP
2023-07-11 15:04:26 +07:00
3e3bbe298d Add support for Office365 SMTP 2023-07-11 14:50:41 +07:00
46dc357651 final changes to sidebars 2023-07-11 00:04:14 -07:00
07d25cb673 extract version from tag 2023-07-10 23:26:14 -04:00
264f75ce8e correct gha for k8 operator 2023-07-10 23:20:45 -04:00
9713a19405 add semvar to k8 images 2023-07-10 23:14:10 -04:00
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
b36801652f Merge pull request #729 from Infisical/trial-revamp
Infisical Cloud Pro Free Trial Update
2023-07-10 15:13:28 +07:00
9e5b9cbdb5 Fix lint errors 2023-07-10 15:06:00 +07:00
bdf4ebd1bc second iteration of the new sidebar 2023-07-09 23:58:27 -07:00
e91e7f96c2 Update free plan logic 2023-07-10 13:48:46 +07:00
34fef4aaad Implemented feature to remove the trailing slash from the domain url 2023-07-10 12:16:51 +05:30
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
ed95b99ed1 Merge branch 'main' into main 2023-07-10 00:08:25 -04:00
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
13a81c9222 add 401 error message for get secrets in cli 2023-07-09 23:25:35 -04:00
6354464859 update terraform docs with path and env 2023-07-09 22:40:00 -04:00
ec26404b94 Merge pull request #727 from Infisical/main
Catching up with main
2023-07-09 11:13:40 -07:00
5ef2508736 docs: Add missing pull request contribution link 2023-07-09 15:44:25 +02:00
93264fd2d0 docs: Fix wrong integration name 2023-07-09 15:40:59 +02:00
7020c7aeab fix: completing allow user to press Enter in forgot password flow 2023-07-09 15:08:25 +08:00
25b1673321 improve k8 operator docs 2023-07-08 21:48:06 -04:00
628bc711c2 update k8 docks for quick start 2023-07-08 21:12:05 -04:00
a3b4228685 add path to export command 2023-07-08 16:15:45 -04:00
374c8e4a1a Update ingress class values.yaml 2023-07-08 13:47:13 -04:00
5afcf2798f Update build-staging-img.yml 2023-07-08 13:32:35 -04:00
1657cf0a7e Update values.yaml 2023-07-08 13:16:10 -04:00
c9820d0071 Update values.yaml 2023-07-08 12:55:49 -04:00
b53c046eef Merge pull request #713 from akhilmhdh/feat/secret-reference
secret reference
2023-07-07 19:02:57 -04:00
fd10d7ed34 add docs for k8 secret refs 2023-07-07 18:59:23 -04:00
c5aae44249 add docs for k8 secret refs 2023-07-07 18:56:38 -04:00
83aa6127ec update k8 chart version 2023-07-07 15:56:47 -04:00
5a2299f758 update k8 operator crd for secret refs 2023-07-07 15:55:45 -04:00
57cdab0727 update k8 operator crd for secret refs 2023-07-07 15:55:22 -04:00
f82fa1b3b3 add secret reference support 2023-07-07 15:49:21 -04:00
e95eef2071 Merge branch 'main' of https://github.com/Infisical/infisical 2023-07-07 13:01:51 +07:00
53efdac0f0 Bring back catch TokenExpiredError in backend error-handling middleware 2023-07-07 13:01:38 +07:00
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
0f72ccf82e Remove Laravel Forge from self-hosting docs, update image name 2023-07-07 12:13:47 +07:00
c191eb74fd Update README.md 2023-07-06 21:39:05 -07:00
f9fca42c5b fix incorrect leading slash in example 2023-07-06 13:36:15 -04:00
11a19eef07 add --path flag to docs for infisical secrets set 2023-07-06 13:20:48 -04:00
8a237af4ac feat(secret-ref): updated reference corner cases of trailing slashes 2023-07-06 22:15:10 +05:30
24413e1edd Added docs for Laravel Forge Integration 2023-07-06 15:43:43 +01:00
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
5599132efe fix(secret-ref): resolved service token unable to fetch secrets in cli 2023-07-06 18:58:48 +05:30
7f9e27e3d3 Update README.md 2023-07-05 15:41:38 -07:00
7d36360111 Updated AWS deploy image 2023-07-05 15:34:22 -07:00
d350297ce1 Deploy to AWS button updated 2023-07-05 15:28:17 -07:00
18d4e42d1f Update README.md 2023-07-05 15:19:54 -07:00
9faf5a3d5c add secret scanning to gamma values 2023-07-05 18:17:09 -04:00
da113612eb diable secret scan by default 2023-07-05 18:09:46 -04:00
e9e2eade89 update helm chart version 2023-07-05 17:56:30 -04:00
3cbc9c1b5c update helm chart to include git app 2023-07-05 17:54:29 -04:00
0772510e47 update gha for git app gamma deploy 2023-07-05 15:52:43 -04:00
f389aa07eb update docker file for prod build 2023-07-05 15:39:44 -04:00
27a110a93a build secret scanning 2023-07-05 15:22:29 -04:00
13eaa4e9a1 feat(secret-ref): updated doc 2023-07-05 23:00:17 +05:30
7ec7d05fb0 feat(secret-ref): implemented cli changes for secret reference 2023-07-05 23:00:17 +05:30
7fe4089bb0 feat(secret-ref): implemented ui for service token changes 2023-07-05 23:00:17 +05:30
0cee453202 feat(secret-ref): implemented backend changes for multi env and folder in service token 2023-07-05 23:00:17 +05:30
088d8097a9 Merge pull request #712 from atimapreandrew/laravel-forge-integration
Laravel forge integration
2023-07-05 23:43:43 +07:00
4e6fae03ff Patch sync Laravel Forge integration 2023-07-05 23:40:43 +07:00
732d0dfdca Added docs for Laravel Forge Integration 2023-07-05 13:45:10 +01:00
93e0232c21 fix: allow user to press Enter in forgot password page 2023-07-05 19:02:48 +08:00
37707c422a fix: allow user to press Enter in login page 2023-07-05 18:40:48 +08:00
2f1bd9ca61 fix: enable user to press Enter in signup flow 2023-07-05 18:32:03 +08:00
3d9ddbf9bc Merge branch 'main' of https://github.com/Infisical/infisical 2023-07-05 13:52:06 +07:00
7c9140dcec Update trial message 2023-07-05 13:51:50 +07:00
a63d179a0d add email notifications for risks 2023-07-04 22:06:29 -04:00
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
ff2c9e98c0 add --path flag to docs 2023-07-04 19:48:36 -04:00
23f4a350e7 Added docs for Laravel Forge Integration 2023-07-04 21:08:15 +01:00
696225d8d2 laravel forge integration 2023-07-04 20:01:49 +01:00
6c1ccc17b3 laravel forge integration 2023-07-04 19:28:42 +01:00
aa60f3a664 Merge branch 'main' of github.com:atimapreandrew/infisical 2023-07-04 17:49:08 +01:00
f01fb2830a patch Eslint GetToken issue 2023-07-04 11:11:05 -04:00
9f6aa6b13e add v1 secret scanning 2023-07-04 10:54:44 -04:00
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
42de0fbe73 Fix lint errors 2023-07-04 16:22:06 +07:00
553c986aa8 Update free trial indicator in usage and billing page 2023-07-04 16:01:20 +07:00
9a1e2260a0 Merge pull request #701 from Infisical/main
Update branch
2023-06-30 16:54:26 -07:00
98f7ce2585 Merge branch 'main' of github.com:atimapreandrew/infisical 2023-06-30 17:55:22 +01:00
c30ec8cb5f Merge pull request #697 from Infisical/revamp-project-settings
Standardize styling of Project Settings Page
2023-06-30 16:44:02 +07:00
104c752f9a Finish preliminary standardization of project settings page 2023-06-30 16:38:54 +07:00
b66bea5671 Merge pull request #692 from akhilmhdh/feat/multi-line-secrets
multi line support for secrets
2023-06-29 17:35:25 -04:00
f9313204a7 add docs for k8 re sync interval 2023-06-29 16:08:43 -04:00
cb5c371a4f add re-sync interval 2023-06-29 15:02:53 -04:00
a32df58f46 Merge pull request #695 from Infisical/check-rbac
Rewire RBAC paywall to new mechanism
2023-06-29 18:53:07 +07:00
e2658cc8dd Rewire RBAC paywall to new mechanism 2023-06-29 18:47:35 +07:00
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
ddff8be53c Fix build error 2023-06-29 18:15:59 +07:00
114d488345 Fix merge conflicts 2023-06-29 17:53:33 +07:00
c4da5a6ead Fix merge conflicts 2023-06-29 17:49:01 +07:00
056f5a4555 Finish preliminary making user settings, org settings styling similar to usage and billing page 2023-06-29 17:47:23 +07:00
dfc88d99f6 first draft new sidebar 2023-06-28 14:28:52 -07:00
033f41a7d5 Merge branch 'main' of github.com:atimapreandrew/infisical 2023-06-28 19:15:08 +01:00
5612a01039 fix(multi-line): resolved linting issues 2023-06-28 20:50:02 +05:30
f1d609cf40 fix: resolved secret version empty 2023-06-28 20:32:12 +05:30
0e9c71ae9f feat(multi-line): added support for multi-line in ui 2023-06-28 20:32:12 +05:30
d1af399489 Merge pull request #684 from akhilmhdh/feat/integrations-page-revamp
integrations page revamp
2023-06-27 17:50:49 -04:00
f445bac42f swap out for v3 secrets 2023-06-27 17:20:30 -04:00
798f091ff2 fix fetching secrets via service token 2023-06-27 15:00:03 -04:00
8381944bb2 feat(integrations-page): fixed id in delete modal 2023-06-27 23:56:43 +05:30
f9d0e0d971 Replace - with Unlimited in compare plans table 2023-06-27 22:00:13 +07:00
29d50f850b Correct current plan text in usage and billing 2023-06-27 19:01:31 +07:00
81c69d92b3 Restyle org name change section 2023-06-27 18:48:26 +07:00
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
1cf65aca1b Remove print statement 2023-06-27 17:46:36 +07:00
470c429bd9 Merge remote-tracking branch 'origin' into paywalls 2023-06-27 17:46:18 +07:00
c8d081e818 Remove print statement 2023-06-27 17:45:20 +07:00
492c6a6f97 Fix lint errors 2023-06-27 17:30:37 +07:00
1dfd18e779 Add paywall for PIT and redirect paywall to contact sales in self-hosted 2023-06-27 17:19:33 +07:00
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
825143f17c Adjust breadcrumb spacing 2023-06-27 16:12:18 +07:00
da144b4d02 Hide usage and billing from Navbar in self-hosted 2023-06-27 15:56:48 +07:00
f4c4545099 Merge remote-tracking branch 'origin' into org-settings 2023-06-27 15:39:51 +07:00
924a969307 Fix lint errors for revamped billing and usage page 2023-06-27 15:39:36 +07:00
072f6c737c UI update to inetgrations 2023-06-26 18:08:00 -07:00
5f683dd389 feat(integrations-page): updated current integrations width and fixed id in delete modal 2023-06-26 14:31:13 +05:30
2526cbe6ca Add padding Checkly integration page 2023-06-26 12:39:29 +07:00
6959fc52ac minor style updates 2023-06-25 21:49:28 -07:00
81bd684305 removed unnecessary variable declarations 2023-06-25 17:17:29 +01:00
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
ca3f7bac6c Remove catch error-handling in favor of error-handling middleware 2023-06-25 17:31:19 +07:00
a127d452bd Continue to make progress on usage and billing page revamp 2023-06-25 17:03:41 +07:00
7c77cc4ea4 fix(integrations-page): eslint fixes to the new upstream changes made 2023-06-24 23:44:52 +05:30
9c0e32a790 fix(integrations-page): added back cloudflare changes in main integrations page 2023-06-24 23:35:55 +05:30
611fae785a chore: updated to latested storybook v7 stable version 2023-06-24 23:31:37 +05:30
0ef4ac1cdc feat(integration-page): implemented new optimized integrations page 2023-06-24 23:31:37 +05:30
c04ea7e731 feat(integration-page): updated components and api hooks 2023-06-24 23:30:27 +05:30
9bdecaf02f removed await-to-js and builder-pattern dependencies from backend 2023-06-24 00:29:31 +01:00
6b222bad01 youtube link change 2023-06-22 19:49:21 -07:00
079d68c042 remove dummy file content 2023-06-22 22:28:39 -04:00
4b800202fb git app with probot 2023-06-22 22:26:23 -04:00
12d0916625 casting to date 2023-06-22 16:25:21 -07:00
e0976d6bd6 added ? to getTime 2023-06-22 16:16:46 -07:00
a31f364361 converted date to unix 2023-06-22 16:10:54 -07:00
8efa17928c intercom date fix 2023-06-22 15:57:20 -07:00
48bfdd500d date format intercom 2023-06-22 15:30:21 -07:00
4621122cfb added created timestamp to intercom 2023-06-22 15:17:11 -07:00
62fb048cce intercom debugging 2023-06-22 15:09:02 -07:00
d4d0fe60b3 Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-22 15:00:16 -07:00
0a6e8e009b intercom update 2023-06-22 14:59:55 -07:00
9f319d7ce3 add dummy value for intercom 2023-06-22 17:17:38 -04:00
7b3bd54386 intercom check 2023-06-22 13:29:26 -07:00
8d82e2d0fc Replace generic error with BadRequestError for missing refresh token in exchange 2023-06-22 18:08:23 +07:00
ffd4655e2f Add API Key auth mode support for v1/workspace 2023-06-22 17:53:09 +07:00
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
b22a179a17 Fix lint issues 2023-06-22 17:03:28 +07:00
1cbab58d29 Merge remote-tracking branch 'origin' into stripe-error 2023-06-22 16:38:46 +07:00
28943f3b6f Finish removing Stripe from codebase 2023-06-22 16:38:02 +07:00
b1f4e17aaf increase limit 2023-06-22 00:40:45 -04:00
afd0c6de08 remove unused import 2023-06-21 15:08:47 -04:00
cf114b0d3c Merge pull request #648 from quinton11/feat/cli-login-redirect
feat: cli login via browser
2023-06-21 14:47:47 -04:00
f785d62315 remove img from login by cli 2023-06-21 14:46:57 -04:00
7aeda9e245 remove service accounts from k8 docs 2023-06-21 12:45:56 -04:00
8a5e655122 fix: frontend lint errors 2023-06-21 07:29:05 +00:00
9b447a4ab0 Merge branch 'main' into feat/cli-login-redirect 2023-06-21 06:47:57 +00:00
f3e84dc6eb Merge pull request #667 from Stijn-Kuijper/cloudflare-pages-integration
Cloudflare Pages integration
2023-06-21 13:09:38 +07:00
a18a86770e Add docs for Cloudflare Pages integration 2023-06-21 13:05:35 +07:00
6300f86cc4 Optimize and patch minor issues for Cloudflare Pages integration 2023-06-21 12:11:53 +07:00
df662b1058 Resolve merge conflicts 2023-06-21 11:44:07 +07:00
db019178b7 Merge pull request #661 from akhilmhdh/feat/folder-doc
doc(folders): updated docs about folders
2023-06-20 13:27:28 -04:00
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
e6ad153e83 feat: option to choose target environment 2023-06-20 13:43:22 +02:00
9d33e4756b Add eslint rule and fix as many issues as possible 2023-06-19 23:42:04 -04:00
c267aee20f feat: interactive login 2023-06-19 22:25:21 +00:00
381e40f9a3 doc(folders): updated docs about folders 2023-06-19 22:38:38 +05:30
1760b319d3 cleanup 2023-06-19 16:00:53 +02:00
59737f89c1 fix: cloudlfare pages sync request fix 2023-06-19 15:44:41 +02:00
17097965d9 feat: cloudflare pages integration sync 2023-06-19 15:14:57 +02:00
1a54bf34ef feat: fix getApps and create for cloudflare pages integration 2023-06-19 13:58:38 +02:00
7e8ba077ae fix: terminal text alignment 2023-06-19 09:32:55 +00:00
6ca010e2ba Merge branch 'Infisical:main' into cloudflare-pages-integration 2023-06-18 18:26:55 +02:00
e9eacc445d Merge pull request #650 from akhilmhdh/feat/integrations-page
Feat/integrations page
2023-06-17 10:06:30 +02:00
db12dafad2 Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-17 10:35:49 +07:00
75acda0d7d Add option to attach accessId onto integration auth middleware 2023-06-17 10:35:42 +07:00
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
149c58fa3e cli: switch from v2 secrets to v3 2023-06-16 17:49:25 -04:00
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
7f7e63236b fix: resolved dashboardpage latestKey undefined error 2023-06-16 20:45:31 +05:30
965a5cc113 update rate limits 2023-06-16 10:03:12 -04:00
5a4a36a06a fix: minor change 2023-06-16 13:20:17 +00:00
dd0fdea19f fix: included mfa login flow 2023-06-16 12:58:00 +00:00
af31549309 Update pairing-session link 2023-06-16 01:15:24 +01:00
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
43f2cf8dc3 bugfix(docs): remove duplicate api key header from API reference documentation 2023-06-15 16:49:50 -07:00
0aca308bbd Update README.md 2023-06-15 15:01:52 -07:00
ff567892f9 added empty state for integrations and terraform 2023-06-15 14:58:40 -07:00
15fc12627a minor style updates 2023-06-15 13:51:28 -07:00
a743c12c1b feat(folder-scoped-integrations): implemented ui for folders in integration page 2023-06-15 22:46:40 +05:30
2471418591 feat(folder-scoped-integration): implemented api changes for integrations to support folders 2023-06-15 22:46:39 +05:30
c77ebd4d0e Merge pull request #649 from Infisical/environment-paywall
Update implementation for environment limit paywall
2023-06-15 15:56:32 +01:00
ccaf9a9ffc Update implementation for environment limit paywall 2023-06-15 15:48:19 +01:00
381806d84b feat: initial getApps for Cloudflare Pages 2023-06-15 09:46:09 +02:00
391e37d49e fixed bugs with env and password reset 2023-06-14 21:27:37 -07:00
7088b3c9d8 patch refresh token cli 2023-06-14 17:32:01 -04:00
ccf0877b81 Revert "Revert "add refresh token to cli""
This reverts commit 6b0e0f70d299ed8bf4fa23e4d70f8426e0a40a5f.
2023-06-14 17:32:01 -04:00
9e9129dd02 feat: cli login via browser 2023-06-14 19:12:56 +00:00
0aa9390ece Merge pull request #647 from Budhathoki356/fix/typo
fix: minor typos in code
2023-06-14 14:51:44 -04:00
e47934a08a Merge branch 'main' into fix/typo 2023-06-14 14:47:22 -04:00
04b7383bbe fix: minor typos in code 2023-06-15 00:17:00 +05:45
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
82a026a426 Update refreshPlan to consider workspace 2023-06-14 12:28:01 +01:00
92647341a9 Update getPlan with workspace-specific consideration and add environmentLimit to returned plan 2023-06-14 11:52:48 +01:00
776cecc3ef create prod release action 2023-06-13 22:16:26 -04:00
a4fb2378bb wait for helm upgrade before mark complete 2023-06-13 22:06:53 -04:00
9742fdc770 rename docker image 2023-06-13 22:00:51 -04:00
786778fef6 isolate gamma environment 2023-06-13 21:56:15 -04:00
3f946180dd add terraform docs 2023-06-13 18:28:41 -04:00
b1b32a34c9 feat(folder-sec-overview): made folder cell fully select 2023-06-13 20:16:14 +05:30
3d70333f9c Update password-reset email response 2023-06-13 15:31:55 +01:00
a6cf7107b9 feat(folder-sec-overview): implemented folder based ui for sec overview 2023-06-13 19:26:33 +05:30
d590dd5db8 feat(folder-sec-overview): added folder path support in get secrets and get folders 2023-06-13 19:26:33 +05:30
c64cf39b69 feat: cloudflare pages integration create page 2023-06-13 12:37:07 +02:00
f4404f66b8 Correct link to E2EE API usage example 2023-06-13 11:30:47 +01:00
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
e24c1f38e0 Add REST API integration option in docs introduction 2023-06-13 11:23:13 +01:00
dffcee52d7 feat: cloudflare integration auth page 2023-06-13 11:52:40 +02:00
db28536ea8 feat: add clouflare pages button to integrations page 2023-06-13 11:12:12 +02:00
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
37d2d580f4 Improve API docs for non-E2EE 2023-06-13 10:02:10 +01:00
41dd2fda8a Changed the intercom to aprovider model 2023-06-12 21:42:29 -07:00
22ca4f2e92 Fixed the typeerror issue 2023-06-12 20:56:19 -07:00
5882eb6f8a Merge pull request #639 from Infisical/intercom-tour
Switched intercom to AppLayout
2023-06-12 20:20:06 -07:00
c13d5e29f4 add intercom env replace during start up 2023-06-12 16:19:27 -07:00
d99c54ca50 Switched intercom to layout 2023-06-12 15:38:12 -07:00
9dd0dac2f9 Patch frontend lint error 2023-06-12 18:07:15 +01:00
98efffafaa Patch subscription plan frontend validation 2023-06-12 17:47:32 +01:00
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
553cf11ad2 Fix lint issue 2023-06-12 12:16:23 +01:00
4616cffecd Add support for read/write non-e2ee secrets 2023-06-12 12:04:28 +01:00
39feb9a6ae Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-11 19:24:38 -07:00
82c1f8607d Added intercom 2023-06-11 19:23:30 -07:00
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
1dea6749ba Allow custom environments in self-hosted instances 2023-06-11 18:19:01 -05:00
631eac803e Finish preliminary v3/secrets/raw endpoints 2023-06-11 12:11:25 +01:00
facabc683b Fix merge conflicts 2023-06-10 11:07:31 +01:00
4b99a9ea93 Merge pull request #633 from akhilmhdh/feat/folders-service-token
Folder scoped service token
2023-06-10 11:02:16 +01:00
445afb397c feat(folder-scoped-st): added batch,create secrets v2 secretpath support and service token 2023-06-10 12:10:43 +05:30
7d554f46d5 feat(folder-scoped-st): changed text css transformation in folders 2023-06-10 12:09:43 +05:30
bbef7d415c remove old commit 2023-06-09 18:41:10 -07:00
bb7b398fa7 throw unauthorized error instead of 500 for permission denied 2023-06-09 18:40:41 -07:00
570457c7c9 check path before service token create 2023-06-09 18:38:39 -07:00
1b77b1d70b fixed the etxt issue 2023-06-09 17:02:41 -07:00
0f697a91ab updated the workspace limit 2023-06-09 16:14:35 -07:00
df6d23d1d3 fixed the ts error 2023-06-09 15:31:38 -07:00
0187d3012b Add non-e2ee option for getSecret, getSecrets, start createSecret 2023-06-09 21:20:12 +01:00
4299a76fcd changed the default envs 2023-06-09 12:52:44 -07:00
2bae6cf084 lots of frontend improvements 2023-06-09 12:50:17 -07:00
22beebc5d0 feat(folder-scoped-st): implemented frontend ui for folder scoped service token 2023-06-09 23:44:33 +05:30
6cb0a20675 feat(folder-scoped-st): implemented backend api for folder scoped service tokens 2023-06-09 23:44:33 +05:30
00fae0023a Add cluster URL image to docs for Vault integration 2023-06-09 15:57:47 +01:00
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
00dfcfcf4e Finish preliminary Vault integration, made docs for Vault and Checkly 2023-06-09 15:36:37 +01:00
f5441e9996 Merge branch 'main' of https://github.com/Infisical/infisical 2023-06-08 11:08:48 -07:00
ee2fb33b50 changed the docs order 2023-06-08 11:08:27 -07:00
c51b194ba6 Merge pull request #629 from Infisical/optimize-checkly
Optimize Checkly integration
2023-06-08 11:21:28 +01:00
2920ba5195 Update Checkly envars only if changed 2023-06-08 11:18:23 +01:00
cd837b07aa Remove Sentry, part-try-catch from sync Checkly 2023-06-08 11:04:34 +01:00
a8e71e8170 Merge pull request #627 from Infisical/checkly-integration
Checkly integration
2023-06-08 10:56:19 +01:00
5fa96411d6 Merge branch 'main' into checkly-integration 2023-06-08 10:53:10 +01:00
329ab8ae61 Add +devices for verifyMfaToken user 2023-06-08 00:58:23 +01:00
3242d9b44e Fix change password button active state on no errors 2023-06-08 00:28:51 +01:00
8ce48fea43 Fix change password button active state on no errors 2023-06-08 00:27:59 +01:00
b011144258 reduce password forgot limit 2023-06-07 16:27:16 -07:00
674828e8e4 Copy data folder into backend build folder 2023-06-07 23:57:37 +01:00
c0563aff77 Bring back try-catch for initGlobalFeatureSet 2023-06-07 23:13:25 +01:00
7cec42a7fb Merge pull request #628 from Infisical/pentest-remediation
Fix issues/bugs
2023-06-07 22:52:08 +01:00
78493d9521 Fix lint errors 2023-06-07 22:47:47 +01:00
49b3e8b538 comment fixes 2023-06-07 13:12:58 -07:00
a3fca200fc comment fixes 2023-06-07 13:12:21 -07:00
158eb584d2 integration with checkly done 2023-06-07 13:11:39 -07:00
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
604810ebd2 fix(ui): resolved reloading when form submission 2023-06-07 22:45:50 +05:30
d4108d1fab update email docs for self hosting 2023-06-07 10:13:43 -07:00
4d6ae0eef8 Merge remote-tracking branch 'origin' into pentest-remediation 2023-06-07 16:30:13 +01:00
8193490d7f Merge pull request #624 from Infisical/stabilize-server-try-catch
Bring back express-async-errors
2023-06-07 16:27:16 +01:00
0deba5e345 Bring back express-async-errors 2023-06-07 16:25:13 +01:00
a2055194c5 Fix merge conflicts 2023-06-07 13:12:54 +01:00
8c0d643a37 Fix merge conflicts 2023-06-07 12:58:24 +01:00
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
04765ffb94 update email setup docs 2023-06-06 23:27:15 -07:00
6b9aa200b5 login/signup styling fixes 2023-06-06 19:41:02 -07:00
5667e47b31 Add default rely on Cloudflare for IP addresses 2023-06-07 00:50:25 +01:00
a8ed187443 Add check for most common passwords 2023-06-07 00:06:35 +01:00
c5be497052 Strengthen password requirement 2023-06-06 23:06:44 +01:00
77d47e071b add folder id to versions in batch update 2023-06-06 13:31:08 -07:00
4bf2407d13 remove encryptionKey validation check 2023-06-06 09:43:11 -07:00
846f5c6680 Upgraded JWT invalidation/session logic to separate TokenVersion model. 2023-06-06 16:36:52 +01:00
6f1f07c9a5 Merge branch 'main' into removing-sentry-logs 2023-06-06 15:17:59 +01:00
aaca66e5a4 Patch support for ENCRYPTION_KEY and ROOT_ENCRYPTION_KEY in generateSecretBlindIndexHelper 2023-06-06 14:24:06 +01:00
b9dad5c3f0 Begin preliminary tokenVersion impl 2023-06-06 11:25:08 +01:00
3a79a855cb Merge pull request #622 from Infisical/folder-patch-v2
Patch backfill data
2023-06-05 23:14:14 -07:00
e28d0cbace bring back tags to secret version 2023-06-05 23:12:48 -07:00
c0fbe82ecb update populate number 2023-06-05 20:21:09 -07:00
b0e7304bff Patch backfill data 2023-06-05 20:19:15 -07:00
5a1b6acc93 Fix merge conflicts with auth changes 2023-06-05 21:27:01 +01:00
5f5ed5d0a9 Change export convention for helper functions 2023-06-05 21:00:23 +01:00
bfee0a6d30 feat: remove try-catch blocks for handling errors in middleware 2023-06-05 21:15:35 +03:00
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
6dee858154 feat(folders): resolved auth issues and added the env dropdown change inside folders 2023-06-05 20:47:58 +05:30
0c18bd71c4 Implement preliminary pentest remediations 2023-06-05 00:44:10 +01:00
b9dfff1cd8 add migration complete logs 2023-06-04 16:37:57 -07:00
44b9533636 Merge pull request #609 from akhilmhdh/feat/folders
Feat/folders
2023-06-04 16:24:43 -07:00
599c8d94c9 Merge pull request #620 from Infisical/single-rate-limit-store
use mongo rate limit store
2023-06-04 13:45:56 -07:00
77788e1524 use mongo rate limit store 2023-06-04 13:43:57 -07:00
3df62a6e0a patch dup email bug for login 2023-06-04 11:07:52 -07:00
e74cc471db fix(folders): changed to secret path in controllers for get by path op 2023-06-04 13:26:20 +05:30
58d3f3945a feat(folders): removed old comments 2023-06-04 13:22:29 +05:30
29fa618bff feat(folders): changed / to root in breadcrumbs for folders 2023-06-04 13:18:05 +05:30
668b5a9cfd feat(folders): adopted new strategy for rollback on folders 2023-06-04 13:18:05 +05:30
6ce0f48b2c fix(folders): fixed algorithm missing in rollback versions and resolved env change reset folderid 2023-06-04 13:18:05 +05:30
467e85b717 Minor style changes 2023-06-04 13:18:05 +05:30
579516bd38 feat(folders): implemented ui for folders in dashboard 2023-06-04 13:18:05 +05:30
deaa85cbe7 feat(folders): added support for snapshot by env and folder 2023-06-04 13:18:05 +05:30
08a4404fed fixed the email issue 2023-06-03 13:58:53 -07:00
73aa01c568 prevent passport init when envs are undefined 2023-06-03 12:20:04 -07:00
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
f619ee7297 revise self hosting order 2023-06-02 14:10:40 -07:00
bb825c3d68 add DO docs link 2023-06-02 14:06:04 -07:00
6bbd7f05a2 add digital ocean docs 2023-06-02 14:05:00 -07:00
4865b69e6d updated the slack link 2023-06-01 13:54:02 -07:00
6f3c7c0fbf fix ts issues 2023-05-31 11:29:08 -07:00
be80b5124f Final style changes to login/signup 2023-05-31 11:25:37 -07:00
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
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
94141fedd6 Update contributing docs, add pull requests section 2023-05-31 12:25:08 +03:00
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
1a1693dbbf Add encryption key validation to validation script 2023-05-30 20:46:20 +03:00
9440afa386 Remove re-encryption from Infisical, move to migration script 2023-05-30 20:30:58 +03:00
8b1ec1424d patch quote type in docs 2023-05-30 11:19:15 -04:00
bd56f3b64c update docker run command for self host 2023-05-30 11:14:22 -04:00
f20ea723b7 Merge branch 'main' into feature/google-signin-signup-integration 2023-05-30 20:46:20 +08:00
86f76ebe70 no default user for selfhosting docs 2023-05-30 08:39:43 -04:00
821385c2f3 Revert "add prod img publish ste p"
This reverts commit f7dbd41431b4f3459161c16667951e6b2005daa4.
2023-05-30 07:29:45 -04:00
03c65c8635 Revert "add dummy step"
This reverts commit 893c4777fea475e905b97ef8bcfb2feb21a37154.
2023-05-30 07:29:31 -04:00
893c4777fe add dummy step 2023-05-29 18:49:14 -04:00
f7dbd41431 add prod img publish ste p 2023-05-29 18:46:04 -04:00
8d1f3e930a revert keychain name 2023-05-29 18:08:49 -04:00
f25715b3c4 update keychain name 2023-05-29 17:36:07 -04:00
37251ed607 Begin migration script for re-encryption 2023-05-29 23:46:16 +03:00
c078fb8bc1 allow user to create new keychain 2023-05-29 15:56:48 -04:00
2ae3c48b88 Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-29 14:21:27 +03:00
ce28151952 Update posthog-js version 2023-05-29 14:21:16 +03:00
e6027b3c72 removed try catch from requireAuth middleware 2023-05-29 18:14:16 +08:00
7f4db518cc Merge branch 'main' into feature/google-signin-signup-integration 2023-05-29 18:00:43 +08:00
7562e7d667 Merge pull request #607 from Infisical/changelog
Add preliminary changelog to docs
2023-05-29 12:18:27 +03:00
5c8f33a2d8 Add preliminary changelog to docs 2023-05-29 12:15:47 +03:00
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
50609d06f5 Final style changes to signup 2023-05-28 14:22:37 -07:00
3a5ad93450 Final style changes to signup 2023-05-28 14:22:17 -07:00
8493d51f5c Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-28 22:23:51 +03:00
e90f63b375 Install and require express-async-errors earlier 2023-05-28 22:23:26 +03:00
af9ffdc51f delete pre commit (pre-commit.com) 2023-05-28 14:36:07 -04:00
3a76a82438 add dummy ENCRYPTION_KEY for testing backend docker img 2023-05-28 14:09:32 -04:00
8e972c704a resolved error handling issue with requireAuth middleware 2023-05-28 23:06:20 +08:00
b975115443 Merge branch 'main' into feature/google-signin-signup-integration 2023-05-28 22:12:02 +08:00
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
01b87aeebf Add pagination to retrieve envars for GitLab integration 2023-05-28 16:46:05 +03:00
cea3b59053 Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-27 19:12:47 -07:00
a6f6711c9a posthog attribution adjustment 2023-05-27 19:12:32 -07:00
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
bfbe2f2dcf brought the button back down and removed side bar for other browsers 2023-05-26 23:08:57 -07:00
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
6b0e0f70d2 Revert "add refresh token to cli" 2023-05-26 16:56:02 -04:00
1fb9aad08a Revert "only re-store user creds when token expire"
This reverts commit df9efa65e7cc523723cd19902f4d183a464022bb.
2023-05-26 16:55:29 -04:00
61a09d817b Merge pull request #604 from Infisical/revised-encryption-key
Update dummy variables in test
2023-05-26 17:31:59 +03:00
57b8ed4eef Merge remote-tracking branch 'origin' into revised-encryption-key 2023-05-26 17:29:54 +03:00
c3a1d03a9b Update test dummy variables 2023-05-26 17:29:23 +03:00
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
200d9de740 Fix merge conflicts 2023-05-26 16:41:17 +03:00
17060b22d7 Update README.md 2023-05-25 21:24:07 -07:00
c730280eff Update FeatureSet interface to include used counts 2023-05-26 00:26:16 +03:00
c45120e6e9 add shorter env name for file vault 2023-05-25 13:27:20 -04:00
c96fbd3724 fix(ui): fixing scroll on project list selection 2023-05-25 19:44:06 +05:30
e1e2eb7c3b Add SecretBlindIndexData for development user initialization 2023-05-25 16:07:08 +03:00
7812061e66 Update isPaid telemetry accounting to be tier-based instead of via slug 2023-05-25 12:59:18 +03:00
ca41c65fe0 small helm doc changes 2023-05-24 23:46:34 -04:00
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
df9efa65e7 only re-store user creds when token expire 2023-05-24 19:46:02 -04:00
1c5616e3b6 revise pre commit doc 2023-05-24 19:11:33 -04:00
27030138ec Merge pull request #601 from Infisical/add-refresh-token-cli
add refresh token to cli
2023-05-24 18:53:52 -04:00
c37ce4eaea add refresh token to cli 2023-05-24 18:51:42 -04:00
5aa367fe54 fix(ui): fixed tags overflow in card + port correction in README 2023-05-24 23:03:12 +05:30
fac4968193 moved oauth controller endpoints to auth 2023-05-24 23:44:30 +08:00
17647587f9 remove tests for time being 2023-05-24 10:48:11 -04:00
f3dc7fcf7b add timout to pull requests 2023-05-24 10:48:11 -04:00
93cf7cde2d fixed login issue after mfa 2023-05-24 21:33:24 +08:00
422d04d7d7 migrated to standard request 2023-05-24 18:50:59 +08:00
4c41d279e9 Merge branch 'main' into feature/google-signin-signup-integration 2023-05-24 18:39:18 +08:00
e65c6568e1 Modify convention for PostHog isPaid attr to be tier-based instead of slug 2023-05-24 10:26:06 +03:00
9d40a96633 Update README.md 2023-05-23 20:22:01 -04:00
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
d0d6419d4d add pre commit install command to README.md 2023-05-23 20:20:10 -04:00
8b05ce11f7 add pre commit to husky 2023-05-23 20:15:39 -04:00
a7fb0786f9 improve pre commit docs 2023-05-23 19:45:10 -04:00
f2de1778cb catch case when hook path is default 2023-05-23 19:34:31 -04:00
952cf47b9a Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-23 15:41:43 -07:00
1d17596af1 added boolean flag for the plan in posthog logging 2023-05-23 15:41:34 -07:00
01385687e0 make posthog failed calls level=debug 2023-05-23 18:16:13 -04:00
d2e3aa15b0 patch standalone docker image 2023-05-23 17:16:32 -04:00
96607153dc Modularize getOrganizationPlan function 2023-05-23 23:54:54 +03:00
a8502377c7 Add endpoint for updating organization plan 2023-05-23 23:14:20 +03:00
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
83dd35299c Added add/remove/get organization payment methods and get cloud plans from license server 2023-05-23 22:28:41 +03:00
b5b2f402ad add missing required envrs 2023-05-23 14:09:45 -04:00
ec34572087 patch invite only 2023-05-23 13:18:21 -04:00
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
899d46514c Add forwarding usedSeats and subscription quantity to license server on org member add/delete 2023-05-23 16:59:13 +03:00
658df21189 Add auto install pre commit 2023-05-23 00:09:00 -04:00
51914c6a2e resolved package-lock conflicts 2023-05-22 22:07:30 +08:00
ad37a14f2e Merge branch 'main' into feature/google-signin-signup-integration 2023-05-22 21:54:51 +08:00
8341faddc5 Add support for pulling plan details from license server with LICENSE_KEY, LICENSE_SERVER_KEY 2023-05-22 15:43:33 +03:00
8e3a23e6d8 fix prod node img for standalone 2023-05-22 08:18:50 -04:00
bc61de4a80 add provider auth secret to kubernetes and docker yaml 2023-05-20 23:15:36 +08:00
1c89474159 hello 2023-05-19 17:23:15 -04:00
2f765600b1 add pre-commit hook 2023-05-19 17:20:27 -04:00
d9057216b5 remove keyring access during telemetry 2023-05-19 16:10:59 -04:00
6aab90590f add version to cli run telemtry 2023-05-19 12:24:49 -04:00
f7466d4855 update cli telemetry 2023-05-19 12:20:37 -04:00
ea2565ed35 Merge pull request #591 from Infisical/cli-telemetry
Cli telemetry
2023-05-19 10:55:27 -04:00
4586656b85 add post hog api to go releaser and update cli telemetry 2023-05-19 10:49:57 -04:00
e4953398df add telemetry to cli 2023-05-19 00:16:26 -04:00
7722231656 Merge pull request #590 from Infisical/infisical-scan-docs
Infisical scan docs
2023-05-18 15:59:51 -04:00
845a476974 add secret scanning to README.md 2023-05-18 15:57:48 -04:00
fc19a17f4b update readme with scaning feature 2023-05-18 15:42:25 -04:00
0890b1912f Merge pull request #589 from Infisical/infisical-scan-docs
add docs for infisical scan
2023-05-18 15:20:26 -04:00
82ecc2d7dc add secret scanning to resources 2023-05-18 15:18:29 -04:00
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
446a63a917 add docs for infisical scan 2023-05-18 14:55:39 -04:00
d67cb7b507 Merge pull request #588 from Infisical/add-gitleak
rebrand and small tweeks
2023-05-18 12:07:26 -04:00
353ff63298 rebrand and small tweeks 2023-05-18 12:04:17 -04:00
4367822777 re-added token caching and redirection 2023-05-18 23:04:55 +08:00
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
ca4a9b9937 resolved MFA not appearing 2023-05-18 02:07:07 +08:00
ec8d62d106 show toast when oauth login error 2023-05-18 01:58:35 +08:00
8af8a1d3d5 Merge pull request #580 from Infisical/add-gitleak
add gitleak to cli
2023-05-17 13:20:40 -04:00
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
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
8249043826 add testing files and create create scan command 2023-05-17 13:08:00 -04:00
6ca3b8ba61 handled error cases for external auth login 2023-05-18 00:39:20 +08:00
20294ee233 Fixed the const issue 2023-05-17 09:27:12 -07:00
4b2e91da74 added proper error handling for user creation 2023-05-18 00:12:28 +08:00
fac8affe78 added missing envs for documentation 2023-05-17 22:24:04 +08:00
1ccec486cc removed caching of providerAuthToken 2023-05-17 21:43:18 +08:00
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
429bfd27b2 Add support for custom environments in GitLab integration 2023-05-17 14:25:18 +03:00
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
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
a9b642e618 Merge branch 'main' of https://github.com/Infisical/infisical 2023-05-15 16:34:02 -07:00
919ddf5de2 removed console log 2023-05-15 16:33:44 -07:00
89a89af4e6 improving UX for the onboarding experience 2023-05-15 16:33:11 -07:00
b3e68cf3fb add gitleak to cli 2023-05-15 19:31:36 -04:00
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
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
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
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
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
e791684f4d fix: docker-compose failing due to missing frontend i18n file 2023-05-16 00:19:03 +05:30
6746f04f33 added self-hosting documentation for google 2023-05-15 23:39:19 +08:00
d32c5fb869 update the dev stripe product id 2023-05-15 07:31:17 -07:00
dba19b4a1d Merge branch 'main' into feature/google-signin-signup-integration 2023-05-15 20:41:08 +08:00
884aed74a5 made last name optional 2023-05-15 20:39:45 +08:00
abbf1918dc Added limits to the number of projects in an org 2023-05-14 18:25:27 -07:00
9dc7cc58a7 uncommented code 2023-05-15 00:41:27 +08:00
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
6d70dc437e update cli usage docs 2023-05-13 11:22:38 -04:00
174e22a2bc put aliases docs in Accordion 2023-05-13 11:17:17 -04:00
6f66b56e7c updated package-lock 2023-05-12 22:24:38 +08:00
be2bac41bb Merge branch 'main' into feature/google-signin-signup-integration 2023-05-12 22:22:51 +08:00
f4815641d8 fixed the bug with smaller icon buttons 2023-05-11 18:11:04 -07:00
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
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
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
0afa44a9f0 removed express-session types 2023-05-11 15:39:04 +08:00
781e0b24c8 add docs for spring boot maven 2023-05-10 21:08:38 -04:00
5a99878d15 Final style edits to the login and signup flows 2023-05-10 16:12:57 -07:00
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
967e520173 More designed changes to login flow 2023-05-10 12:55:08 -07:00
ccfe0b1eb9 reverted changes made to nginx config 2023-05-11 00:52:49 +08:00
0ef5779776 add providerAuthToken for MFA login 2023-05-11 00:47:16 +08:00
a194e90644 removed session references 2023-05-11 00:41:38 +08:00
addc849fa6 changed google-auth strategy and removed session use 2023-05-11 00:37:02 +08:00
074c0bdd77 utilized mongodb as persistent store for sessions 2023-05-10 23:01:44 +08:00
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
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
7ee33e9393 resolved merge conflict issues and updated use of translations 2023-05-10 21:39:11 +08:00
32cef27e8e Merge branch 'main' into feature/google-sign 2023-05-10 21:11:42 +08:00
1fce8cc769 More style changes to login 2023-05-09 23:24:37 -07:00
4e7145dfe5 Style changes to login 2023-05-09 20:45:59 -07:00
9cb4d5abb7 improve docker compose and add standalone docs 2023-05-09 22:07:48 -04:00
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
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
12c399d4a9 fix typo in k8 docs 2023-05-09 17:34:20 -04:00
ecd17e1d6d refine k8 deploy docs 2023-05-09 17:32:57 -04:00
fb4c811414 update detailed kubernetes helm docs 2023-05-09 16:41:20 -04:00
3561c589b1 refactor(ui): changed frontend to normal i18n without SSR 2023-05-09 23:28:23 +05:30
420d71d923 add membership validate to folder get 2023-05-09 10:23:41 -04:00
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
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
43e61c94f0 get folder by id 2023-05-08 21:01:26 -04:00
69fa4a80c5 update check for CLI update 2023-05-08 16:43:28 -04:00
cf9e8b8a6b patch login bug when override empty 2023-05-08 16:09:57 -04:00
c6d5498a42 add dangling prefix for aur 2023-05-08 10:59:24 -04:00
7aa5ef844c Update CLI usage docs to showcase the ability to inject environment variables in shell aliases 2023-05-08 01:04:35 -04:00
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
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
d8ff0bef0d add semantic-version gh action 2023-05-07 19:09:24 -04:00
29b96246b9 add back osx cross build 2023-05-07 17:00:12 -04:00
8503c9355b add completions for aurs 2023-05-07 16:55:42 -04:00
ddf0a272f6 back out of dir for archive file 2023-05-07 15:41:23 -04:00
e3980f8666 bring back completions and man page for cli 2023-05-07 15:27:19 -04:00
d52534b185 Dashboard UI update 2023-05-07 12:24:40 -07:00
4c434555a4 finalized signup/signin ux regarding redirects 2023-05-07 20:54:30 +08:00
f011d61167 Merge remote-tracking branch 'origin' into revised-encryption-key 2023-05-06 22:22:03 +03:00
db07a033e1 Add filter query param to getApps for Netlify integration 2023-05-06 22:19:00 +03:00
87e047a152 Checkpoint finish preliminary support for ROOT_ENCRYPTION_KEY 2023-05-06 22:07:59 +03:00
ea86e59d4f resolved component alignment of signup 2023-05-06 19:26:42 +08:00
3e19e6fd99 finalized login and signup ui 2023-05-06 18:52:01 +08:00
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
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
2eff7b6128 set internal port for render 2023-05-05 16:26:03 -04:00
d8a781af1f remove health check 2023-05-05 16:06:28 -04:00
8b42f4f998 typo in doc 2023-05-05 15:50:19 -04:00
da127a3c0a update step 2 of fly.io 2023-05-05 15:48:03 -04:00
d4aa75a182 update self hosting docs layout 2023-05-05 15:42:41 -04:00
d097003e9b set sync=false for mongo db url render 2023-05-05 14:37:24 -04:00
b615a5084e update render IaC template 2023-05-05 14:31:11 -04:00
379f086828 add render IaC 2023-05-05 14:28:25 -04:00
f11a7d0f87 fix(ui): resolved token missing due to cache invalidation 2023-05-05 21:56:26 +05:30
f5aeb85c62 rename standalone docker image 2023-05-05 08:43:57 -04:00
3d3d7c9821 Merge remote-tracking branch 'origin' into revised-encryption-key 2023-05-05 10:27:44 +03:00
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
b1f2515731 fixed minor bugs and updated the design 2023-05-04 15:31:06 -07:00
c5094ec37d patch copy invite link 2023-05-04 18:27:09 -04:00
6c745f617d add org id to complete invite link 2023-05-04 17:50:36 -04:00
5eeda6272c Checkpoint adding crypto metadata 2023-05-04 20:35:06 +03:00
b734b51954 developed new ui for new login and signup page 2023-05-05 00:39:28 +08:00
82995fbd02 feat(ui): fixed lagging issues with new dashboard 2023-05-04 20:45:26 +05:30
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
38f578c4ae Fixed the issue with favicon 2023-05-03 16:06:50 -07:00
65b12eee5e update standlone gwf 2023-05-03 17:22:32 -04:00
9043db4727 add github workflow to release standalone app 2023-05-03 17:14:24 -04:00
0eceeb6aa9 create standalone infisical docker file 2023-05-03 16:57:19 -04:00
2d2bbbd0ad Update README.md 2023-05-03 15:51:15 -04:00
c9b4e11539 add note to ENCRYPTION_KEY to indicate non prod 2023-05-03 15:48:20 -04:00
fd4ea97e18 remove default smtp since Infisical no longer requires SMTP 2023-05-03 15:45:16 -04:00
49d2ecc460 switch install command to run prod docker compose 2023-05-03 15:41:11 -04:00
1172726e74 added signup v3 endpoints and developed initial new signup flow 2023-05-04 01:32:40 +08:00
c766686670 Fix merge conflicts for variable imports 2023-05-03 19:30:30 +03:00
ca31a70032 Merge pull request #550 from Infisical/gmail-smtp-support
Add support for Gmail SMTP + docs
2023-05-03 18:34:49 +03:00
3334338eaa Add Gmail SMTP option + docs 2023-05-03 18:28:20 +03:00
099cee7f39 Begin refactoring backfilling and preparation operations into setup and start adding encryption metadata to models 2023-05-03 14:21:42 +03:00
f703ee29e5 implemented comments 2023-05-03 18:58:32 +08:00
6d5e281811 add helm version requirement 2023-05-02 11:11:41 -04:00
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
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
837ea2ef40 add sem var to docker image workflow 2023-05-01 20:43:28 -04:00
b462ca3e89 Patch missing function invocation for GitLab envar 2023-05-01 22:38:01 +03:00
f639f682c9 Merge pull request #458 from Spelchure/removing-sentry-logs
Replace Sentry error handling logic
2023-05-01 22:35:11 +03:00
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
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
21eb1815c4 feat: remove try-catch blocks for handling errors in middleware 2023-05-01 17:14:39 +03:00
85f3ae95b6 Merge pull request #546 from Infisical/update-docs
Add local machine to deployment options
2023-05-01 16:04:11 +03:00
e888eed1bf Add local machine to deployment options 2023-05-01 16:02:46 +03:00
addac63700 fix broken link for start guide 2023-04-30 15:56:10 -04:00
efd13e6b19 remove completions 2023-04-30 15:49:12 -04:00
4ac74e6e9a add back completion with dir 2023-04-30 15:36:55 -04:00
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
8ba3f8d1f7 Merge branch 'main' into docs-guides 2023-04-30 22:25:22 +03:00
6b83393952 Add initial Node, Python, Nextjs + Vercel guides to docs, delete README translations 2023-04-30 22:21:34 +03:00
da07d71e15 remove completions 2023-04-30 12:42:21 -04:00
82d3971d9e Update README.md 2023-04-30 09:07:25 -07:00
3dd21374e7 update go releaser distribution 2023-04-30 11:40:19 -04:00
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
9f0313f50b strip v from existing tags 2023-04-30 11:28:55 -04:00
a6e670e93a update tag fetch method to filetr for cli tags only 2023-04-30 11:22:28 -04:00
ec97e1a930 add mono repo support for goreleaser 2023-04-30 11:09:29 -04:00
55ca6938db update cli github action to only listen to infisical-cli/{version} tags 2023-04-30 11:08:58 -04:00
1401c7f6bc add go releaser pro 2023-04-30 10:32:39 -04:00
bb6d0fd7c6 Patch .secretValue access in INVITE_ONLY_SIGNUP 2023-04-30 14:56:27 +03:00
689a20dca2 Begin adding guides to docs 2023-04-30 14:54:54 +03:00
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
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
89e5f644a4 Update README.md 2023-04-29 15:13:27 -04:00
c5619d27d7 Merge pull request #542 from Infisical/revise-readme
Updated README
2023-04-29 21:43:17 +03:00
12a1d8e822 Update README 2023-04-29 21:41:33 +03:00
a85a7d1b00 Update README 2023-04-29 21:23:05 +03:00
fc2846534f Update README 2023-04-29 21:06:25 +03:00
2b605856a3 Update README 2023-04-29 20:55:52 +03:00
191582ef26 Merge pull request #541 from Infisical/revise-quickstart
Add quickstarts to documentation
2023-04-29 20:40:34 +03:00
213b5d465b Merge remote-tracking branch 'origin' into revise-quickstart 2023-04-29 20:39:30 +03:00
75f550caf2 Finish documentation quickstarts update 2023-04-29 20:38:58 +03:00
daabf5ab70 add k8 quick start 2023-04-29 12:24:03 -04:00
7b11976a60 Preliminary README change proposal 2023-04-29 18:55:27 +03:00
39be52c6b2 make minor changes to wording for quick start guide 2023-04-29 11:27:04 -04:00
bced5d0151 Complete preliminary new quickstarts 2023-04-29 14:39:22 +03:00
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
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
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
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
2aa79d4ad6 Merge pull request #518 from seonggwonyoon/main
Add namespace option for using helm
2023-04-28 14:18:38 -04:00
44b4de754a remove test check in workflow 2023-04-28 13:21:38 -04:00
db0f0d0d9c disable secrets integ tests temp 2023-04-28 13:18:25 -04:00
3471e387ae Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-28 10:11:36 -07:00
aadd964409 Fix the deployment issue 2023-04-28 10:11:25 -07:00
102e45891c Update getAppsGitHub to include pagination 2023-04-28 20:10:29 +03:00
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
e5cb0cbca3 Add preliminary platform, sdks, and cli quickstarts 2023-04-28 14:30:13 +03:00
330968c7af added gradient to the menu 2023-04-27 19:46:01 -07:00
68e8e727cd Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-27 18:44:46 -07:00
3b94ee42e9 Animated menu icons 2023-04-27 18:44:23 -07:00
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
04a9604ba9 add advanced use cases for hostAPI 2023-04-27 11:14:47 -04:00
dfb84e9932 developed initial version of new login page 2023-04-27 23:10:27 +08:00
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
fc53c094b7 Merge branch 'main' into snyk-upgrade-9829915033f54fef09ffef896e2c5908 2023-04-27 09:57:49 -04:00
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
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
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
c3a47597b6 fix formatting 2023-04-27 23:31:33 +10:00
a696a99232 add backend service inof to doc 2023-04-27 23:28:19 +10:00
8b1e64f75e Merge pull request #529 from Infisical/python-sdk-docs
Finish Python SDK docs
2023-04-27 15:57:19 +03:00
f137087ef1 Finish Python SDK docs 2023-04-27 15:53:23 +03:00
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
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
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
4ac13f61e0 Update README.md 2023-04-26 12:05:13 -07:00
3d2b0fa3fc Update docker-image.yml 2023-04-26 15:03:31 -04:00
2dd1570200 updated use of environment variables to utilize await 2023-04-27 02:00:16 +08:00
69472514af Merge branch 'main' into feature/google-signin-signup-integration 2023-04-27 01:46:26 +08:00
f956170820 added auth v3 endpoints for login1 and login2 2023-04-27 01:38:06 +08:00
242809ce26 add folders to batch and get secrets api 2023-04-26 12:53:14 -04:00
492bf39243 Clarify getSecret and caching behavior in docs 2023-04-26 12:11:46 +03:00
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
3fd2e22cbd Move Express example for Node SDK to top of that docs page 2023-04-26 11:53:46 +03:00
150eb1f5ee Merge remote-tracking branch 'origin' into update-node-sdk 2023-04-26 11:51:21 +03:00
6314a949f8 Update Infisical to use Infisical Node SDK 1.1.3 2023-04-26 11:50:51 +03:00
660c5806e3 Merge pull request #523 from Infisical/revise-node-sdk-docs
Revise docs for Node SDK
2023-04-26 09:31:54 +03:00
c6d2828262 Merge remote-tracking branch 'origin' into revise-node-sdk-docs 2023-04-26 09:30:03 +03:00
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
7a3456ca1d scrolling fix 2023-04-25 19:25:31 -07:00
a946031d6f fix loading animation 2023-04-25 17:14:39 -07:00
f0075e8d09 add folder controller 2023-04-25 16:15:18 -04:00
007e8c4442 initial setup for google signin signup integration 2023-04-25 23:47:46 +08:00
3b00df6662 Updated readme 2023-04-25 08:12:12 -07:00
a263d7481b Added truncation for secret names on the comparison screen 2023-04-25 08:11:31 -07:00
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
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
a5c5ec1f4d Print helm with namespace 2023-04-25 10:55:27 +09:00
cbb28dc373 Merge pull request #517 from satyamgupta1495/patch-3
Added country flag [India]
2023-04-24 15:31:37 +03:00
e00aad4159 Merge pull request #515 from satyamgupta1495/patch-2
Translated README.md in Hindi language
2023-04-24 15:30:57 +03:00
fb8aaa9d9f Added country flag [india] 2023-04-24 17:57:33 +05:30
4bda67c9f7 remove check for --env for service tokens 2023-04-24 05:16:08 -07:00
e5c5e4cca2 Updated readme.hi.md 2023-04-24 17:26:33 +05:30
803a97fdfc Translated README.md in Hindi language 2023-04-23 23:10:47 +05:30
9e42a7a33e Update quickstart example 2023-04-23 15:51:42 +03:00
7127b60867 Undo last README change 2023-04-23 14:06:28 +03:00
bcba2e9c2c Merge pull request #514 from satyamgupta1495/patch-1
Translated readme in Hindi Language
2023-04-23 14:02:18 +03:00
34c79b08bc Update InfisicalClient initialization 2023-04-23 13:38:36 +03:00
aacdaf4556 Modify Node SDK docs to be inline with new initializer 2023-04-23 12:45:13 +03:00
a7484f8be5 Update node SDK docs, positioning of examples 2023-04-23 09:49:21 +03:00
51154925fd Translated readme in Hindi Language 2023-04-23 03:18:16 +05:30
e1bf31b371 Update envars to new node SDK format 2023-04-22 16:20:33 +03:00
3817831577 Update docs for upcoming Node SDK update 2023-04-22 14:34:05 +03:00
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
03110c8a83 Update package-lock.json 2023-04-22 11:50:26 +03:00
e0d5644b3a Add back service token data select fields for GET endpoint 2023-04-22 11:47:23 +03:00
c7172337ed Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-20 21:29:05 -07:00
7183546e7e Fix dashboard bugs 2023-04-20 21:28:52 -07:00
d717430947 add faq for self hosting 2023-04-20 17:56:43 -07:00
5922921896 add error for adding incorrect env flag for service token 2023-04-20 15:44:42 -07:00
66ce269f42 add docs for infisical user command 2023-04-20 15:19:53 -07:00
f79e1d754d update prompt selections to const 2023-04-20 14:08:35 -07:00
5a906d412b show help for update and user sub command 2023-04-20 14:07:35 -07:00
1bb3115880 Merge pull request #481 from quinton11/feat/multi-profile
feat: CLI support for switching between multiple logged in user accounts
2023-04-20 14:00:23 -07:00
7d8c6eb6b7 Merge pull request #511 from Infisical/snyk-upgrade-aba197e4f121a17ffaf02cd20097245f
[Snyk] Upgrade @sentry/tracing from 7.45.0 to 7.46.0
2023-04-20 13:55:34 -07:00
4dd96704f0 Merge branch 'main' into snyk-upgrade-aba197e4f121a17ffaf02cd20097245f 2023-04-20 13:55:24 -07:00
2e428f9d66 Merge pull request #512 from Infisical/snyk-upgrade-93cddd3ce0262fa45d130dffdccf8932
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.301.0 to 3.303.0
2023-04-20 13:53:56 -07:00
7a926fbdac Merge pull request #513 from Infisical/snyk-upgrade-a9ff8e4e7d9befabd7d947ce7f0c25b3
[Snyk] Upgrade @sentry/node from 7.45.0 to 7.46.0
2023-04-20 13:53:46 -07:00
0d3999c7e5 fix: upgrade @sentry/node from 7.45.0 to 7.46.0
Snyk has created this PR to upgrade @sentry/node from 7.45.0 to 7.46.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-20 20:06:11 +00:00
24913217c6 fix: upgrade @aws-sdk/client-secrets-manager from 3.301.0 to 3.303.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.301.0 to 3.303.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-20 20:06:07 +00:00
c581fde65e fix: upgrade @sentry/tracing from 7.45.0 to 7.46.0
Snyk has created this PR to upgrade @sentry/tracing from 7.45.0 to 7.46.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-20 20:06:02 +00:00
fa9a7301d9 minor changes 2023-04-19 23:21:26 +00:00
3add40bfbd Minor changes 2023-04-19 19:49:14 +00:00
d4206cdbd8 login and user update commands support for existing domain override methods 2023-04-19 19:41:14 +00:00
3adbb7316a Merge pull request #510 from Infisical/snyk-upgrade-51cc31a6d1afe5b1d4d65d58bb609257
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.299.0 to 3.301.0
2023-04-19 09:52:05 -07:00
3e022346cd fix: upgrade @aws-sdk/client-secrets-manager from 3.299.0 to 3.301.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.299.0 to 3.301.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-19 16:44:21 +00:00
7fc01df93e Update package-lock.json 2023-04-19 18:18:38 +03:00
9f944135b9 Update docs for blind indices and secrets v3 endpoints 2023-04-19 18:15:35 +03:00
afdf971014 Fixed path in docs 2023-04-19 07:53:22 -07:00
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
c1b97841cf resolved review concerns 2023-04-19 11:29:04 +00:00
b0107d28d4 update chart value 2023-04-18 16:33:29 -07:00
9f1f709b57 add service token field in helmchart for k8 2023-04-18 16:32:27 -07:00
dd4c4e1473 make hostAPI optional 2023-04-18 16:32:27 -07:00
92e04c45e7 Update Chart.yaml 2023-04-18 15:28:12 -07:00
44a7eb8123 Merge pull request #503 from Infisical/service-accounts-with-k8-operator
update k8 operator to use service account
2023-04-18 14:49:00 -07:00
7a2192cf95 Merge pull request #505 from Infisical/snyk-upgrade-ae9971a130863ea0dd7614699a93f40b
[Snyk] Upgrade @sentry/node from 7.41.0 to 7.45.0
2023-04-18 14:26:49 -07:00
0ad8075197 Merge branch 'main' into snyk-upgrade-ae9971a130863ea0dd7614699a93f40b 2023-04-18 14:26:42 -07:00
b258cbd852 Merge pull request #506 from Infisical/snyk-upgrade-f7e4421cf1dcf4abd7da31a7f2f0269c
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.294.0 to 3.299.0
2023-04-18 14:25:44 -07:00
f1c2512600 Merge branch 'main' into snyk-upgrade-f7e4421cf1dcf4abd7da31a7f2f0269c 2023-04-18 14:25:37 -07:00
1348c94154 Merge pull request #504 from Infisical/snyk-upgrade-92cb55bf13238343efcdc817c6e6b2ce
[Snyk] Upgrade @sentry/tracing from 7.41.0 to 7.45.0
2023-04-18 14:24:58 -07:00
11ac5d18ff Merge branch 'main' into snyk-upgrade-92cb55bf13238343efcdc817c6e6b2ce 2023-04-18 14:24:52 -07:00
bb60e1d327 Merge pull request #507 from Infisical/snyk-upgrade-0aba917b89e37535cd36bc3e962221b0
[Snyk] Upgrade mongoose from 6.10.3 to 6.10.4
2023-04-18 14:24:21 -07:00
70668d7783 add docs for using k8 controller with service acounts 2023-04-18 13:36:30 -07:00
be2cf54d6e host API support for login and switch commands 2023-04-18 12:03:03 +00:00
acb90ee0f7 Add frontend migration support for existing project to be blind-indexed 2023-04-18 12:43:06 +03:00
b62ea41e02 Add workspaces v3 endpoints for blind-index naming/labeling 2023-04-17 23:48:48 +03:00
48cd2bddfe Rolled back the dashboard 2023-04-17 12:08:23 -07:00
884394866e Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-17 11:50:01 -07:00
44c716aba3 Fixing minor bugs in dashboard and billing 2023-04-17 11:49:49 -07:00
763ec1aa0f And workspace-environment specific integrations syncs to secrets v3 endpoints, add PostHog 2023-04-17 14:23:56 +03:00
338d287d35 Update package-lock.json 2023-04-17 11:11:18 +03:00
df83e8ceb9 Complete first iteration of CRUD secrets operations by name 2023-04-17 11:09:45 +03:00
8f08c4955f Update README.md 2023-04-16 22:26:31 -07:00
d1c62d655d Merge pull request #500 from sheensantoscapadngan/adjustment/allow-multiselect-for-secrets-deletion
[Adjustment][Sheen] removed prompt when deleting secret
2023-04-16 22:24:09 -07:00
8e2837c8e8 fix: upgrade mongoose from 6.10.3 to 6.10.4
Snyk has created this PR to upgrade mongoose from 6.10.3 to 6.10.4.

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-04-17 05:01:23 +00:00
aa27308f5a fix: upgrade @aws-sdk/client-secrets-manager from 3.294.0 to 3.299.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.294.0 to 3.299.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-17 05:01:18 +00:00
2d22c96a97 fix: upgrade @sentry/node from 7.41.0 to 7.45.0
Snyk has created this PR to upgrade @sentry/node from 7.41.0 to 7.45.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-17 05:01:13 +00:00
b4839eaac8 fix: upgrade @sentry/tracing from 7.41.0 to 7.45.0
Snyk has created this PR to upgrade @sentry/tracing from 7.41.0 to 7.45.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-17 05:01:06 +00:00
92df5e1a2f add docs for new k8 oper with service account 2023-04-16 21:04:28 -07:00
df2e0e03ff add service token to auth methods block for k8 2023-04-16 18:40:33 -07:00
5585893cfe allow service account to fetch secrets 2023-04-16 17:12:07 -07:00
e348e4678e remove unused method 2023-04-16 17:11:18 -07:00
4a36dcd1ed update helm and kubectl install manifests 2023-04-16 17:07:49 -07:00
619fe553ef update k8 operator to use service account 2023-04-16 16:51:36 -07:00
4c41a7f1cf Merge branch 'Infisical:main' into feat/multi-profile 2023-04-15 18:23:21 +00:00
04d46099f6 address package fixes 2023-04-15 10:02:10 -07:00
250428c64f Merge pull request #468 from Infisical/snyk-upgrade-3f3d5368cc3b2bbb1bc7ecf70c71c625
[Snyk] Upgrade @sentry/tracing from 7.39.0 to 7.41.0
2023-04-15 09:56:03 -07:00
d40758a43d Merge branch 'main' into snyk-upgrade-3f3d5368cc3b2bbb1bc7ecf70c71c625 2023-04-15 09:55:55 -07:00
6a3d6ecbe5 Merge pull request #489 from Infisical/snyk-upgrade-0afc777ee2d9380ebff1888a241d4d4a
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.287.0 to 3.294.0
2023-04-15 09:54:24 -07:00
d6ed456ebd Merge pull request #488 from Infisical/snyk-upgrade-c74bcdca67fc70a1214aee998010b3e4
[Snyk] Upgrade aws-sdk from 2.1331.0 to 2.1338.0
2023-04-15 09:54:13 -07:00
f99bb253df fix: upgrade @sentry/node from 7.40.0 to 7.41.0
Snyk has created this PR to upgrade @sentry/node from 7.40.0 to 7.41.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-15 09:53:26 -07:00
0c3c15be91 Merge pull request #490 from Infisical/snyk-upgrade-417b9ee764e0ce7eb85b51db9c2ffdda
[Snyk] Upgrade posthog-node from 2.5.4 to 2.6.0
2023-04-15 09:44:00 -07:00
d9afe90885 Begin frontend for blinded indices 2023-04-15 17:39:30 +03:00
fcb677d990 Checkpoint argon2id test to generate blind index 2023-04-15 15:21:44 +03:00
5fb7b55fdf Merge branch 'main' of https://github.com/Infisical/infisical 2023-04-15 12:30:51 +03:00
49559fbc5f Update contributors in README 2023-04-15 12:30:48 +03:00
12d8e144d1 Merge pull request #501 from sheensantoscapadngan/feature/added-service-token-never-expire-1
[Feature][Sheen] added never expire service token
2023-04-15 11:46:48 +03:00
3eb810b979 Checkpoint 2023-04-15 11:02:56 +03:00
c1f39b866f [Feature][Sheen] added never expire service token 2023-04-15 15:50:06 +08:00
954335bd37 remove addNewUserPrompt 2023-04-14 21:37:57 -07:00
fe115a7476 Update user menu 2023-04-14 21:37:32 -07:00
00442992d2 Add user main command and add switch as sub command 2023-04-14 20:43:58 -07:00
12e16b4a03 [Adjustment][Sheen] removed additional prompt when deleting secret 2023-04-15 11:21:35 +08:00
56c35293eb hotfix: choose env when opening a dashboard link 2023-04-14 19:39:30 -07:00
d38432e0d6 Merge pull request #499 from Infisical/birdseye-view
feat/birdseye-environment-overview
2023-04-14 19:36:52 -07:00
cfc9470a6f Fixed merge conflicts 2023-04-14 19:33:25 -07:00
3907c99b5b Merge branch 'main' into birdseye-view 2023-04-14 18:35:13 -07:00
903560a2d1 Finished the env overview feature 2023-04-14 18:20:19 -07:00
6d8b16fc85 mark smtp fields as not required 2023-04-14 16:16:37 -07:00
6b1f704a44 Update README.md 2023-04-14 12:49:12 -07:00
3dfb85b03f Merge remote-tracking branch 'origin' into secrets-v3 2023-04-14 17:48:23 +03:00
0b7508b40c Merge pull request #495 from Infisical/improve-service-accounts
Improve service accounts / middleware + revamp images in documentation
2023-04-14 14:17:31 +03:00
7eae2392fe Merge remote-tracking branch 'origin' into improve-service-accounts 2023-04-14 14:08:28 +03:00
b21a8b4574 Add supabase docs, modify integration docs wording, check integration middleware 2023-04-14 14:08:09 +03:00
3b30095629 update CF link 2023-04-13 15:31:10 -07:00
5c15fab46e correct HTTPS_ENABLED cast 2023-04-13 15:24:41 -07:00
806448a7f9 Correct service token telemetry depending on creating entity 2023-04-13 22:30:42 +03:00
d824305fd6 Begin Supabase docs 2023-04-13 20:53:17 +03:00
83ddba29e2 conditionally set https 2023-04-13 09:03:23 -07:00
4489adeefa set https to false by default aws ec2 deploy 2023-04-13 09:01:57 -07:00
242f362682 Merge remote-tracking branch 'origin' into improve-service-accounts 2023-04-13 15:17:15 +03:00
0a9dc7ac46 Merge pull request #497 from Aashish-Upadhyay-101/Supabase-Integration-Updated
Supabase integration updated
2023-04-13 14:30:17 +03:00
99dd661c56 Update middleware for service token data 2023-04-13 10:18:37 +03:00
1fe1afbb8e updated PR supabase-integration 2023-04-13 12:39:19 +05:45
83be9efee8 Merge remote-tracking branch 'origin' into improve-service-accounts 2023-04-13 09:37:01 +03:00
1b1cb4a1de supabase updated setup 2023-04-13 12:20:31 +05:45
dfa33e63cb remove secure cookie from default install 2023-04-12 17:29:53 -07:00
ac8b13116f Merge pull request #496 from akhilmhdh/rollback/dashboard
feat(ui): rollback to old dashboard page
2023-04-12 14:10:55 -07:00
810554e13c First commit of env overview 2023-04-12 13:41:12 -07:00
3791ba2609 Merge remote-tracking branch 'origin' into improve-service-accounts 2023-04-12 22:41:41 +03:00
ed7dbb655c Updated bot, integration, and integrationAuth middlewares to support multiple clients 2023-04-12 22:36:36 +03:00
dda5f75450 add integration tests for checking service token with overrides 2023-04-12 12:00:27 -07:00
e2c67ffbef allow service tokens to continue to support overrides 2023-04-12 11:59:41 -07:00
2a64d657d3 feat(ui): rollback to old dashboard page 2023-04-12 19:34:27 +05:30
e2139882da Update middleware for requireSecretAuth 2023-04-12 14:17:20 +03:00
c7a402c4cb add integ tests for service token with overrides 2023-04-11 18:50:01 -07:00
73ddad8dac update service token tests 2023-04-11 18:12:20 -07:00
8c450d51da add integration tests for service-tokens 2023-04-11 17:49:45 -07:00
bec80de174 add integ tests for fetching/secrets secrets with jwt/service token 2023-04-11 16:51:50 -07:00
c768383f7e Merge remote-tracking branch 'origin' into improve-service-accounts 2023-04-11 23:58:52 +03:00
9df8e8926d Continue refactoring remaining middleware to be compatible with multiple clients 2023-04-11 23:58:29 +03:00
689ac6a8fe add back .populate(tags) to secrets GET via service token 2023-04-10 15:16:57 -07:00
576381cd58 add back user populate 2023-04-10 13:45:49 -07:00
8a67549ec5 remove user from GetServiceTokenDetailsResponse 2023-04-10 13:45:32 -07:00
5032450b1c Add service account support for organization endpoints and update docs images 2023-04-10 17:30:51 +03:00
802cb80416 Add helm chart video tut 2023-04-09 21:44:46 -07:00
e0ac12be14 default to prod when no node env 2023-04-09 10:41:13 -07:00
afa7b35d50 Add public key to service account creation modal 2023-04-09 19:51:13 +03:00
e5e15d26bf Begin foundation for secrets v3 2023-04-09 18:19:53 +03:00
192e3beb46 Merge pull request #492 from Infisical/fix-railway-sa-errors
Patch uncaught lint error
2023-04-09 16:39:46 +03:00
9c3a426cb1 Patch lint error 2023-04-09 16:38:19 +03:00
7e15e733f8 Merge pull request #491 from Infisical/railway
Railway Integration + Service Accounts
2023-04-09 16:31:00 +03:00
365daa97a8 Remove service accounts from permitted secrets auth modes for now 2023-04-09 15:10:18 +03:00
710364e3a1 Add support for service variables to Railway integration, add docs for Railway 2023-04-09 15:00:40 +03:00
f6e23127ac Fix merge conflicts 2023-04-09 09:55:34 +03:00
5855c859e5 Merge pull request #443 from akhilmhdh/feat/new-dashboard
Feat/new dashboard
2023-04-08 23:44:50 -07:00
12478130d0 Fixing UI, UX, and bugs in the dashboard 2023-04-08 23:36:33 -07:00
ecb182ad03 Fixing UI, UX, and bugs in the dashboard 2023-04-08 17:30:28 -07:00
553703decb add ingress controller to helm chart 2023-04-08 16:57:38 -07:00
cbf05b7c31 Finish first iteration of Railway integration 2023-04-09 01:31:40 +03:00
0fe4a3c033 fix: upgrade posthog-node from 2.5.4 to 2.6.0
Snyk has created this PR to upgrade posthog-node from 2.5.4 to 2.6.0.

See this package in npm:
https://www.npmjs.com/package/posthog-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-08 21:29:16 +00:00
e8e8ff5563 fix: upgrade @aws-sdk/client-secrets-manager from 3.287.0 to 3.294.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.287.0 to 3.294.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-08 21:29:12 +00:00
dbe75eeecb fix: upgrade aws-sdk from 2.1331.0 to 2.1338.0
Snyk has created this PR to upgrade aws-sdk from 2.1331.0 to 2.1338.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-04-08 21:29:07 +00:00
57ee287dd2 Merge remote-tracking branch 'origin' into railway 2023-04-08 22:17:58 +03:00
98dcc42db2 Begin Railway integration, frontend, getApps 2023-04-08 22:17:38 +03:00
aa108d575d Begin v3 secrets 2023-04-08 20:34:27 +03:00
56cc77e0e8 Merge pull request #486 from Infisical/vercel-preview-branches
Add support for syncing to Vercel preview branches
2023-04-08 16:08:21 +03:00
c175519d70 Add support for syncing to Vercel preview branches 2023-04-08 16:01:58 +03:00
a3093de55b Patch GitHub integration organization owner 2023-04-08 00:17:24 +03:00
00b17d250e Merge pull request #467 from Infisical/snyk-upgrade-0ea5952cd096302b197acdddf41b8188
[Snyk] Upgrade aws-sdk from 2.1324.0 to 2.1331.0
2023-04-07 11:43:42 -07:00
eb94ad5ba4 Merge pull request #469 from Infisical/snyk-upgrade-8427871e6a576319fcd05f240276f7da
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.281.0 to 3.287.0
2023-04-07 11:43:28 -07:00
a3b35a9228 Merge pull request #478 from Infisical/snyk-upgrade-107238710f6bc2ba87fa8a7314c55fe2
[Snyk] Upgrade mongoose from 6.10.2 to 6.10.3
2023-04-07 11:41:46 -07:00
5bbe09e4be Substitute hardcoded auth modes for variables 2023-04-07 16:50:05 +03:00
a7880db871 Patch service account UI in audit logs, add lastUsed for API keys and service accounts/tokens 2023-04-06 23:52:06 +03:00
db8ce00536 Docs to point cli to selfhost on windows 2023-04-06 10:43:11 -07:00
d54753289a Add required endpoints/functions for service account to create service tokens 2023-04-06 16:59:55 +03:00
c40546945f Minor dashboard updates 2023-04-05 18:58:59 -07:00
5508434563 feat: CLI support for multiple user accounts logins
See #340
2023-04-04 16:27:47 +00:00
a3b2d1c838 Merge remote-tracking branch 'origin' into service-account 2023-04-04 11:08:57 +03:00
35d345f17e Merge remote-tracking branch 'origin' into service-account 2023-04-04 11:08:28 +03:00
aa53de9070 Begin refactoring middleware for service accounts 2023-04-04 11:08:03 +03:00
b7142a1f24 fix: upgrade mongoose from 6.10.2 to 6.10.3
Snyk has created this PR to upgrade mongoose from 6.10.2 to 6.10.3.

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-04-04 00:27:11 +00:00
cca9476975 Update helm chart docs 2023-04-03 13:06:40 -07:00
7ec5f0d342 Merge pull request #439 from Grraahaam/fix/helm-charts-improvements
feat: helm auto-generated variables
2023-04-03 11:27:30 -07:00
bdb71d1051 feat(chart): added updatedAt annotation to apps dpl and pod 2023-04-03 09:10:54 +02:00
278f1caa19 chore(doc): updated scripts and docs 2023-04-01 00:18:00 +02:00
dd10bf1702 fix(chart): simplified ingress rules configuration 2023-04-01 00:17:06 +02:00
7c33b6159f Merge branch 'heads/main' into fix/helm-charts-improvements 2023-03-31 23:32:54 +02:00
510d5f0ffd chore(chart): discard secret-operator changes 2023-03-31 23:29:03 +02:00
84d46a428c Merge pull request #466 from Aashish-Upadhyay-101/user-remove-notification-typo
Typo: User remove from Organization notification typo
2023-03-30 12:41:20 -07:00
520c294e45 Merge pull request #471 from Infisical/snyk-upgrade-1afcc79e653f80864b072e202c5af918
[Snyk] Upgrade mongoose from 6.10.1 to 6.10.2
2023-03-30 11:50:48 -07:00
c797901778 Merge remote-tracking branch 'origin' into service-account 2023-03-31 00:04:57 +07:00
9c18adf35f Begin service account middleware 2023-03-30 23:55:24 +07:00
6d7628cdc0 fix: upgrade mongoose from 6.10.1 to 6.10.2
Snyk has created this PR to upgrade mongoose from 6.10.1 to 6.10.2.

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-03-29 21:29:40 +00:00
381652cbb2 fix: upgrade @aws-sdk/client-secrets-manager from 3.281.0 to 3.287.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.281.0 to 3.287.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-03-29 21:29:32 +00:00
acc0198637 fix: upgrade @sentry/tracing from 7.39.0 to 7.41.0
Snyk has created this PR to upgrade @sentry/tracing from 7.39.0 to 7.41.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-03-29 21:29:28 +00:00
6af326685b fix: upgrade aws-sdk from 2.1324.0 to 2.1331.0
Snyk has created this PR to upgrade aws-sdk from 2.1324.0 to 2.1331.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-03-29 21:29:24 +00:00
91c83e04be fix typo notification message 2023-03-30 01:08:30 +05:45
c9e12d33bd Revert "typo fixed"
This reverts commit 9e2a03244ed54f1a1c83b2343088f8adc2144a04.
2023-03-30 01:06:43 +05:45
9e2a03244e typo fixed 2023-03-30 01:05:15 +05:45
2a8a90c0c5 Merge pull request #464 from Aashish-Upadhyay-101/test-crypto-and-posthog
unit tests for utils/posthog and utils/crypto
2023-03-29 20:58:51 +07:00
54e099f8a8 Merge remote-tracking branch 'origin' into service-account 2023-03-29 18:08:11 +07:00
88c0a46de3 Clean up and weave crypto into service account permissions 2023-03-29 18:07:15 +07:00
356f0ac860 improvement tests 2023-03-29 12:27:13 +05:45
49a690b7b2 Update envars.mdx 2023-03-28 20:16:33 -07:00
830368b812 Merge pull request #453 from Infisical/snyk-upgrade-5364ad43aff0d54873feea82482cb023
[Snyk] Upgrade @sentry/tracing from 7.38.0 to 7.39.0
2023-03-28 20:03:38 -07:00
d19c2936e6 Merge pull request #461 from Infisical/snyk-upgrade-68a4ecfcd221d249c48614d7d7a47ab6
[Snyk] Upgrade @aws-sdk/client-secrets-manager from 3.279.0 to 3.281.0
2023-03-28 20:03:07 -07:00
592cef67bc Merge pull request #459 from Infisical/snyk-upgrade-5b74ea7bb92da36a422bbf22d2389650
[Snyk] Upgrade aws-sdk from 2.1323.0 to 2.1324.0
2023-03-28 20:02:51 -07:00
676d0a7bf2 Merge branch 'main' into snyk-upgrade-5364ad43aff0d54873feea82482cb023 2023-03-28 20:01:38 -07:00
5149f526d4 Merge pull request #462 from Infisical/snyk-upgrade-15aecde6d4745facead24cb7fb372005
[Snyk] Upgrade @sentry/node from 7.39.0 to 7.40.0
2023-03-28 19:59:46 -07:00
4875cac4ef Merge pull request #463 from Infisical/snyk-upgrade-363d8de4e07190af10bb7d948bd05cf8
[Snyk] Upgrade mongoose from 6.10.0 to 6.10.1
2023-03-28 19:59:31 -07:00
729aacc154 Resolve merge conflicts 2023-03-28 14:43:56 +07:00
68deea28b7 Make host name optional 2023-03-27 14:19:05 -07:00
53a7e0dac3 Merge pull request #426 from jon4hz/table
Improve infisical secrets command
2023-03-27 08:43:12 -07:00
d36d7bfce6 Checkpoint service account functionality, added UI and general backend structure 2023-03-27 22:00:10 +07:00
1c4649cc9e unit tests for utils/posthog and utils/crypto 2023-03-26 17:51:08 +05:45
77e537c35d fix: upgrade mongoose from 6.10.0 to 6.10.1
Snyk has created this PR to upgrade mongoose from 6.10.0 to 6.10.1.

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-03-24 19:36:50 +00:00
935db128d3 Update faq.mdx 2023-03-23 13:38:03 -07:00
49f26d591b fix: upgrade @sentry/node from 7.39.0 to 7.40.0
Snyk has created this PR to upgrade @sentry/node from 7.39.0 to 7.40.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-03-23 18:26:14 +00:00
3b37a03249 Update overview.mdx 2023-03-22 13:11:15 -07:00
e7e2a869d2 Update overview.mdx 2023-03-22 13:10:13 -07:00
94b18e6fc4 fix: upgrade @aws-sdk/client-secrets-manager from 3.279.0 to 3.281.0
Snyk has created this PR to upgrade @aws-sdk/client-secrets-manager from 3.279.0 to 3.281.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-03-22 18:02:16 +00:00
34edab25c8 Merge pull request #460 from yoobato/main
Fixed broken docs link
2023-03-22 12:13:53 +07:00
3a858e7cd6 Fixed broken docs link
environment variables link are broken in self-hosting overview doc.
2023-03-22 11:51:18 +09:00
0008ff9a98 chore(chart): improved NOTES.txt commands format/layout 2023-03-22 00:27:27 +01:00
5cb6c663bb Merge branch 'heads/main' into fix/helm-charts-improvements 2023-03-22 00:19:02 +01:00
a90375ea3d revert: secret-operator changes 2023-03-22 00:17:19 +01:00
9cf921bb1c fix(conf): add MONGO_URL to backend variables 2023-03-22 00:17:19 +01:00
5ec1a1eedf fix: upgrade aws-sdk from 2.1323.0 to 2.1324.0
Snyk has created this PR to upgrade aws-sdk from 2.1323.0 to 2.1324.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-03-21 18:17:28 +00:00
88a208f47e Update README.md 2023-03-20 22:44:57 -07:00
6cb036bb38 Merge branch 'main' into snyk-upgrade-5364ad43aff0d54873feea82482cb023 2023-03-20 19:32:04 -07:00
77bf857e91 Merge pull request #454 from Infisical/snyk-upgrade-bd2ac852078ffa54b1f933925aa5b9b2
[Snyk] Upgrade @sentry/node from 7.38.0 to 7.39.0
2023-03-20 19:29:37 -07:00
b36559558d Merge pull request #455 from Infisical/snyk-upgrade-e1243e9e41daf282bfbdaff72c202760
[Snyk] Upgrade posthog-node from 2.5.3 to 2.5.4
2023-03-20 19:29:14 -07:00
e88ed97528 Merge pull request #456 from Infisical/snyk-upgrade-5e724fb56e71fa4a80f2c790e01b36b7
[Snyk] Upgrade swagger-ui-express from 4.6.1 to 4.6.2
2023-03-20 19:28:53 -07:00
ed73ded05f Update docker.mdx 2023-03-20 12:52:28 -07:00
66fae1fa0a Update docker.mdx 2023-03-20 12:51:03 -07:00
6e3ee3f4a6 fix: upgrade swagger-ui-express from 4.6.1 to 4.6.2
Snyk has created this PR to upgrade swagger-ui-express from 4.6.1 to 4.6.2.

See this package in npm:
https://www.npmjs.com/package/swagger-ui-express

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-03-20 19:12:53 +00:00
2916c05101 fix: upgrade posthog-node from 2.5.3 to 2.5.4
Snyk has created this PR to upgrade posthog-node from 2.5.3 to 2.5.4.

See this package in npm:
https://www.npmjs.com/package/posthog-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-03-20 19:12:49 +00:00
1cc882462e fix: upgrade @sentry/node from 7.38.0 to 7.39.0
Snyk has created this PR to upgrade @sentry/node from 7.38.0 to 7.39.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-03-20 19:12:46 +00:00
30472505ce fix: upgrade @sentry/tracing from 7.38.0 to 7.39.0
Snyk has created this PR to upgrade @sentry/tracing from 7.38.0 to 7.39.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-03-20 19:12:42 +00:00
119600b64f remove site url env var from frontend 2023-03-20 10:19:09 -07:00
18bbe09af4 remove background from aws deploy btn 2023-03-19 10:46:28 -07:00
fef1adb34f expand backup explanation 2023-03-19 10:34:15 -07:00
93d07c34ab chore(doc): restore link to the web doc 2023-03-19 12:07:39 +01:00
3a0ce7c084 Service account checkpoint 2023-03-19 17:01:36 +07:00
b944e8bb84 fix typo in self host docs 2023-03-18 21:30:05 -07:00
0b2e6a0d77 Update self hosting docs 2023-03-18 20:57:13 -07:00
4cce75d128 feat(ui): implemented the new dashboard page 2023-03-19 00:40:13 +05:30
b42c33107e feat(ui): added api hooks and state hooks for dashboard 2023-03-19 00:39:18 +05:30
686d3c409d feat(ui): added ui components for new dashboard 2023-03-19 00:39:13 +05:30
51f4ab473b Merge pull request #440 from Aashish-Upadhyay-101/aashish/backend-test-setup
test setup
2023-03-18 13:58:01 +07:00
8fb473c57c Checkpoint service accounts 2023-03-18 13:52:38 +07:00
9e8e538647 example test to resolve CI fail 2023-03-18 12:27:06 +05:45
ebdcccb6ca Checkpoint service accounts 2023-03-18 13:34:06 +07:00
809a551073 Remove 1X1 call 2023-03-17 15:42:22 -07:00
83e1900d89 get site url from request 2023-03-17 15:29:49 -07:00
9b2a31761a add default jwt life time 2023-03-17 13:23:19 -07:00
87c99df13d jest test basic setup 2023-03-17 15:50:22 +05:45
8adc53a8bc Merge branch 'Infisical:main' into main 2023-03-17 15:48:12 +05:45
ba240f9e29 chore(doc): add INVITE_ONLY_SIGNUP description 2023-03-17 09:41:49 +01:00
95bb9e2586 fix(chart): add INVITE_ONLY_SIGNUP variable 2023-03-17 09:39:57 +01:00
1487afb36b Show invite code to send if no email service in modal 2023-03-17 00:04:00 -07:00
2bbcd3d9e6 parse invite url from mutationFn 2023-03-17 00:00:15 -07:00
fb1f93a3c0 Skip adding users if no email service 2023-03-16 23:59:24 -07:00
1489604f82 send back invite url if no email service 2023-03-16 23:57:11 -07:00
273f4228d7 Init models for permission, service account, and service account key 2023-03-17 10:43:14 +07:00
fdae5105f8 merge backend 2023-03-16 11:26:15 -07:00
8e55d17a55 show popup when email not configured 2023-03-16 09:56:42 -07:00
ded5c50157 setup done ! 2023-03-16 17:36:49 +05:45
46f2b7a3f8 test setup 2023-03-16 17:34:11 +05:45
3d818f953d Make root/example default DB user in development 2023-03-16 16:44:47 +07:00
3ac98ba326 Improve SDK, quickstart, token, features docs 2023-03-16 15:47:24 +07:00
c8b6eb0d6c fix(chart): NOTES.txt typos 2023-03-16 01:49:53 +01:00
fc41be9db8 chore(script): add local installation example scripts 2023-03-16 01:35:43 +01:00
164da9d8e0 chore(doc): updated helm parameters doc + 0.1.16 upgrade instructions 2023-03-16 01:34:40 +01:00
767943368e fix(conf): mongodb probes + docs 2023-03-16 01:32:54 +01:00
e37f584d75 fix(chart): upgrade deps and bump the verison 2023-03-16 01:30:55 +01:00
b2663fb3e0 chore(pr): pull request template comments 2023-03-16 01:29:58 +01:00
bb2bcb8bd1 Merge pull request #438 from xinity/xinity_infisical_typofix
typo fix
2023-03-15 10:42:59 -07:00
d103c81f67 Merge pull request #432 from Infisical/add-infisical-node
Add the new infisical-node SDK to the backend
2023-03-16 00:31:12 +07:00
b591d638d0 Update Node SDK docs 2023-03-16 00:28:29 +07:00
778631f396 typo fix
removed the 'add' from the helm uninstall command
2023-03-15 18:14:09 +01:00
2ec3143d27 Merge remote-tracking branch 'origin' into add-infisical-node 2023-03-16 00:01:09 +07:00
d705440400 Revamp Node SDK docs 2023-03-15 18:21:52 +07:00
e6e3d82fa6 Modify healthcheck and server on-close to close database connection 2023-03-15 16:58:59 +07:00
db48ab8f6c Modify healthcheck.test 2023-03-15 15:16:50 +07:00
b868b6a5f3 Clean up infisical-node 2023-03-15 14:03:39 +07:00
dabc7e3eb1 Solved the issue with empty secret names 2023-03-14 22:13:02 -07:00
38efb6a1e2 Solved the issue with empty secret names 2023-03-14 22:07:52 -07:00
a6c8638345 Refactor infisical-node to config file for birds eye view of envars 2023-03-15 00:09:40 +07:00
fc8023b941 fix: dont get size of cygwin terminals 2023-03-14 17:02:12 +01:00
31111fc63b Merge remote-tracking branch 'origin' into add-infisical-node 2023-03-14 16:39:34 +07:00
7fd06e36bc Complete preliminary addition of infisical-node to support service tokens 2023-03-14 16:38:30 +07:00
4899c4de5b fix(chart): secret data to stringData for cross-type compatibility 2023-03-14 01:45:00 +01:00
744caf8c79 chore(script): remove auto-generated variables from setup script 2023-03-14 01:43:43 +01:00
da888e27ad fix(chart): ingressClassName cross-version compatibility 2023-03-13 20:18:21 +01:00
28369411f7 fix(chart): update chart dependencies 2023-03-13 20:16:37 +01:00
b7a1689aeb feat(chart): env variables auto-generation 2023-03-13 19:58:03 +01:00
c034b62b71 fix(chart): default secret name to the service fullname 2023-03-13 19:57:13 +01:00
a6b9400a4a fix(chart): wrap secret values into quotes automatically 2023-03-13 19:57:13 +01:00
aa5d761081 fix(chart): image tags default to latest 2023-03-13 19:56:51 +01:00
2cc8e59ca8 fix(chart): kubeSecretRef disables and overwrites default secrets 2023-03-13 19:56:51 +01:00
a031e84ab8 fix(chart): uses envFrom only to inject secrets and conf 2023-03-13 19:55:40 +01:00
e2df6e94a5 chore(chart): breaking change version bump + docs 2023-03-13 19:55:40 +01:00
9db69430b5 fix(chart): ingress shorten names with variables + ingressClassName 2023-03-13 19:51:16 +01:00
00feee6903 fix(chart): shorten names with variables + improved secrets support 2023-03-13 19:51:16 +01:00
10dd747899 fix(chart): truncated secrets-operator resource name 2023-03-13 19:49:25 +01:00
5c55e6e508 clean up prod nginx 2023-03-12 19:09:27 -07:00
71fe15d56e remove ssl cert in nginx 2023-03-12 17:18:39 -07:00
0a71c993ed Update README.md 2023-03-12 11:58:27 -07:00
63adc181c8 Merge pull request #430 from simonemargio/main
README.md Italian translation
2023-03-12 11:57:45 -07:00
76fc82811a README.md Italian translation 2023-03-12 18:05:29 +01:00
1e859c19f4 Merge pull request #429 from Infisical/check-vercel
Add docs for Node SDK
2023-03-12 23:04:48 +07:00
175b4a3fb6 Add docs for Node SDK 2023-03-12 23:03:25 +07:00
d89976802d Merge pull request #424 from Aashish-Upadhyay-101/aashish/example-docs
docs: python docs for example CRUD
2023-03-11 21:53:51 +07:00
dce5c8f621 add emailConfigured to status api 2023-03-10 23:20:08 -08:00
d8ff36f59f Merge pull request #400 from Infisical/snyk-fix-0ab98e0c00b32ecebcd11cb2298f542f
[Snyk] Security upgrade styled-components from 5.3.5 to 5.3.7
2023-03-10 20:46:33 -08:00
c900022697 feat: truncate secret value if terminal is too small 2023-03-10 21:19:56 +01:00
1090a61162 python create_secrets, update_secrets and delete_secrets docs 2023-03-10 22:45:58 +05:45
0e11ff198c Merge pull request #400 from Infisical/snyk-fix-0ab98e0c00b32ecebcd11cb2298f542f
[Snyk] Security upgrade styled-components from 5.3.5 to 5.3.7
2023-03-10 08:46:15 -08:00
cdbc6f5619 Merge pull request #423 from Infisical/check-integrations
Patch create integration page on no integration projects and add support for groups in GitLab integration
2023-03-10 21:50:10 +07:00
78cb18ad0e Fix lint errors 2023-03-10 21:45:51 +07:00
42374a775d python retrieve_secrets docs 2023-03-10 20:29:58 +05:45
0269b58a3c Finish support for GitLab groups integration 2023-03-10 21:25:04 +07:00
ef4a316558 Update docker.mdx 2023-03-09 16:26:32 -08:00
a676ce7c21 Update features.mdx 2023-03-09 13:13:38 -08:00
f475daf7a6 Update README.md 2023-03-08 22:00:28 -08:00
c8110c31ef update helm chart with rbac for configmaps 2023-03-08 21:37:04 -08:00
a5c8c9c279 add rbac for config 2023-03-08 21:33:29 -08:00
5860136494 Patch serviceTokenData workspace string comparison 2023-03-09 12:00:30 +07:00
3f3516b7ba Checkpoint GitLab integration group support 2023-03-09 11:52:34 +07:00
06e26da684 Update README.md 2023-03-08 20:41:16 -08:00
bb70ff96d2 Add docs for k8 Global configuration 2023-03-08 20:33:18 -08:00
c019d57fb6 allow global defaults for secrets operator 2023-03-08 18:52:05 -08:00
7854a5eea2 Fix stripe checks 2023-03-08 16:20:47 -08:00
29636173ef Removed the add to project button for new people 2023-03-08 08:22:31 -08:00
4edfc1e0be Merge pull request #411 from Infisical/revised-service-token-docs
Add read/write support for service tokens and update CRUD examples in docs to use service tokens
2023-03-07 15:42:07 +07:00
61d4da49aa Merge remote-tracking branch 'origin' into revised-service-token-docs 2023-03-07 15:34:22 +07:00
56187ec43e Fix lint errors 2023-03-07 15:33:45 +07:00
971ac26033 Fix lint errors 2023-03-07 15:23:10 +07:00
1f316a0b65 Add SveteKit to the docs sidebar 2023-03-06 21:17:33 -08:00
23d09c37b5 Merge pull request #407 from jerriclynsjohn/patch-2
Adding sveltekit into the index of Integrations
2023-03-06 21:12:50 -08:00
fc7c3022be Merge pull request #410 from jerriclynsjohn/add-sveltekit
Adding SvelteKit in the frontend app
2023-03-06 21:12:09 -08:00
5b65adedbb Resolve merge conflicts 2023-03-07 11:22:56 +07:00
6faf9bf4bf bug fix for https://github.com/Infisical/infisical/issues/403 2023-03-06 11:13:35 -05:00
b5998d7f22 Adding SvelteKit in the frontend app 2023-03-06 20:30:40 +05:30
6abbc1c54d Revise docs for working with CRUD secrets 2023-03-06 18:52:30 +07:00
85e5319981 Merge pull request #401 from jon4hz/update-check
Update check
2023-03-05 22:38:06 -05:00
50da0a753a Add cli docs for supported environment variables 2023-03-05 22:36:00 -05:00
6a5f2d0566 Adding sveltekit into the index 2023-03-06 06:44:17 +05:30
d93277155f Merge pull request #405 from jerriclynsjohn/patch-1
Adding documentation for SvelteKit
2023-03-05 15:56:32 -08:00
cb905e5ee6 Create sveltekit.mdx 2023-03-06 05:18:53 +05:30
71261e7594 Merge pull request #394 from ha-sante/patch-1
Update create-secrets.mdx
2023-03-05 12:34:26 +07:00
27e4f490d3 Merge pull request #387 from MatthewJohn/main
Correct port in self-host documentation and simplify downloading nginx config
2023-03-05 12:32:20 +07:00
298c8705d7 Merge pull request #370 from Neeraj138/login-after-delete-all-projects
Fix: Unable to login after deleting all projects
2023-03-04 21:22:54 -08:00
edc4382a48 Merge pull request #378 from caioluis/fix/update-pt-br-copies
feat(webui-localization): update and fix pt-BR
2023-03-04 20:59:05 -08:00
5baab76f2e Merge branch 'main' into fix/update-pt-br-copies 2023-03-04 20:54:32 -08:00
f9f30efe03 add integrations to main nav in docs 2023-03-04 19:33:11 -05:00
12701bdf98 Merge pull request #402 from Aqib-Rime/update_overview_docs
update AWS and AZURE integrations
2023-03-04 15:01:57 +07:00
70967ac7b0 update AWS and AZURE integrations 2023-03-04 13:52:14 +06:00
98b443da82 Merge pull request #380 from jon4hz/tf
docs: add terraform
2023-03-03 21:07:51 -05:00
10f75c8e55 add terraform docs 2023-03-03 21:07:18 -05:00
b226642853 fix: add debug log 2023-03-03 23:56:14 +01:00
933f837f64 feat: option to disable update check 2023-03-03 23:52:01 +01:00
7327698305 fix: dont use ioutils and handle error 2023-03-03 23:47:54 +01:00
1dc59d0d41 Merge pull request #399 from eltociear/add-ja
Add Japanese README.md
2023-03-03 14:43:05 -08:00
13e067dc4f fix: frontend/package.json & frontend/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-STYLEDCOMPONENTS-3149924
2023-03-03 22:37:03 +00:00
885d348f96 Add Ja link 2023-03-03 21:35:05 +09:00
c71ee77503 Add Japanese README.md 2023-03-03 21:28:59 +09:00
14206de926 Merge pull request #398 from Infisical/patch-azure
Add secret recovery for setting deleted secrets for Azure integration
2023-03-03 15:05:41 +07:00
c73d64d784 Add secret recovery for setting deleted secrets for Azure integration 2023-03-03 14:59:59 +07:00
913067f014 Merge pull request #390 from jorgeteixe/main
Feat: add spanish (es) locale
2023-03-02 14:33:58 -08:00
ff2ee989d6 Add GitLab and Azure Key Vault docs 2023-03-02 23:44:57 +07:00
200cefc1b2 Complete Azure integration 2023-03-02 22:12:56 +07:00
721af0f26d Paginate Netlify sites 2023-03-02 14:36:52 +07:00
aca6269920 add support link for all major errors 2023-03-02 00:00:31 -05:00
a4074c9687 add update Instructions 2023-03-01 23:44:40 -05:00
205ec61549 improve export and run command docs 2023-03-01 18:01:09 -05:00
0d16f707c2 update export command docs with proejctId flag 2023-03-01 17:40:39 -05:00
d3d5ead6ed allow export by explicit projectId 2023-03-01 17:36:02 -05:00
1f05d6ea4d update file permissions to be r/w only for owner 2023-03-01 17:22:59 -05:00
ff82af8358 remove unused GetAllWorkSpaceConfigsStartingFromCurrentPath method 2023-03-01 17:22:59 -05:00
a7da858694 reset cmd also delete secret backups 2023-03-01 17:22:59 -05:00
b5c2f6e551 no login override popup when invalid private key 2023-03-01 17:22:59 -05:00
77226e0924 check public and private keys before DecryptAsymmetric call 2023-03-01 17:22:59 -05:00
0cc4286f5f Added notifications for wrong file types when dropping 2023-03-01 11:08:22 -08:00
99144143ff Added Kubernetes to the integrations list 2023-02-28 20:42:34 -08:00
efff841121 Updated slack link 2023-02-28 09:44:47 -08:00
2f8d914ecb Merge pull request #391 from Aashish-Upadhyay-101/Aashish-Upadhyay-101/GitLab-integration
Feat: GitLab Integration
2023-03-01 00:16:21 +07:00
7dd28a5941 Update create-secrets.mdx
I am making this change to draw your attention to this as it seems that some variables are used wrong.

- Specifically this section:


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



- Imported tweetnacl as well so it's easier to understand and for the code to make sense from the get go.
2023-02-28 13:03:32 +00:00
a89fccdc1f Add support for Zoho email 2023-02-28 19:02:17 +07:00
40ddd3b2a5 remove console.log() i.e used for testing 2023-02-28 10:19:31 +05:45
74d17a20a4 axios changes to request 2023-02-28 10:15:09 +05:45
d537bd2f58 merge conflict resolve 2023-02-28 09:52:49 +05:45
2f045be8a4 missing break statement 2023-02-28 09:37:47 +05:45
c5ee4810ad add dropdown option for spanish locale 2023-02-28 00:24:12 +01:00
1dbda5876f add spanish locale files 2023-02-28 00:23:47 +01:00
d948923d95 add typescript types to secret versions 2023-02-27 16:32:13 -05:00
fb1085744a Merge pull request #389 from Infisical/revert-374-shell
Revert "fix: always execute cmd in subshell"
2023-02-27 14:26:41 -05:00
ec22291aca Revert "fix: always execute cmd in subshell" 2023-02-27 14:24:47 -05:00
00a07fd27c Correct port in linux selfhost setup 2023-02-27 18:47:59 +00:00
ec0e77cc5a Remove unecessary 'cd' during download of nginx config in linux selfhost setup 2023-02-27 18:47:23 +00:00
16c49a9626 update slack link in welcome message 2023-02-26 23:21:36 -05:00
06ea809d60 change color of bold welcome text 2023-02-26 23:19:28 -05:00
515e010065 docs: add terraform 2023-02-25 18:09:06 +01:00
2c46e8a2dc feat(webui-localization): fix and update pt-br copies 2023-02-25 08:24:27 +00:00
eebe3c164a Fix: Unable to login after deleting all projects 2023-02-25 01:16:10 +05:30
1482 changed files with 108991 additions and 46004 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,9 +17,7 @@ JWT_SERVICE_SECRET=f32f716d70a42c5703f4656015e76200
JWT_AUTH_LIFETIME=
JWT_REFRESH_LIFETIME=
JWT_SIGNUP_LIFETIME=
# Optional lifetimes for OTP expressed in seconds
EMAIL_TOKEN_LIFETIME=
JWT_PROVIDER_AUTH_LIFETIME=
# MongoDB
# Backend will connect to the MongoDB instance at connection string MONGO_URL which can either be a ref
@ -33,14 +33,12 @@ MONGO_PASSWORD=example
# Required
SITE_URL=http://localhost:8080
# Mail/SMTP
SMTP_HOST= # required
SMTP_USERNAME= # required
SMTP_PASSWORD= # required
SMTP_PORT=587
SMTP_SECURE=false
SMTP_FROM_ADDRESS= # required
SMTP_FROM_NAME=Infisical
# Mail/SMTP
SMTP_HOST=
SMTP_PORT=
SMTP_NAME=
SMTP_USERNAME=
SMTP_PASSWORD=
# Integration
# Optional only if integration is used
@ -48,10 +46,12 @@ CLIENT_ID_HEROKU=
CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB=
CLIENT_ID_GITLAB=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB=
CLIENT_SECRET_GITLAB=
CLIENT_SLUG_VERCEL=
# Sentry (optional) for monitoring errors
@ -61,10 +61,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

@ -1,6 +1,6 @@
# Description 📣
*Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.*
<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
## Type ✨
@ -11,7 +11,7 @@
# Tests 🛠️
*Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. You may want to add screenshots when relevant and possible*
<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. You may want to add screenshots when relevant and possible -->
```sh
# Here's some code block to paste some code snippets

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,10 +5,15 @@ jobs:
backend-image:
name: Build backend image
runs-on: ubuntu-latest
steps:
- 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
@ -45,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
@ -75,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
@ -94,11 +99,42 @@ 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 }}
secret-scanning-git-app:
name: Build secret scanning git app
runs-on: ubuntu-latest
steps:
- 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 secret scanning git app and push
uses: depot/build-push-action@v1
with:
project: 64mmf0n610
push: true
token: ${{ secrets.DEPOT_PROJECT_TOKEN }}
context: secret-engine
tags: |
infisical/staging_deployment_secret-scanning-git-app:${{ steps.commit.outputs.short }}
infisical/staging_deployment_secret-scanning-git-app:latest
platforms: linux/amd64,linux/arm64
gamma-deployment:
name: Deploy to gamma
runs-on: ubuntu-latest
@ -122,17 +158,17 @@ jobs:
token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}
- name: Save DigitalOcean kubeconfig with short-lived credentials
run: doctl kubernetes cluster kubeconfig save --expiry-seconds 600 k8s-1-25-4-do-0-nyc1-1670645170179
- name: switch to gamma namespace
- name: switch to gamma namespace
run: kubectl config set-context --current --namespace=gamma
- name: test kubectl
run: kubectl get ingress
- 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
else
echo "Helm upgrade was successful"
fi
fi

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]
name: Release Docker image for K8 operator
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
@ -164,7 +165,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"]

375
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,45 @@
{
"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
"@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",
"@typescript-eslint/no-empty-function": "off",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
]
}
}
}

7
backend/.prettierrc Normal file
View File

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

View File

@ -1,19 +0,0 @@
import { server } from '../src/app';
import { describe, expect, it, beforeAll, afterAll } from '@jest/globals';
import supertest from 'supertest';
import { setUpHealthEndpoint } from '../src/services/health';
const requestWithSupertest = supertest(server);
describe('Healthcheck endpoint', () => {
beforeAll(async () => {
setUpHealthEndpoint(server);
});
afterAll(async () => {
server.close();
});
it('GET /healthcheck should return OK', async () => {
const res = await requestWithSupertest.get('/healthcheck');
expect(res.status).toEqual(200);
});
});

View File

@ -4,7 +4,6 @@ declare global {
namespace NodeJS {
interface ProcessEnv {
PORT: string;
EMAIL_TOKEN_LIFETIME: string;
ENCRYPTION_KEY: string;
SALT_ROUNDS: string;
JWT_AUTH_LIFETIME: string;
@ -15,17 +14,19 @@ 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;
CLIENT_ID_VERCEL: string;
CLIENT_ID_NETLIFY: string;
CLIENT_ID_GITHUB: string;
CLIENT_ID_GITLAB: string;
CLIENT_SECRET_HEROKU: string;
CLIENT_SECRET_VERCEL: string;
CLIENT_SECRET_NETLIFY: string;
CLIENT_SECRET_GITHUB: string;
CLIENT_SECRET_GITLAB: string;
CLIENT_SLUG_VERCEL: string;
POSTHOG_HOST: string;
POSTHOG_PROJECT_API_KEY: string;
@ -38,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;
}

9
backend/jest.config.ts Normal file
View File

@ -0,0 +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"],
};

6152
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.267.0",
"@godaddy/terminus": "^4.11.2",
"@aws-sdk/client-secrets-manager": "^3.319.0",
"@godaddy/terminus": "^4.12.0",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.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.1311.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.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.7.2",
"mongoose": "^6.10.5",
"nanoid": "^3.3.6",
"node-cache": "^5.1.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.2.2",
"passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0",
"posthog-node": "^2.6.0",
"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.0",
"swagger-ui-express": "^4.6.2",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
@ -51,12 +55,12 @@
"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",
"pretest": "docker compose -f test-resources/docker-compose.test.yml up -d",
"test": "cross-env NODE_ENV=test jest --testTimeout=10000 --detectOpenHandles",
"test": "cross-env NODE_ENV=test jest --verbose --testTimeout=10000 --detectOpenHandles; npm run posttest",
"test:ci": "npm test -- --watchAll=false --ci --reporters=default --reporters=jest-junit --reporters=github-actions --coverage --testLocationInResults --json --outputFile=coverage/report.json",
"posttest": "docker compose -f test-resources/docker-compose.test.yml down"
},
@ -79,18 +83,21 @@
"@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"@types/jest": "^29.2.4",
"@types/jest": "^29.5.0",
"@types/jsonwebtoken": "^8.5.9",
"@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",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.40.1",
"cross-env": "^7.0.3",
"eslint": "^8.26.0",
"eslint-plugin-unused-imports": "^2.0.0",
"install": "^0.13.0",
"jest": "^29.3.1",
"jest-junit": "^15.0.0",
@ -100,17 +107,6 @@
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1"
},
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"collectCoverageFrom": [
"src/*.{js,ts}",
"!**/node_modules/**"
],
"setupFiles": [
"<rootDir>/test-resources/env-vars.js"
]
},
"jest-junit": {
"outputDirectory": "reports",
"outputName": "jest-junit.xml",

File diff suppressed because it is too large Load Diff

View File

@ -1,144 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { patchRouterParam } = require('./utils/patchAsyncRoutes');
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import dotenv from 'dotenv';
import swaggerUi = require('swagger-ui-express');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerFile = require('../spec.json');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const requestIp = require('request-ip');
dotenv.config();
import { PORT, NODE_ENV, SITE_URL } from './config';
import { apiLimiter } from './helpers/rateLimiter';
import {
workspace as eeWorkspaceRouter,
secret as eeSecretRouter,
secretSnapshot as eeSecretSnapshotRouter,
action as eeActionRouter
} from './ee/routes/v1';
import {
signup as v1SignupRouter,
auth as v1AuthRouter,
bot as v1BotRouter,
organization as v1OrganizationRouter,
workspace as v1WorkspaceRouter,
membershipOrg as v1MembershipOrgRouter,
membership as v1MembershipRouter,
key as v1KeyRouter,
inviteOrg as v1InviteOrgRouter,
user as v1UserRouter,
userAction as v1UserActionRouter,
secret as v1SecretRouter,
serviceToken as v1ServiceTokenRouter,
password as v1PasswordRouter,
stripe as v1StripeRouter,
integration as v1IntegrationRouter,
integrationAuth as v1IntegrationAuthRouter
} from './routes/v1';
import {
signup as v2SignupRouter,
auth as v2AuthRouter,
users as v2UsersRouter,
organizations as v2OrganizationsRouter,
workspace as v2WorkspaceRouter,
secret as v2SecretRouter, // begin to phase out
secrets as v2SecretsRouter,
serviceTokenData as v2ServiceTokenDataRouter,
apiKeyData as v2APIKeyDataRouter,
environment as v2EnvironmentRouter,
tags as v2TagsRouter,
} from './routes/v2';
import { healthCheck } from './routes/status';
import { getLogger } from './utils/logger';
import { RouteNotFoundError } from './utils/errors';
import { requestErrorHandler } from './middleware/requestErrorHandler';
// patch async route params to handle Promise Rejections
patchRouterParam();
export const app = express();
app.enable('trust proxy');
app.use(express.json());
app.use(cookieParser());
app.use(
cors({
credentials: true,
origin: SITE_URL
})
);
app.use(requestIp.mw())
if (NODE_ENV === 'production') {
// enable app-wide rate-limiting + helmet security
// in production
app.disable('x-powered-by');
app.use(apiLimiter);
app.use(helmet());
}
// (EE) routes
app.use('/api/v1/secret', eeSecretRouter);
app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter);
app.use('/api/v1/workspace', eeWorkspaceRouter);
app.use('/api/v1/action', eeActionRouter);
// v1 routes
app.use('/api/v1/signup', v1SignupRouter);
app.use('/api/v1/auth', v1AuthRouter);
app.use('/api/v1/bot', v1BotRouter);
app.use('/api/v1/user', v1UserRouter);
app.use('/api/v1/user-action', v1UserActionRouter);
app.use('/api/v1/organization', v1OrganizationRouter);
app.use('/api/v1/workspace', v1WorkspaceRouter);
app.use('/api/v1/membership-org', v1MembershipOrgRouter);
app.use('/api/v1/membership', v1MembershipRouter);
app.use('/api/v1/key', v1KeyRouter);
app.use('/api/v1/invite-org', v1InviteOrgRouter);
app.use('/api/v1/secret', v1SecretRouter);
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated
app.use('/api/v1/password', v1PasswordRouter);
app.use('/api/v1/stripe', v1StripeRouter);
app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
// v2 routes
app.use('/api/v2/signup', v2SignupRouter);
app.use('/api/v2/auth', v2AuthRouter);
app.use('/api/v2/users', v2UsersRouter);
app.use('/api/v2/organizations', v2OrganizationsRouter);
app.use('/api/v2/workspace', v2EnvironmentRouter);
app.use('/api/v2/workspace', v2TagsRouter);
app.use('/api/v2/workspace', v2WorkspaceRouter);
app.use('/api/v2/secret', v2SecretRouter); // deprecated
app.use('/api/v2/secrets', v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/api-key', v2APIKeyDataRouter);
// api docs
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
// Server status
app.use('/api', healthCheck)
//* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next) => {
if (res.headersSent) return next();
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
})
//* Error Handling Middleware (must be after all routing logic)
app.use(requestErrorHandler)
export const server = app.listen(PORT, () => {
getLogger("backend-main").info(`Server started listening at port ${PORT}`)
});

View File

@ -1,103 +1,85 @@
const PORT = process.env.PORT || 4000;
const EMAIL_TOKEN_LIFETIME = parseInt(process.env.EMAIL_TOKEN_LIFETIME! || '86400');
const INVITE_ONLY_SIGNUP = process.env.INVITE_ONLY_SIGNUP == undefined ? false : process.env.INVITE_ONLY_SIGNUP
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY!;
const SALT_ROUNDS = parseInt(process.env.SALT_ROUNDS!) || 10;
const JWT_AUTH_LIFETIME = process.env.JWT_AUTH_LIFETIME! || '10d';
const JWT_AUTH_SECRET = process.env.JWT_AUTH_SECRET!;
const JWT_MFA_LIFETIME = process.env.JWT_MFA_LIFETIME! || '5m';
const JWT_MFA_SECRET = process.env.JWT_MFA_SECRET!;
const JWT_REFRESH_LIFETIME = process.env.JWT_REFRESH_LIFETIME! || '90d';
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET!;
const JWT_SERVICE_SECRET = process.env.JWT_SERVICE_SECRET!;
const JWT_SIGNUP_LIFETIME = process.env.JWT_SIGNUP_LIFETIME! || '15m';
const JWT_SIGNUP_SECRET = process.env.JWT_SIGNUP_SECRET!;
const MONGO_URL = process.env.MONGO_URL!;
const NODE_ENV = process.env.NODE_ENV! || 'production';
const VERBOSE_ERROR_OUTPUT = process.env.VERBOSE_ERROR_OUTPUT! === 'true' && true;
const LOKI_HOST = process.env.LOKI_HOST || undefined;
const CLIENT_ID_AZURE = process.env.CLIENT_ID_AZURE!;
const TENANT_ID_AZURE = process.env.TENANT_ID_AZURE!;
const CLIENT_ID_HEROKU = process.env.CLIENT_ID_HEROKU!;
const CLIENT_ID_VERCEL = process.env.CLIENT_ID_VERCEL!;
const CLIENT_ID_NETLIFY = process.env.CLIENT_ID_NETLIFY!;
const CLIENT_ID_GITHUB = process.env.CLIENT_ID_GITHUB!;
const CLIENT_SECRET_AZURE = process.env.CLIENT_SECRET_AZURE!;
const CLIENT_SECRET_HEROKU = process.env.CLIENT_SECRET_HEROKU!;
const CLIENT_SECRET_VERCEL = process.env.CLIENT_SECRET_VERCEL!;
const CLIENT_SECRET_NETLIFY = process.env.CLIENT_SECRET_NETLIFY!;
const CLIENT_SECRET_GITHUB = process.env.CLIENT_SECRET_GITHUB!;
const CLIENT_SLUG_VERCEL = process.env.CLIENT_SLUG_VERCEL!;
const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com';
const POSTHOG_PROJECT_API_KEY =
process.env.POSTHOG_PROJECT_API_KEY! ||
'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE';
const SENTRY_DSN = process.env.SENTRY_DSN!;
const SITE_URL = process.env.SITE_URL!;
const SMTP_HOST = process.env.SMTP_HOST!;
const SMTP_SECURE = process.env.SMTP_SECURE! === 'true' || false;
const SMTP_PORT = parseInt(process.env.SMTP_PORT!) || 587;
const SMTP_USERNAME = process.env.SMTP_USERNAME!;
const SMTP_PASSWORD = process.env.SMTP_PASSWORD!;
const SMTP_FROM_ADDRESS = process.env.SMTP_FROM_ADDRESS!;
const SMTP_FROM_NAME = process.env.SMTP_FROM_NAME! || 'Infisical';
const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!;
const STRIPE_PRODUCT_PRO = process.env.STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_TEAM = process.env.STRIPE_PRODUCT_TEAM!;
const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!;
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!;
const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!;
const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true;
const LICENSE_KEY = process.env.LICENSE_KEY!;
import InfisicalClient from "infisical-node";
export {
PORT,
EMAIL_TOKEN_LIFETIME,
INVITE_ONLY_SIGNUP,
ENCRYPTION_KEY,
SALT_ROUNDS,
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_MFA_LIFETIME,
JWT_MFA_SECRET,
JWT_REFRESH_LIFETIME,
JWT_REFRESH_SECRET,
JWT_SERVICE_SECRET,
JWT_SIGNUP_LIFETIME,
JWT_SIGNUP_SECRET,
MONGO_URL,
NODE_ENV,
VERBOSE_ERROR_OUTPUT,
LOKI_HOST,
CLIENT_ID_AZURE,
TENANT_ID_AZURE,
CLIENT_ID_HEROKU,
CLIENT_ID_VERCEL,
CLIENT_ID_NETLIFY,
CLIENT_ID_GITHUB,
CLIENT_SECRET_AZURE,
CLIENT_SECRET_HEROKU,
CLIENT_SECRET_VERCEL,
CLIENT_SECRET_NETLIFY,
CLIENT_SECRET_GITHUB,
CLIENT_SLUG_VERCEL,
POSTHOG_HOST,
POSTHOG_PROJECT_API_KEY,
SENTRY_DSN,
SITE_URL,
SMTP_HOST,
SMTP_PORT,
SMTP_SECURE,
SMTP_USERNAME,
SMTP_PASSWORD,
SMTP_FROM_ADDRESS,
SMTP_FROM_NAME,
STRIPE_PRODUCT_STARTER,
STRIPE_PRODUCT_TEAM,
STRIPE_PRODUCT_PRO,
STRIPE_PUBLISHABLE_KEY,
STRIPE_SECRET_KEY,
STRIPE_WEBHOOK_SECRET,
TELEMETRY_ENABLED,
LICENSE_KEY
};
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 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 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 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 ((await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || (await client.getSecret("HTTPS_ENABLED")).secretValue == "") {
// default when no value present
return 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 @@
/* 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 { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth';
import { checkUserDevice } from '../../helpers/user';
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 {
LoginSRPDetail,
TokenVersion,
User,
} from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user";
import {
ACTION_LOGIN,
ACTION_LOGOUT
} from '../../variables';
ACTION_LOGOUT,
AUTH_MODE_JWT,
} from "../../variables";
import {
BadRequestError,
UnauthorizedRequestError,
} from "../../utils/errors";
import { EELogService } from "../../ee/services";
import { getChannelFromUserAgent } from "../../utils/posthog";
import {
NODE_ENV,
JWT_AUTH_LIFETIME,
JWT_AUTH_SECRET,
JWT_REFRESH_SECRET
} from '../../config';
import { BadRequestError } from '../../utils/errors';
import { EELogService } from '../../ee/services';
import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this
getHttpsEnabled,
getJwtAuthLifetime,
getJwtAuthSecret,
getJwtRefreshSecret,
} from "../../config";
declare module 'jsonwebtoken' {
declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
refreshVersion?: number;
}
}
@ -34,47 +44,39 @@ declare module 'jsonwebtoken' {
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
try {
const {
email,
clientPublicKey
}: { email: string; clientPublicKey: string } = req.body;
const {
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier');
const user = await User.findOne({
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(
{
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: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false })
return res.status(200).send({
serverPublicKey,
salt: user.salt,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, {
email: email,
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'
});
}
});
}
);
};
/**
@ -85,84 +87,80 @@ 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');
const { email, clientProof } = req.body;
const user = await User.findOne({
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 })
const loginSRPDetailFromDB = await LoginSRPDetail.findOneAndDelete({ email: email })
if (!loginSRPDetailFromDB) {
return BadRequestError(Error("It looks like some details from the first login are not found. Please try login one again"))
}
if (!loginSRPDetailFromDB) {
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();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt,
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// issue tokens
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// issue tokens
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
await checkUserDevice({
user,
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, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
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.ip
});
// return (access) token in response
return res.status(200).send({
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag
});
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id,
});
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP,
});
// return (access) token in response
return res.status(200).send({
token: tokens.token,
publicKey: user.publicKey,
encryptedPrivateKey: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
});
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
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()
});
// clear httpOnly cookie
res.cookie('jid', '', {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT,
userId: req.user._id
});
logoutAction && await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to logout'
});
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,
});
const logoutAction = await EELogService.createAction({
name: ACTION_LOGOUT,
userId: req.user._id,
});
logoutAction && await EELogService.createLog({
userId: req.user._id,
actions: [logoutAction],
channel: getChannelFromUserAgent(req.headers["user-agent"]),
ipAddress: req.realIP,
});
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;
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, JWT_REFRESH_SECRET)
);
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(refreshToken, await getJwtRefreshSecret())
);
const user = await User.findOne({
_id: decodedToken.userId
}).select('+publicKey');
const user = await User.findOne({
_id: decodedToken.userId,
}).select("+publicKey +refreshVersion +accessVersion");
if (!user) throw new Error('Failed to authenticate unfound user');
if (!user?.publicKey)
throw new Error('Failed to authenticate not fully set up account');
if (!user) throw new Error("Failed to authenticate unfound user");
if (!user?.publicKey)
throw new Error("Failed to authenticate not fully set up account");
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
const token = createToken({
payload: {
userId: decodedToken.userId
},
expiresIn: JWT_AUTH_LIFETIME,
secret: JWT_AUTH_SECRET
});
if (!tokenVersion) throw UnauthorizedRequestError({
message: "Failed to validate refresh token",
});
return res.status(200).send({
token
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Invalid request'
});
}
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) throw BadRequestError({
message: "Failed to validate refresh token",
});
const token = createToken({
payload: {
userId: decodedToken.userId,
tokenVersionId: tokenVersion._id.toString(),
accessVersion: tokenVersion.refreshVersion,
},
expiresIn: await getJwtAuthLifetime(),
secret: await getJwtAuthSecret(),
});
return res.status(200).send({
token,
});
};
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,33 +16,24 @@ interface BotKey {
* @returns
*/
export const getBotByWorkspaceId = async (req: Request, res: Response) => {
let bot;
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
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'
});
}
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: new Types.ObjectId(workspaceId),
});
}
return res.status(200).send({
bot
});
return res.status(200).send({
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'
});
}
await BotKey.findOneAndUpdate({
workspace: req.bot.workspace
}, {
encryptedKey: botKey.encryptedKey,
nonce: botKey.nonce,
sender: req.user._id,
bot: req.bot._id,
workspace: req.bot.workspace
}, {
upsert: true,
new: true
});
} else {
// case: bot state set to inactive -> delete bot's workspace key
await BotKey.deleteOne({
bot: req.bot._id
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",
});
}
bot = await Bot.findOneAndUpdate({
_id: req.bot._id
await BotKey.findOneAndUpdate({
workspace: req.bot.workspace,
}, {
isActive
encryptedKey: botKey.encryptedKey,
nonce: botKey.nonce,
sender: req.user._id,
bot: req.bot._id,
workspace: req.bot.workspace,
}, {
new: true
upsert: 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'
});
}
} else {
// case: bot state set to inactive -> delete bot's workspace key
await BotKey.deleteOne({
bot: req.bot._id,
});
}
const bot = await Bot.findOneAndUpdate({
_id: req.bot._id,
}, {
isActive,
}, {
new: true,
});
if (!bot) throw new Error("Failed to update bot active state");
return res.status(200).send({
bot
bot,
});
};

View File

@ -1,19 +1,19 @@
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";
export {
authController,
@ -28,8 +28,8 @@ export {
secretController,
serviceTokenController,
signupController,
stripeController,
userActionController,
userController,
workspaceController
workspaceController,
secretScanningController
};

View File

@ -1,43 +1,40 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
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 {
Integration,
IntegrationAuth,
Bot
} from '../../models';
import { INTEGRATION_SET, INTEGRATION_OPTIONS } from '../../variables';
import { IntegrationService } from '../../services';
import { getApps, revokeAccess } from '../../integrations';
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8,
INTEGRATION_RAILWAY_API_URL,
INTEGRATION_SET,
INTEGRATION_VERCEL_API_URL,
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);
if (!integrationAuth) return res.status(400).send({
message: 'Failed to find integration authorization'
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get integration authorization'
});
}
return res.status(200).send({
integrationAuth
});
}
const { integrationAuthId } = req.params;
const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
if (!integrationAuth)
return res.status(400).send({
message: "Failed to find integration authorization"
});
return res.status(200).send({
integrationAuth
});
};
export const getIntegrationOptions = async (req: Request, res: Response) => {
const INTEGRATION_OPTIONS = await getIntegrationOptionsFunc();
return res.status(200).send({
integrationOptions: INTEGRATION_OPTIONS,
integrationOptions: INTEGRATION_OPTIONS
});
};
@ -47,105 +44,94 @@ export const getIntegrationOptions = async (req: Request, res: Response) => {
* @param res
* @returns
*/
export const oAuthExchange = async (
req: Request,
res: Response
) => {
try {
const { workspaceId, code, integration } = req.body;
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")
}
const integrationAuth = await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
code,
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'
});
}
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");
const environments = req.membership.workspace?.environments || [];
if (environments.length === 0) {
throw new Error("Failed to get environments");
}
const integrationAuth = await IntegrationService.handleOAuthExchange({
workspaceId,
integration,
code,
environment: environments[0].slug
});
return res.status(200).send({
integrationAuth
});
};
/**
* Save integration access token and (optionally) access id as part of integration
* [integration] for workspace with id [workspaceId]
* @param req
* @param res
* @param req
* @param res
*/
export const saveIntegrationAccessToken = async (
req: Request,
res: Response
) => {
// TODO: refactor
// TODO: check if access token is valid for each integration
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,
integration
}: {
workspaceId: string;
accessId: string | null;
accessToken: string;
integration: string;
} = req.body;
let integrationAuth;
const {
workspaceId,
accessId,
accessToken,
url,
namespace,
integration
}: {
workspaceId: string;
accessId: string | null;
accessToken: string;
url: string;
namespace: string;
integration: string;
} = req.body;
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
isActive: true
});
if (!bot) throw new Error('Bot must be enabled to save integration access token');
const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId),
isActive: true
});
integrationAuth = await IntegrationAuth.findOneAndUpdate({
workspace: new Types.ObjectId(workspaceId),
integration
}, {
workspace: new Types.ObjectId(workspaceId),
integration
}, {
new: true,
upsert: true
});
// encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
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'
});
}
return res.status(200).send({
integrationAuth
});
}
if (!bot) throw new Error("Bot must be enabled to save integration access token");
integrationAuth = await IntegrationAuth.findOneAndUpdate(
{
workspace: new Types.ObjectId(workspaceId),
integration
},
{
workspace: new Types.ObjectId(workspaceId),
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({
integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken,
accessExpiresAt: undefined
});
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]
@ -154,22 +140,245 @@ export const saveIntegrationAccessToken = async (
* @returns
*/
export const getIntegrationAuthApps = async (req: Request, res: Response) => {
let apps;
try {
apps = await getApps({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to get integration authorization applications",
const teamId = req.query.teamId as string;
const apps = await getApps({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
accessId: req.accessId,
...(teamId && { teamId })
});
return res.status(200).send({
apps
});
};
/**
* Return list of teams allowed for integration with integration authorization id [integrationAuthId]
* @param req
* @param res
* @returns
*/
export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
const teams = await getTeams({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
});
return res.status(200).send({
teams
});
};
/**
* Return list of available Vercel (preview) branches for Vercel project with
* id [appId]
* @param req
* @param res
*/
export const getIntegrationAuthVercelBranches = async (req: Request, res: Response) => {
const appId = req.query.appId as string;
interface VercelBranch {
ref: string;
lastCommit: string;
isProtected: boolean;
}
const params = new URLSearchParams({
projectId: appId,
...(req.integrationAuth.teamId
? {
teamId: req.integrationAuth.teamId
}
: {})
});
let branches: string[] = [];
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"
}
}
);
branches = data.map((b) => b.ref);
}
return res.status(200).send({
branches
});
};
/**
* Return list of Railway environments for Railway project with
* id [appId]
* @param req
* @param res
*/
export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: Response) => {
const appId = req.query.appId as string;
interface RailwayEnvironment {
node: {
id: string;
name: string;
isEphemeral: boolean;
};
}
interface Environment {
environmentId: string;
name: string;
}
let environments: Environment[] = [];
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) {
edges {
node {
id
name
isEphemeral
}
}
}
}
`;
const variables = {
projectId: appId
};
const {
data: {
data: {
environments: { edges }
}
}
} = await standardRequest.post(
INTEGRATION_RAILWAY_API_URL,
{
query,
variables
},
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Content-Type": "application/json"
}
}
);
environments = edges.map((e: RailwayEnvironment) => {
return {
name: e.node.name,
environmentId: e.node.id
};
});
}
return res.status(200).send({
apps,
environments
});
};
/**
* Return list of Railway services for Railway project with id
* [appId]
* @param req
* @param res
*/
export const getIntegrationAuthRailwayServices = async (req: Request, res: Response) => {
const appId = req.query.appId as string;
interface RailwayService {
node: {
id: string;
name: string;
};
}
interface Service {
name: string;
serviceId: string;
}
let services: Service[] = [];
const query = `
query project($id: String!) {
project(id: $id) {
createdAt
deletedAt
id
description
expiredAt
isPublic
isTempProject
isUpdatable
name
prDeploys
teamId
updatedAt
upstreamUrl
services {
edges {
node {
id
name
}
}
}
}
}
`;
if (appId && appId !== "") {
const variables = {
id: appId
};
const {
data: {
data: {
project: {
services: { edges }
}
}
}
} = await standardRequest.post(
INTEGRATION_RAILWAY_API_URL,
{
query,
variables
},
{
headers: {
Authorization: `Bearer ${req.accessToken}`,
"Content-Type": "application/json"
}
}
);
services = edges.map((e: RailwayService) => ({
name: e.node.name,
serviceId: e.node.id
}));
}
return res.status(200).send({
services
});
};
@ -180,21 +389,12 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
* @returns
*/
export const deleteIntegrationAuth = async (req: Request, res: Response) => {
let integrationAuth;
try {
integrationAuth = await revokeAccess({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to delete integration authorization",
});
}
const integrationAuth = await revokeAccess({
integrationAuth: req.integrationAuth,
accessToken: req.accessToken
});
return res.status(200).send({
integrationAuth,
integrationAuth
});
};

View File

@ -1,14 +1,11 @@
import { Request, Response } from 'express';
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import {
Integration,
Workspace,
Bot,
BotKey
} 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 { eventPushSecrets } 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
@ -17,53 +14,66 @@ import { eventPushSecrets } from '../../events';
* @returns
*/
export const createIntegration = async (req: Request, res: Response) => {
let integration;
try {
const {
integrationAuthId,
app,
appId,
isActive,
sourceEnvironment,
targetEnvironment,
owner,
path,
region
} = req.body;
// TODO: validate [sourceEnvironment] and [targetEnvironment]
const {
integrationAuthId,
app,
appId,
isActive,
sourceEnvironment,
targetEnvironment,
targetEnvironmentId,
targetService,
targetServiceId,
owner,
path,
region,
secretPath,
} = req.body;
// initialize new integration after saving integration access token
integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment,
isActive,
app,
appId,
targetEnvironment,
owner,
path,
region,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId)
}).save();
if (integration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: integration.workspace.toString()
})
});
}
const folders = await Folder.findOne({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment,
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create integration'
});
}
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
const integration = await new Integration({
workspace: req.integrationAuth.workspace._id,
environment: sourceEnvironment,
isActive,
app,
appId,
targetEnvironment,
targetEnvironmentId,
targetService,
targetServiceId,
owner,
path,
region,
secretPath,
integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId),
}).save();
if (integration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: integration.workspace,
environment: sourceEnvironment,
}),
});
}
return res.status(200).send({
integration,
@ -77,51 +87,58 @@ 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 {
const {
environment,
isActive,
app,
appId,
targetEnvironment,
owner, // github-specific integration param
secretPath,
} = req.body;
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,
},
{
environment,
isActive,
app,
appId,
targetEnvironment,
owner, // github-specific integration param
} = req.body;
integration = await Integration.findOneAndUpdate(
{
_id: req.integration._id,
},
{
environment,
isActive,
app,
appId,
targetEnvironment,
owner,
},
{
new: true,
}
);
if (integration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: integration.workspace.toString(),
}),
});
owner,
secretPath,
},
{
new: true,
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to update integration",
);
if (integration) {
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: integration.workspace,
environment,
}),
});
}
@ -138,22 +155,13 @@ export const updateIntegration = async (req: Request, res: Response) => {
* @returns
*/
export const deleteIntegration = async (req: Request, res: Response) => {
let integration;
try {
const { integrationId } = req.params;
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",
});
}
if (!integration) throw new Error("Failed to find integration");
return res.status(200).send({
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,37 +10,29 @@ import { findMembership } from '../../helpers/membership';
* @returns
*/
export const uploadKey = async (req: Request, res: Response) => {
try {
const { workspaceId } = req.params;
const { key } = req.body;
const { workspaceId } = req.params;
const { key } = req.body;
// validate membership of receiver
const receiverMembership = await findMembership({
user: key.userId,
workspace: workspaceId
});
// validate membership of receiver
const receiverMembership = await findMembership({
user: key.userId,
workspace: workspaceId,
});
if (!receiverMembership) {
throw new Error('Failed receiver membership validation for workspace');
}
if (!receiverMembership) {
throw new Error("Failed receiver membership validation for workspace");
}
await new Key({
encryptedKey: key.encryptedKey,
nonce: key.nonce,
sender: req.user._id,
receiver: key.userId,
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'
});
}
await new Key({
encryptedKey: key.encryptedKey,
nonce: key.nonce,
sender: req.user._id,
receiver: key.userId,
workspace: workspaceId,
}).save();
return res.status(200).send({
message: 'Successfully uploaded key to workspace'
message: "Successfully uploaded key to workspace",
});
};
@ -52,31 +43,22 @@ export const uploadKey = async (req: Request, res: Response) => {
* @returns
*/
export const getLatestKey = async (req: Request, res: Response) => {
let latestKey;
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
// get latest key
latestKey = await Key.find({
workspace: workspaceId,
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'
});
}
// get latest key
const latestKey = await Key.find({
workspace: workspaceId,
receiver: req.user._id,
})
.sort({ createdAt: -1 })
.limit(1)
.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 { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Membership, MembershipOrg, User, Key, IMembership, Workspace } from '../../models';
import {
findMembership,
deleteMembership as deleteMember
} from '../../helpers/membership';
import { sendMail } from '../../helpers/nodemailer';
import { SITE_URL } from '../../config';
import { ADMIN, MEMBER, ACCEPTED } from '../../variables';
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,29 +12,20 @@ import { ADMIN, MEMBER, ACCEPTED } from '../../variables';
* @returns
*/
export const validateMembership = async (req: Request, res: Response) => {
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
// validate membership
const membership = await findMembership({
user: req.user._id,
workspace: workspaceId
});
// validate membership
const membership = await findMembership({
user: req.user._id,
workspace: workspaceId
});
if (!membership) {
throw new Error("Failed to validate membership");
}
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'
});
}
return res.status(200).send({
message: 'Workspace membership confirmed'
});
return res.status(200).send({
message: "Workspace membership confirmed"
});
};
/**
@ -48,52 +35,41 @@ export const validateMembership = async (req: Request, res: Response) => {
* @returns
*/
export const deleteMembership = async (req: Request, res: Response) => {
let deletedMembership;
try {
const { membershipId } = req.params;
const { membershipId } = req.params;
// check if membership to delete exists
const membershipToDelete = await Membership.findOne({
_id: membershipId
}).populate('user');
// check if membership to delete exists
const membershipToDelete = await Membership.findOne({
_id: membershipId
}).populate("user");
if (!membershipToDelete) {
throw new Error(
"Failed to delete workspace membership that doesn't exist"
);
}
if (!membershipToDelete) {
throw new Error("Failed to delete workspace membership that doesn't exist");
}
// check if user is a member and admin of the workspace
// whose membership we wish to delete
const membership = await Membership.findOne({
user: req.user._id,
workspace: membershipToDelete.workspace
});
// check if user is a member and admin of the workspace
// whose membership we wish to delete
const membership = await Membership.findOne({
user: req.user._id,
workspace: membershipToDelete.workspace
});
if (!membership) {
throw new Error('Failed to validate workspace membership');
}
if (!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');
}
if (membership.role !== ADMIN) {
// user is not an admin member of the workspace
throw new Error("Insufficient role for deleting workspace membership");
}
// delete workspace membership
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'
});
}
// delete workspace membership
const deletedMembership = await deleteMember({
membershipId: membershipToDelete._id.toString()
});
return res.status(200).send({
deletedMembership
});
return res.status(200).send({
deletedMembership
});
};
/**
@ -103,53 +79,44 @@ 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;
const { membershipId } = req.params;
const { role } = req.body;
if (![ADMIN, MEMBER].includes(role)) {
throw new Error('Failed to validate role');
}
if (![ADMIN, MEMBER].includes(role)) {
throw new Error("Failed to validate role");
}
// validate target membership
membershipToChangeRole = await findMembership({
_id: membershipId
});
// validate target membership
const membershipToChangeRole = await findMembership({
_id: membershipId
});
if (!membershipToChangeRole) {
throw new Error('Failed to find membership to change role');
}
if (!membershipToChangeRole) {
throw new Error("Failed to find membership to change role");
}
// check if user is a member and admin of target membership's
// workspace
const membership = await findMembership({
user: req.user._id,
workspace: membershipToChangeRole.workspace
});
// check if user is a member and admin of target membership's
// workspace
const membership = await findMembership({
user: req.user._id,
workspace: membershipToChangeRole.workspace
});
if (!membership) {
throw new Error('Failed to validate membership');
}
if (!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');
}
if (membership.role !== ADMIN) {
// user is not an admin member of the workspace
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'
});
}
membershipToChangeRole.role = role;
await membershipToChangeRole.save();
return res.status(200).send({
membership: membershipToChangeRole
});
return res.status(200).send({
membership: membershipToChangeRole
});
};
/**
@ -159,75 +126,63 @@ 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;
const { workspaceId } = req.params;
const { email }: { email: string } = req.body;
invitee = await User.findOne({
email
}).select('+publicKey');
const invitee = await User.findOne({
email
}).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
const inviteeMembership = await Membership.findOne({
user: invitee._id,
workspace: workspaceId
});
// validate invitee's workspace membership - ensure member isn't
// already a member of the workspace
const inviteeMembership = await Membership.findOne({
user: invitee._id,
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
const membershipOrg = await MembershipOrg.findOne({
user: invitee._id,
organization: req.membership.workspace.organization,
status: ACCEPTED
});
// validate invitee's organization membership - ensure that only
// (accepted) organization members can be added to the workspace
const membershipOrg = await MembershipOrg.findOne({
user: invitee._id,
organization: req.membership.workspace.organization,
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({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate('sender', '+publicKey');
// get latest key
const latestKey = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate("sender", "+publicKey");
// create new workspace membership
const m = await new Membership({
user: invitee._id,
workspace: workspaceId,
role: MEMBER
}).save();
// create new workspace membership
await new Membership({
user: invitee._id,
workspace: workspaceId,
role: MEMBER
}).save();
await sendMail({
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: SITE_URL + '/login'
}
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to invite user to workspace'
});
}
await sendMail({
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: (await getSiteURL()) + "/login"
}
});
return res.status(200).send({
invitee,
latestKey
});
};
return res.status(200).send({
invitee,
latestKey
});
};

View File

@ -1,13 +1,27 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { SITE_URL, JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET } from '../../config';
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 { Types } from "mongoose";
import { Request, Response } from "express";
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 { 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,55 +29,44 @@ import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } f
* @param res
* @returns
*/
export const deleteMembershipOrg = async (req: Request, res: Response) => {
let membershipOrgToDelete;
try {
const { membershipOrgId } = req.params;
export const deleteMembershipOrg = async (req: Request, _res: Response) => {
const { membershipOrgId } = req.params;
// check if organization membership to delete exists
membershipOrgToDelete = await MembershipOrg.findOne({
_id: membershipOrgId
}).populate('user');
// check if organization membership to delete exists
const membershipOrgToDelete = await MembershipOrg.findOne({
_id: membershipOrgId
}).populate("user");
if (!membershipOrgToDelete) {
throw new Error(
"Failed to delete organization membership that doesn't exist"
);
}
if (!membershipOrgToDelete) {
throw new Error("Failed to delete organization membership that doesn't exist");
}
// check if user is a member and admin of the organization
// whose membership we wish to delete
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: membershipOrgToDelete.organization
});
// check if user is a member and admin of the organization
// whose membership we wish to delete
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: membershipOrgToDelete.organization
});
if (!membershipOrg) {
throw new Error('Failed to validate organization membership');
}
if (!membershipOrg) {
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');
}
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");
}
// delete organization membership
const deletedMembershipOrg = await deleteMemberFromOrg({
membershipOrgId: membershipOrgToDelete._id.toString()
});
// delete organization membership
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'
});
}
await updateSubscriptionOrgQuantity({
organizationId: membershipOrg.organization.toString()
});
return membershipOrgToDelete;
return membershipOrgToDelete;
};
/**
@ -73,22 +76,14 @@ export const deleteMembershipOrg = async (req: Request, res: Response) => {
* @returns
*/
export const changeMembershipOrgRole = async (req: Request, res: Response) => {
// change role for (target) organization membership with id
// [membershipOrgId]
// change role for (target) organization membership with id
// [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'
// });
// }
let membershipToChangeRole;
return res.status(200).send({
membershipOrg: membershipToChangeRole
});
return res.status(200).send({
membershipOrg: membershipToChangeRole
});
};
/**
@ -99,102 +94,119 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
* @returns
*/
export const inviteUserToOrganization = async (req: Request, res: Response) => {
let invitee, inviteeMembershipOrg;
try {
const { organizationId, inviteeEmail } = req.body;
let inviteeMembershipOrg, completeInviteLink;
const { organizationId, inviteeEmail } = req.body;
const host = req.headers.host;
const siteUrl = `${req.protocol}://${host}`;
// validate membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: organizationId
});
// validate membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: organizationId
});
if (!membershipOrg) {
throw new Error('Failed to validate organization membership');
}
if (!membershipOrg) {
throw new Error("Failed to validate organization membership");
}
invitee = await User.findOne({
email: inviteeEmail
}).select('+publicKey');
const plan = await EELicenseService.getPlan(organizationId);
if (invitee) {
// case: invitee is an existing user
if (plan.memberLimit !== null) {
// case: limit imposed on number of members allowed
inviteeMembershipOrg = await MembershipOrg.findOne({
user: invitee._id,
organization: organizationId
});
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."
});
}
}
if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) {
throw new Error(
'Failed to invite an existing member of the organization'
);
}
const invitee = await User.findOne({
email: inviteeEmail
}).select("+publicKey");
if (!inviteeMembershipOrg) {
await new MembershipOrg({
user: invitee,
inviteEmail: inviteeEmail,
organization: organizationId,
role: MEMBER,
status: invitee?.publicKey ? ACCEPTED : INVITED
}).save();
}
} else {
// check if invitee has been invited before
inviteeMembershipOrg = await MembershipOrg.findOne({
inviteEmail: inviteeEmail,
organization: organizationId
});
if (invitee) {
// case: invitee is an existing user
if (!inviteeMembershipOrg) {
// case: invitee has never been invited before
inviteeMembershipOrg = await MembershipOrg.findOne({
user: invitee._id,
organization: organizationId
});
await new MembershipOrg({
inviteEmail: inviteeEmail,
organization: organizationId,
role: MEMBER,
status: INVITED
}).save();
}
}
if (inviteeMembershipOrg && inviteeMembershipOrg.status === ACCEPTED) {
throw new Error("Failed to invite an existing member of the organization");
}
const organization = await Organization.findOne({ _id: organizationId });
if (!inviteeMembershipOrg) {
await new MembershipOrg({
user: invitee,
inviteEmail: inviteeEmail,
organization: organizationId,
role: MEMBER,
status: INVITED
}).save();
}
} else {
// check if invitee has been invited before
inviteeMembershipOrg = await MembershipOrg.findOne({
inviteEmail: inviteeEmail,
organization: organizationId
});
if (organization) {
const token = await TokenService.createToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email: inviteeEmail,
organizationId: organization._id
});
if (!inviteeMembershipOrg) {
// case: invitee has never been invited before
await sendMail({
template: 'organizationInvitation.handlebars',
subjectLine: 'Infisical organization invitation',
recipients: [inviteeEmail],
substitutions: {
inviterFirstName: req.user.firstName,
inviterEmail: req.user.email,
organizationName: organization.name,
email: inviteeEmail,
token,
callback_url: SITE_URL + '/signupinvite'
}
});
}
// validate that email is not disposable
validateUserEmail(inviteeEmail);
await updateSubscriptionOrgQuantity({ organizationId });
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to send organization invite'
});
}
await new MembershipOrg({
inviteEmail: inviteeEmail,
organization: organizationId,
role: MEMBER,
status: INVITED
}).save();
}
}
return res.status(200).send({
message: `Sent an invite link to ${req.body.inviteeEmail}`
});
const organization = await Organization.findOne({ _id: organizationId });
if (organization) {
const token = await TokenService.createToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email: inviteeEmail,
organizationId: organization._id
});
await sendMail({
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: (await getSiteURL()) + "/signupinvite"
}
});
if (!(await getSmtpConfigured())) {
completeInviteLink = `${
siteUrl + "/signupinvite"
}?token=${token}&to=${inviteeEmail}&organization_id=${organization._id}`;
}
}
await updateSubscriptionOrgQuantity({ organizationId });
return res.status(200).send({
message: `Sent an invite link to ${req.body.inviteeEmail}`,
completeInviteLink
});
};
/**
@ -205,65 +217,61 @@ 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
});
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED,
organization: new Types.ObjectId(organizationId)
});
if (!membershipOrg)
throw new Error('Failed to find any invitations for email');
await TokenService.validateToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email,
organizationId: membershipOrg.organization,
token: code
});
if (!membershipOrg) throw new Error("Failed to find any invitations for email");
if (user && user?.publicKey) {
// case: user has already completed account
// membership can be approved and redirected to login/dashboard
membershipOrg.status = ACCEPTED;
await membershipOrg.save();
await TokenService.validateToken({
type: TOKEN_EMAIL_ORG_INVITATION,
email,
organizationId: membershipOrg.organization,
token: code
});
return res.status(200).send({
message: 'Successfully verified email',
user,
});
}
if (user && user?.publicKey) {
// case: user has already completed account
// membership can be approved and redirected to login/dashboard
membershipOrg.status = ACCEPTED;
await membershipOrg.save();
if (!user) {
// initialize user account
user = await new User({
email
}).save();
}
await updateSubscriptionOrgQuantity({
organizationId
});
// generate temporary signup token
token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: JWT_SIGNUP_LIFETIME,
secret: JWT_SIGNUP_SECRET
});
} 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",
user
});
}
return res.status(200).send({
message: 'Successfully verified email',
user,
token
});
if (!user) {
// initialize user account
user = await new User({
email
}).save();
}
// generate temporary signup token
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret()
});
return res.status(200).send({
message: "Successfully verified email",
user,
token
});
};

View File

@ -1,45 +1,27 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import {
SITE_URL,
STRIPE_SECRET_KEY
} from '../../config';
import Stripe from 'stripe';
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: '2022-08-01'
});
import { Request, Response } from "express";
import {
IncidentContactOrg,
Membership,
MembershipOrg,
Organization,
Workspace,
IncidentContactOrg,
IMembershipOrg
} from '../../models';
import { createOrganization as create } from '../../helpers/organization';
import { addMembershipsOrg } from '../../helpers/membershipOrg';
import { OWNER, ACCEPTED } from '../../variables';
import _ from 'lodash';
} from "../../models";
import { createOrganization as create } from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { ACCEPTED, OWNER } from "../../variables";
import { getSiteURL, getLicenseServerUrl } from "../../config";
import { licenseServerKeyRequest } from "../../config/request";
export const getOrganizations = async (req: Request, res: Response) => {
let organizations;
try {
organizations = (
await MembershipOrg.find({
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 organizations'
});
}
const organizations = (
await MembershipOrg.find({
user: req.user._id,
status: ACCEPTED,
}).populate("organization")
).map((m) => m.organization);
return res.status(200).send({
organizations
organizations,
});
};
@ -51,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;
const { organizationName } = req.body;
if (organizationName.length < 1) {
throw new Error('Organization names must be at least 1-character long');
}
if (organizationName.length < 1) {
throw new Error("Organization names must be at least 1-character long");
}
// create organization and add user as member
organization = await create({
email: req.user.email,
name: organizationName
});
// create organization and add user as member
const organization = await create({
email: req.user.email,
name: organizationName,
});
await addMembershipsOrg({
userIds: [req.user._id.toString()],
organizationId: organization._id.toString(),
roles: [OWNER],
statuses: [ACCEPTED]
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create organization'
});
}
await addMembershipsOrg({
userIds: [req.user._id.toString()],
organizationId: organization._id.toString(),
roles: [OWNER],
statuses: [ACCEPTED],
});
return res.status(200).send({
organization
organization,
});
};
@ -91,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,
});
};
@ -114,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;
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,
});
};
@ -144,38 +98,29 @@ export const getOrganizationWorkspaces = async (
req: Request,
res: Response
) => {
let workspaces;
try {
const { organizationId } = req.params;
const { organizationId } = req.params;
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
'_id'
)
).map((w) => w._id.toString())
);
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId,
},
"_id"
)
).map((w) => w._id.toString())
);
workspaces = (
await Membership.find({
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'
});
}
const workspaces = (
await Membership.find({
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,
});
};
@ -186,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;
const { organizationId } = req.params;
const { name } = req.body;
organization = await Organization.findOneAndUpdate(
{
_id: organizationId
},
{
name
},
{
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to change organization name'
});
}
const organization = await Organization.findOneAndUpdate(
{
_id: organizationId,
},
{
name,
},
{
new: true,
}
);
return res.status(200).send({
message: 'Successfully changed organization name',
organization
message: "Successfully changed organization name",
organization,
});
};
@ -226,23 +162,14 @@ export const getOrganizationIncidentContacts = async (
req: Request,
res: Response
) => {
let incidentContactsOrg;
try {
const { organizationId } = req.params;
const { organizationId } = req.params;
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'
});
}
const incidentContactsOrg = await IncidentContactOrg.find({
organization: organizationId,
});
return res.status(200).send({
incidentContactsOrg
incidentContactsOrg,
});
};
@ -256,26 +183,17 @@ export const addOrganizationIncidentContact = async (
req: Request,
res: Response
) => {
let incidentContactOrg;
try {
const { organizationId } = req.params;
const { email } = req.body;
const { organizationId } = req.params;
const { email } = req.body;
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'
});
}
const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
{ email, organization: organizationId },
{ email, organization: organizationId },
{ upsert: true, new: true }
);
return res.status(200).send({
incidentContactOrg
incidentContactOrg,
});
};
@ -289,31 +207,22 @@ export const deleteOrganizationIncidentContact = async (
req: Request,
res: Response
) => {
let incidentContactOrg;
try {
const { organizationId } = req.params;
const { email } = req.body;
const { organizationId } = req.params;
const { email } = req.body;
incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
email,
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'
});
}
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
email,
organization: organizationId,
});
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
@ -323,38 +232,32 @@ export const createOrganizationPortalSession = async (
req: Request,
res: Response
) => {
let session;
try {
// 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: SITE_URL + '/dashboard',
cancel_url: SITE_URL + '/dashboard'
});
} else {
session = await stripe.billingPortal.sessions.create({
customer: req.membershipOrg.organization.customerId,
return_url: SITE_URL + '/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'
});
}
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
);
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 });
} 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 });
}
};
/**
@ -367,21 +270,8 @@ export const getOrganizationSubscriptions = async (
req: Request,
res: Response
) => {
let subscriptions;
try {
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 = {};
@ -424,4 +314,4 @@ export const getOrganizationMembersAndTheirWorkspaces = async (
});
return res.json(userToWorkspaceIds);
};
};

View File

@ -1,113 +1,98 @@
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 { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config';
import { TOKEN_EMAIL_PASSWORD_RESET } from '../../variables';
import { BadRequestError } from '../../utils/errors';
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]
* Password reset step 1: Send email verification link to email [email]
* for account recovery.
* @param req
* @param res
* @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');
if (!user || !user?.publicKey) {
// case: user has already completed account
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'
});
}
const token = await TokenService.createToken({
type: TOKEN_EMAIL_PASSWORD_RESET,
email
});
await sendMail({
template: 'passwordReset.handlebars',
subjectLine: 'Infisical password reset',
recipients: [email],
substitutions: {
email,
token,
callback_url: SITE_URL + '/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: "If an account exists with this email, a password reset link has been sent"
});
}
return res.status(200).send({
message: `Sent an email for account recovery to ${email}`
});
}
const token = await TokenService.createToken({
type: TOKEN_EMAIL_PASSWORD_RESET,
email
});
await sendMail({
template: "passwordReset.handlebars",
subjectLine: "Infisical password reset",
recipients: [email],
substitutions: {
email,
token,
callback_url: (await getSiteURL()) + "/password-reset"
}
});
return res.status(200).send({
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]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const emailPasswordResetVerify = async (req: Request, res: Response) => {
let user, token;
try {
const { email, code } = req.body;
const { email, code } = req.body;
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'
});
}
await TokenService.validateToken({
type: TOKEN_EMAIL_PASSWORD_RESET,
email,
token: code
});
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"
});
}
// generate temporary password-reset token
token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: JWT_SIGNUP_LIFETIME,
secret: JWT_SIGNUP_SECRET
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed email verification for password reset'
});
}
await TokenService.validateToken({
type: TOKEN_EMAIL_PASSWORD_RESET,
email,
token: code
});
return res.status(200).send({
message: 'Successfully verified email',
user,
token
});
}
// generate temporary password-reset token
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret()
});
return res.status(200).send({
message: "Successfully verified email",
user,
token
});
};
/**
* Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
@ -116,44 +101,41 @@ export const emailPasswordResetVerify = async (req: Request, res: Response) => {
* @returns
*/
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');
// return salt, serverPublicKey as part of first step of SRP protocol
if (!user) throw new Error('Failed to find user');
const { clientPublicKey } = req.body;
const user = await User.findOne({
email: req.user.email
}).select("+salt +verifier");
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
if (!user) throw new Error("Failed to find user");
await LoginSRPDetail.findOneAndReplace({ email: req.user.email }, {
email: req.user.email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false })
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
return res.status(200).send({
serverPublicKey,
salt: user.salt
});
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed to start change password process'
});
}
await LoginSRPDetail.findOneAndReplace(
{ email: req.user.email },
{
email: req.user.email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt)
},
{ upsert: true, returnNewDocument: false }
);
return res.status(200).send({
serverPublicKey,
salt: user.salt
});
}
);
};
/**
@ -165,80 +147,93 @@ export const srp1 = async (req: Request, res: Response) => {
* @returns
*/
export const changePassword = async (req: Request, res: Response) => {
try {
const {
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
const {
clientProof,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
const user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
const user = await User.findOne({
email: req.user.email
}).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"))
}
if (!loginSRPDetailFromDB) {
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();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// change password
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// change password
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
{
new: true
}
);
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
{
new: true
}
);
return res.status(200).send({
message: 'Successfully changed password'
});
}
if (
req.authData.authMode === AUTH_MODE_JWT &&
req.authData.authPayload instanceof User &&
req.authData.tokenVersionId
) {
await clearTokens(req.authData.tokenVersionId);
}
return res.status(400).send({
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?'
});
}
// 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"
});
}
return res.status(400).send({
error: "Failed to change password. Try again?"
});
}
);
};
/**
@ -248,141 +243,117 @@ export const changePassword = async (req: Request, res: Response) => {
* @returns
*/
export const createBackupPrivateKey = async (req: Request, res: Response) => {
// create/change backup private key
// requires verifying [clientProof] as part of second step of SRP protocol
// as initiated in /srp1
// create/change backup private key
// 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 user = await User.findOne({
email: req.user.email
}).select('+salt +verifier');
const { clientProof, encryptedPrivateKey, iv, tag, salt, verifier } = req.body;
const user = await User.findOne({
email: req.user.email
}).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"))
}
if (!loginSRPDetailFromDB) {
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();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(
loginSRPDetailFromDB.clientPublicKey
);
const server = new jsrp.server();
server.init(
{
salt: user.salt,
verifier: user.verifier,
b: loginSRPDetailFromDB.serverBInt
},
async () => {
server.setClientPublicKey(loginSRPDetailFromDB.clientPublicKey);
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// create new or replace backup private key
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
// create new or replace backup private key
const backupPrivateKey = await BackupPrivateKey.findOneAndUpdate(
{ user: req.user._id },
{
user: req.user._id,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
},
{ upsert: true, new: true }
).select('+user, encryptedPrivateKey');
const backupPrivateKey = await BackupPrivateKey.findOneAndUpdate(
{ user: req.user._id },
{
user: req.user._id,
encryptedPrivateKey,
iv,
tag,
salt,
verifier
},
{ upsert: true, new: true }
).select("+user, encryptedPrivateKey");
// issue tokens
return res.status(200).send({
message: 'Successfully updated backup private key',
backupPrivateKey
});
}
// issue tokens
return res.status(200).send({
message: "Successfully updated backup private key",
backupPrivateKey
});
}
return res.status(400).send({
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'
});
}
return res.status(400).send({
message: "Failed to update backup private key"
});
}
);
};
/**
* Return backup private key for user
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getBackupPrivateKey = async (req: Request, res: Response) => {
let backupPrivateKey;
try {
backupPrivateKey = await BackupPrivateKey.findOne({
user: req.user._id
}).select('+encryptedPrivateKey +iv +tag');
const backupPrivateKey = await BackupPrivateKey.findOne({
user: req.user._id
}).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
});
}
return res.status(200).send({
backupPrivateKey
});
};
export const resetPassword = async (req: Request, res: Response) => {
try {
const {
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
} = req.body;
const {
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
{
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'
});
}
await User.findByIdAndUpdate(
req.user._id.toString(),
{
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier
},
{
new: true
}
);
return res.status(200).send({
message: 'Successfully reset password'
});
}
return res.status(200).send({
message: "Successfully reset password"
});
};

View File

@ -1,30 +1,30 @@
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,
reformatPullSecrets
} from '../../helpers/secret';
import { pushKeys } from '../../helpers/key';
import { eventPushSecrets } from '../../events';
import { EventService } from '../../services';
import { postHogClient } from '../../services';
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";
interface PushSecret {
ciphertextKey: string;
ivKey: string;
tagKey: string;
hashKey: string;
ciphertextValue: string;
ivValue: string;
tagValue: string;
hashValue: string;
ciphertextComment: string;
ivComment: string;
tagComment: string;
hashComment: string;
type: 'shared' | 'personal';
ciphertextKey: string;
ivKey: string;
tagKey: string;
hashKey: string;
ciphertextValue: string;
ivValue: string;
tagValue: string;
hashValue: string;
ciphertextComment: string;
ivComment: string;
tagComment: string;
hashComment: string;
type: "shared" | "personal";
}
/**
@ -35,69 +35,58 @@ interface PushSecret {
* @returns
*/
export const pushSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId]
// upload (encrypted) secrets to workspace with id [workspaceId]
const postHogClient = await TelemetryService.getPostHogClient();
let { secrets }: { secrets: PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
const { workspaceId } = req.params;
try {
let { secrets }: { secrets: PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
const { workspaceId } = req.params;
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error("Failed to validate environment");
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
// sanitize secrets
secrets = secrets.filter((s: PushSecret) => s.ciphertextKey !== "" && s.ciphertextValue !== "");
// sanitize secrets
secrets = secrets.filter(
(s: PushSecret) => s.ciphertextKey !== '' && s.ciphertextValue !== ''
);
await push({
userId: req.user._id,
workspaceId,
environment,
secrets
});
await push({
userId: req.user._id,
workspaceId,
environment,
secrets
});
await pushKeys({
userId: req.user._id,
workspaceId,
keys
});
await pushKeys({
userId: req.user._id,
workspaceId,
keys
});
if (postHogClient) {
postHogClient.capture({
event: 'secrets pushed',
distinctId: req.user.email,
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
}
});
}
if (postHogClient) {
postHogClient.capture({
event: "secrets pushed",
distinctId: req.user.email,
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : "cli"
}
});
}
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId
})
});
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment
})
});
} 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'
});
return res.status(200).send({
message: "Successfully uploaded workspace secrets"
});
};
/**
@ -108,63 +97,56 @@ export const pushSecrets = async (req: Request, res: Response) => {
* @returns
*/
export const pullSecrets = async (req: Request, res: Response) => {
let secrets;
let key;
try {
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
let secrets;
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
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;
secrets = await pull({
userId: req.user._id.toString(),
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.ip
});
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error("Failed to validate environment");
}
key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate('sender', '+publicKey');
if (channel !== 'cli') {
secrets = reformatPullSecrets({ secrets });
}
secrets = await pull({
userId: req.user._id.toString(),
workspaceId,
environment,
channel: channel ? channel : "cli",
ipAddress: req.realIP
});
if (postHogClient) {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
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'
});
}
const key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
})
.sort({ createdAt: -1 })
.populate("sender", "+publicKey");
return res.status(200).send({
secrets,
key
});
if (channel !== "cli") {
secrets = reformatPullSecrets({ secrets });
}
if (postHogClient) {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: "secrets pulled",
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : "cli"
}
});
}
return res.status(200).send({
secrets,
key
});
};
/**
@ -176,60 +158,51 @@ export const pullSecrets = async (req: Request, res: Response) => {
* @returns
*/
export const pullSecretsServiceToken = async (req: Request, res: Response) => {
let secrets;
let key;
try {
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
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;
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error("Failed to validate environment");
}
secrets = await pull({
userId: req.serviceToken.user._id.toString(),
workspaceId,
environment,
channel: 'cli',
ipAddress: req.ip
});
const secrets = await pull({
userId: req.serviceToken.user._id.toString(),
workspaceId,
environment,
channel: "cli",
ipAddress: req.realIP
});
key = {
encryptedKey: req.serviceToken.encryptedKey,
nonce: req.serviceToken.nonce,
sender: {
publicKey: req.serviceToken.publicKey
},
receiver: req.serviceToken.user,
workspace: req.serviceToken.workspace
};
const key = {
encryptedKey: req.serviceToken.encryptedKey,
nonce: req.serviceToken.nonce,
sender: {
publicKey: req.serviceToken.publicKey
},
receiver: req.serviceToken.user,
workspace: req.serviceToken.workspace
};
if (postHogClient) {
// capture secrets pulled event in production
postHogClient.capture({
distinctId: req.serviceToken.user.email,
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
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'
});
}
if (postHogClient) {
// capture secrets pulled event in production
postHogClient.capture({
distinctId: req.serviceToken.user.email,
event: "secrets pulled",
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : "cli"
}
});
}
return res.status(200).send({
secrets: reformatPullSecrets({ secrets }),
key
});
};
return res.status(200).send({
secrets: reformatPullSecrets({ secrets }),
key
});
};

View File

@ -0,0 +1,89 @@
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_UNRESOLVED } 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, status: STATUS_UNRESOLVED }).lean()
res.json({
risks: risks
})
}
export const updateRisksStatus = async (req: Request, res: Response) => {
const { riskId } = req.params
const { status } = req.body
const risks = await GitRisks.findByIdAndUpdate(riskId, {
sttaus: status
}).lean()
res.json(risks)
}

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 { JWT_SERVICE_SECRET } 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 { JWT_SERVICE_SECRET } 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: JWT_SERVICE_SECRET
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 { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, INVITE_ONLY_SIGNUP } from '../../config';
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';
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,40 +19,26 @@ import { BadRequestError } from '../../utils/errors';
* @returns
*/
export const beginEmailSignup = async (req: Request, res: Response) => {
let email: string;
try {
email = req.body.email;
const email: string = req.body.email;
if (INVITE_ONLY_SIGNUP) {
// 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');
if (user && user?.publicKey) {
// case: user has already completed account
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'
});
}
return res.status(403).send({
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'
});
}
// send send verification email
await sendEmailVerification({ email });
return res.status(200).send({
message: `Sent an email verification code to ${email}`
});
return res.status(200).send({
message: `Sent an email verification code to ${email}`
});
};
/**
@ -61,50 +49,54 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
* @returns
*/
export const verifyEmailSignup = async (req: Request, res: Response) => {
let user, token;
try {
const { email, code } = req.body;
let user;
const { email, code } = req.body;
// initialize user account
user = await User.findOne({ email });
if (user && user?.publicKey) {
// case: user has already completed account
return res.status(403).send({
error: 'Failed email verification for complete user'
});
}
// initialize user account
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"
});
}
// verify email
await checkEmailVerification({
email,
code
});
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."
});
}
}
if (!user) {
user = await new User({
email
}).save();
}
// verify email
if (await getSmtpConfigured()) {
await checkEmailVerification({
email,
code
});
}
// generate temporary signup token
token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: JWT_SIGNUP_LIFETIME,
secret: JWT_SIGNUP_SECRET
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
error: 'Failed email verification'
});
}
if (!user) {
user = await new User({
email
}).save();
}
return res.status(200).send({
message: 'Successfuly verified email',
user,
token
});
// generate temporary signup token
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: await getJwtSignupLifetime(),
secret: await getJwtSignupSecret()
});
return res.status(200).send({
message: "Successfuly verified email",
user,
token
});
};

View File

@ -1,40 +0,0 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import Stripe from 'stripe';
import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../../config';
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: '2022-08-01'
});
/**
* 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 sig = req.headers['stripe-signature'] as string;
event = stripe.webhooks.constructEvent(
req.body,
sig,
STRIPE_WEBHOOK_SECRET // ?
);
} 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;
const { action } = req.body;
userAction = await UserAction.findOneAndUpdate(
{
user: req.user._id,
action
},
{ user: req.user._id, action },
{
new: 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'
});
}
const userAction = await UserAction.findOneAndUpdate(
{
user: req.user._id,
action,
},
{ user: req.user._id, action },
{
new: true,
upsert: true,
}
);
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;
const action: string = req.query.action as string;
userAction = await UserAction.findOne({
user: req.user._id,
action
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get user action'
});
}
const userAction = await UserAction.findOne({
user: req.user._id,
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

@ -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,27 +23,18 @@ import { ADMIN } from "../../variables";
* @returns
*/
export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
let publicKeys;
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
publicKeys = (
await Membership.find({
workspace: workspaceId,
}).populate<{ user: IUser }>("user", "publicKey")
).map((member) => {
return {
publicKey: member.user.publicKey,
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",
});
}
const publicKeys = (
await Membership.find({
workspace: workspaceId,
}).populate<{ user: IUser }>("user", "publicKey")
).map((member) => {
return {
publicKey: member.user.publicKey,
userId: member.user._id,
};
});
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;
const { workspaceId } = req.params;
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",
});
}
const users = await Membership.find({
workspace: workspaceId,
}).populate("user", "+publicKey");
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 = (
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",
});
}
const workspaces = (
await Membership.find({
user: req.user._id,
}).populate("workspace")
).map((m) => m.workspace);
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;
const { workspaceId } = req.params;
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",
});
}
const workspace = await Workspace.findOne({
_id: workspaceId,
});
return res.status(200).send({
workspace,
@ -140,43 +103,46 @@ 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;
const { workspaceName, organizationId } = req.body;
// validate organization membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: organizationId,
});
// validate organization membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: organizationId,
});
if (!membershipOrg) {
throw new Error("Failed to validate organization membership");
}
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({
name: workspaceName,
organizationId,
});
await addMemberships({
userIds: [req.user._id],
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",
});
if (!membershipOrg) {
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
const workspace = await create({
name: workspaceName,
organizationId,
});
await addMemberships({
userIds: [req.user._id],
workspaceId: workspace._id.toString(),
roles: [ADMIN],
});
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;
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",
});
}
// delete workspace
await deleteWork({
id: workspaceId,
});
return res.status(200).send({
message: "Successfully deleted workspace",
@ -216,29 +174,20 @@ 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;
const { workspaceId } = req.params;
const { name } = req.body;
workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId,
},
{
name,
},
{
new: true,
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: "Failed to change workspace name",
});
}
const workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId,
},
{
name,
},
{
new: true,
}
);
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;
const { workspaceId } = req.params;
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",
});
}
const integrations = await Integration.find({
workspace: workspaceId,
});
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;
const { workspaceId } = req.params;
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",
});
}
const authorizations = await IntegrationAuth.find({
workspace: workspaceId,
});
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({
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",
});
}
const { workspaceId } = req.params;
// ?? FIX.
const serviceTokens = await ServiceToken.find({
user: req.user._id,
workspace: workspaceId,
});
return res.status(200).send({
serviceTokens,

View File

@ -1,105 +0,0 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
APIKeyData
} from '../../models';
import {
SALT_ROUNDS
} 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, SALT_ROUNDS);
const expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
apiKeyData = await new APIKeyData({
name,
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,35 +1,32 @@
/* 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 {
NODE_ENV,
JWT_MFA_LIFETIME,
JWT_MFA_SECRET
} from '../../config';
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,
} from "../../config";
declare module 'jsonwebtoken' {
declare module "jsonwebtoken" {
export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string;
}
}
const clientPublicKeys: any = {};
/**
* Log in user step 1: Return [salt] and [serverPublicKey] as part of step 1 of SRP protocol
* @param req
@ -37,47 +34,40 @@ const clientPublicKeys: any = {};
* @returns
*/
export const login1 = async (req: Request, res: Response) => {
try {
const {
email,
clientPublicKey
}: { email: string; clientPublicKey: string } = req.body;
const {
email,
clientPublicKey,
}: { email: string; clientPublicKey: string } = req.body;
const user = await User.findOne({
email
}).select('+salt +verifier');
const user = await User.findOne({
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(
{
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: email,
clientPublicKey: clientPublicKey,
serverBInt: bigintConversion.bigintToBuf(server.bInt),
}, { upsert: true, returnNewDocument: false });
return res.status(200).send({
serverPublicKey,
salt: user.salt,
verifier: user.verifier
},
async () => {
// generate server-side public key
const serverPublicKey = server.getPublicKey();
await LoginSRPDetail.findOneAndReplace({ email: email }, {
email: email,
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'
});
}
});
}
);
};
/**
@ -88,149 +78,143 @@ 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');
const { email, clientProof } = 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) throw new Error("Failed to find user");
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
const loginSRPDetail = await LoginSRPDetail.findOneAndDelete({ email: email })
if (!loginSRPDetail) {
return BadRequestError(Error("Failed to find login details for SRP"))
}
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);
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)) {
// compare server and client shared keys
if (server.checkClientProof(clientProof)) {
if (user.isMfaEnabled) {
// case: user has MFA enabled
if (user.isMfaEnabled) {
// case: user has MFA enabled
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString()
},
expiresIn: JWT_MFA_LIFETIME,
secret: JWT_MFA_SECRET
});
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: [email],
substitutions: {
code
}
});
return res.status(200).send({
mfaEnabled: true,
token
});
}
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
// generate temporary MFA token
const token = createToken({
payload: {
userId: user._id.toString(),
},
expiresIn: await getJwtMfaLifetime(),
secret: await getJwtMfaSecret(),
});
// 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: NODE_ENV === 'production' ? true : false
const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA,
email,
});
// 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
// send MFA code [code] to [email]
await sendMail({
template: "emailMfa.handlebars",
subjectLine: "Infisical MFA code",
recipients: [email],
substitutions: {
code,
},
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
return res.status(200).send({
mfaEnabled: true,
token,
});
return res.status(200).send(response);
}
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
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 enabled
// 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.ip,
});
return res.status(200).send(response);
}
);
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to authenticate. Try again?'
});
}
return res.status(400).send({
message: "Failed to authenticate. Try again?",
});
}
);
};
/**
@ -239,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 { email } = req.body;
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: [email],
substitutions: {
code,
},
});
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: [email],
substitutions: {
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",
});
}
@ -276,76 +252,93 @@ export const sendMfaToken = async (req: Request, res: Response) => {
* @param res
*/
export const verifyMfaToken = async (req: Request, res: Response) => {
const { email, mfaToken } = req.body;
const { email, mfaToken } = req.body;
await TokenService.validateToken({
type: TOKEN_EMAIL_MFA,
email,
token: mfaToken
});
await TokenService.validateToken({
type: TOKEN_EMAIL_MFA,
email,
token: mfaToken,
});
const user = await User.findOne({
email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
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) throw new Error("Failed to find user");
await checkUserDevice({
user,
ip: req.ip,
userAgent: req.headers['user-agent'] ?? ''
});
await LoginSRPDetail.deleteOne({ userId: user.id })
// issue tokens
const tokens = await issueAuthTokens({ userId: user._id.toString() });
await checkUserDevice({
user,
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: NODE_ENV === 'production' ? true : false
});
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
const resObj: VerifyMfaTokenRes = {
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string
}
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey;
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.protectedKeyTag = user.protectedKeyTag;
}
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
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.ip
});
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
return res.status(200).send(resObj);
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
const resObj: VerifyMfaTokenRes = {
encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string,
}
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey;
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.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(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 { ABILITY_READ, ABILITY_WRITE } from '../../variables/organization';
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,34 +23,45 @@ 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 ||
workspace?.environments.find(
({ name, slug }) => slug === environmentSlug || environmentName === name
)
) {
throw new Error('Failed to create workspace environment');
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.",
});
}
workspace?.environments.push({
name: environmentName,
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',
});
}
if (
!workspace ||
workspace?.environments.find(
({ name, slug }) => slug === environmentSlug || environmentName === name
)
) {
throw new Error("Failed to create workspace environment");
}
workspace?.environments.push({
name: environmentName,
slug: environmentSlug.toLowerCase(),
});
await workspace.save();
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,77 +83,69 @@ 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.');
}
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
throw new Error('Failed to create workspace environment');
}
const isEnvExist = workspace.environments.some(
({ name, slug }) =>
slug !== oldEnvironmentSlug &&
(name === environmentName || slug === environmentSlug)
);
if (isEnvExist) {
throw new Error('Invalid environment given');
}
const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === oldEnvironmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
}
workspace.environments[envIndex].name = environmentName;
workspace.environments[envIndex].slug = environmentSlug.toLowerCase();
await workspace.save();
await Secret.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await SecretVersion.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceToken.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceTokenData.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Integration.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Membership.updateMany(
{
workspace: workspaceId,
"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',
});
// user should pass both new slug and env name
if (!environmentSlug || !environmentName) {
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");
}
const isEnvExist = workspace.environments.some(
({ name, slug }) =>
slug !== oldEnvironmentSlug &&
(name === environmentName || slug === environmentSlug)
);
if (isEnvExist) {
throw new Error("Invalid environment given");
}
const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === oldEnvironmentSlug
);
if (envIndex === -1) {
throw new Error("Invalid environment given");
}
workspace.environments[envIndex].name = environmentName;
workspace.environments[envIndex].slug = environmentSlug.toLowerCase();
await workspace.save();
await Secret.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await SecretVersion.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceToken.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await ServiceTokenData.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Integration.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Membership.updateMany(
{
workspace: workspaceId,
"deniedPermissions.environmentSlug": oldEnvironmentSlug,
},
{ $set: { "deniedPermissions.$[element].environmentSlug": environmentSlug } },
{ arrayFilters: [{ "element.environmentSlug": oldEnvironmentSlug }] }
)
return res.status(200).send({
message: 'Successfully update environment',
message: "Successfully update environment",
workspace: workspaceId,
environment: {
name: environmentName,
@ -163,59 +166,52 @@ 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');
}
const envIndex = workspace?.environments.findIndex(
({ slug }) => slug === environmentSlug
);
if (envIndex === -1) {
throw new Error('Invalid environment given');
}
workspace.environments.splice(envIndex, 1);
await workspace.save();
// clean up
await Secret.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await SecretVersion.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceToken.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceTokenData.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await Integration.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
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',
});
// atomic update the env to avoid conflict
const workspace = await Workspace.findById(workspaceId).exec();
if (!workspace) {
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");
}
workspace.environments.splice(envIndex, 1);
await workspace.save();
// clean up
await Secret.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await SecretVersion.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceToken.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await ServiceTokenData.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await Integration.deleteMany({
workspace: workspaceId,
environment: environmentSlug,
});
await Membership.updateMany(
{ workspace: workspaceId },
{ $pull: { deniedPermissions: { environmentSlug: environmentSlug } } }
);
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 +225,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
const { workspaceId } = req.params;
const workspacesUserIsMemberOf = await Membership.findOne({
workspace: workspaceId,
user: req.user
user: req.user,
})
if (!workspacesUserIsMemberOf) {
@ -244,8 +240,8 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
throw BadRequestError()
}
relatedWorkspace.environments.forEach(environment => {
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_READ })
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: ABILITY_WRITE })
const isReadBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_READ_SECRETS })
const isWriteBlocked = _.some(deniedPermission, { environmentSlug: environment.slug, ability: PERMISSION_WRITE_SECRETS })
if (isReadBlocked && isWriteBlocked) {
return
} else {
@ -253,7 +249,7 @@ export const getAllAccessibleEnvironmentsOfWorkspace = async (
name: environment.name,
slug: environment.slug,
isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked
isReadDenied: isReadBlocked,
})
}
})

View File

@ -1,14 +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 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,
@ -17,9 +17,9 @@ export {
organizationsController,
workspaceController,
serviceTokenDataController,
apiKeyDataController,
secretController,
secretsController,
serviceAccountsController,
environmentController,
tagController
tagController,
}

View File

@ -1,12 +1,13 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import { Request, Response } from "express";
import { Types } from "mongoose";
import {
MembershipOrg,
Membership,
Workspace
} from '../../models';
import { deleteMembershipOrg } from '../../helpers/membershipOrg';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
MembershipOrg,
ServiceAccount,
Workspace,
} from "../../models";
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
/**
* Return memberships for organization with id [organizationId]
@ -47,23 +48,14 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
}
}
*/
let memberships;
try {
const { organizationId } = req.params;
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,
});
}
@ -126,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(
membershipId,
{
role
}, {
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to update organization membership'
});
}
const { membershipId } = req.params;
const { role } = req.body;
const membership = await MembershipOrg.findByIdAndUpdate(
membershipId,
{
role,
}, {
new: true,
}
);
return res.status(200).send({
membership
membership,
});
}
@ -195,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 { membershipId } = req.params;
// delete organization membership
const membership = await deleteMembershipOrg({
membershipOrgId: membershipId,
});
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString()
await updateSubscriptionOrgQuantity({
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,
});
}
@ -260,37 +234,45 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}
}
*/
let workspaces;
try {
const { organizationId } = req.params;
const { organizationId } = req.params;
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId
},
'_id'
)
).map((w) => w._id.toString())
);
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId,
},
"_id"
)
).map((w) => w._id.toString())
);
workspaces = (
await Membership.find({
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 organization workspaces'
});
}
const workspaces = (
await Membership.find({
user: req.user._id,
}).populate("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
return res.status(200).send({
workspaces,
});
}
/**
* Return service accounts for organization with id [organizationId]
* @param req
* @param res
*/
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const serviceAccounts = await ServiceAccount.find({
organization: new Types.ObjectId(organizationId),
});
return res.status(200).send({
workspaces
serviceAccounts,
});
}
}

View File

@ -1,22 +1,39 @@
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 { postHogClient } from '../../services';
import {
BadRequestError,
InternalServerError,
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]
* @param req
* @param res
* @param req
* @param res
*/
export const createSecret = async (req: Request, res: Response) => {
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,
@ -33,45 +50,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]
* @param req
* @param res
* 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 = 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,
@ -88,138 +104,132 @@ 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]
* @param req
* @param res
* @param req
* @param res
*/
export const deleteSecrets = async (req: Request, res: Response) => {
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]
* @param req
* @param req
* @param res
*/
export const deleteSecret = async (req: Request, res: Response) => {
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]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const updateSecrets = async (req: Request, res: Response) => {
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,
@ -233,56 +243,54 @@ 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]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const updateSecret = async (req: Request, res: Response) => {
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,
@ -296,89 +304,103 @@ export const updateSecret = async (req: Request, res: Response) => {
secretCommentCiphertext: secretModificationsRequested.secretCommentCiphertext,
secretCommentIV: secretModificationsRequested.secretCommentIV,
secretCommentTag: secretModificationsRequested.secretCommentTag,
secretCommentHash: secretModificationsRequested.secretCommentHash,
}
secretCommentHash: secretModificationsRequested.secretCommentHash
};
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 })
}
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
});
}
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
* with id [req.user._id]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getSecrets = async (req: Request, res: Response) => {
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(
{
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 })
}
const secrets = await Secret.find({
workspace: workspaceId,
environment,
$or: [{ user: userId }, { user: { $exists: false } }],
type: { $in: [SECRET_SHARED, SECRET_PERSONAL] }
})
.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]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getSecret = async (req: Request, res: Response) => {
// if (postHogClient) {
@ -398,4 +420,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

@ -0,0 +1,306 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import crypto from "crypto";
import bcrypt from "bcrypt";
import {
ServiceAccount,
ServiceAccountKey,
ServiceAccountOrganizationPermission,
ServiceAccountWorkspacePermission,
} from "../../models";
import {
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
* @param req
* @param res
*/
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" });
}
return res.status(200).send({
serviceAccount,
});
}
/**
* Return service account with id [serviceAccountId]
* @param req
* @param res
*/
export const getServiceAccountById = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;
const serviceAccount = await ServiceAccount.findById(serviceAccountId);
if (!serviceAccount) {
throw ServiceAccountNotFoundError({ message: "Failed to find service account" });
}
return res.status(200).send({
serviceAccount,
});
}
/**
* Create a new service account under organization with id [organizationId]
* that has access to workspaces [workspaces]
* @param req
* @param res
* @returns
*/
export const createServiceAccount = async (req: Request, res: Response) => {
const {
name,
organizationId,
publicKey,
expiresIn,
}: CreateServiceAccountDto = req.body;
let expiresAt;
if (expiresIn) {
expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}
const secret = crypto.randomBytes(16).toString("base64");
const secretHash = await bcrypt.hash(secret, await getSaltRounds());
// create service account
const serviceAccount = await new ServiceAccount({
name,
organization: new Types.ObjectId(organizationId),
user: req.user,
publicKey,
lastUsed: new Date(),
expiresAt,
secretHash,
}).save();
const serviceAccountObj = serviceAccount.toObject();
delete serviceAccountObj.secretHash;
// provision default org-level permission for service account
await new ServiceAccountOrganizationPermission({
serviceAccount: serviceAccount._id,
}).save();
const secretId = Buffer.from(serviceAccount._id.toString(), "hex").toString("base64");
return res.status(200).send({
serviceAccountAccessKey: `sa.${secretId}.${secret}`,
serviceAccount: serviceAccountObj,
});
}
/**
* Change name of service account with id [serviceAccountId] to [name]
* @param req
* @param res
* @returns
*/
export const changeServiceAccountName = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;
const { name } = req.body;
const serviceAccount = await ServiceAccount.findOneAndUpdate(
{
_id: new Types.ObjectId(serviceAccountId),
},
{
name,
},
{
new: true,
}
);
return res.status(200).send({
serviceAccount,
});
}
/**
* Add a service account key to service account with id [serviceAccountId]
* for workspace with id [workspaceId]
* @param req
* @param res
* @returns
*/
export const addServiceAccountKey = async (req: Request, res: Response) => {
const {
workspaceId,
encryptedKey,
nonce,
} = req.body;
const serviceAccountKey = await new ServiceAccountKey({
encryptedKey,
nonce,
sender: req.user._id,
serviceAccount: req.serviceAccount._d,
workspace: new Types.ObjectId(workspaceId),
}).save();
return serviceAccountKey;
}
/**
* Return workspace-level permission for service account with id [serviceAccountId]
* @param req
* @param res
*/
export const getServiceAccountWorkspacePermissions = async (req: Request, res: Response) => {
const serviceAccountWorkspacePermissions = await ServiceAccountWorkspacePermission.find({
serviceAccount: req.serviceAccount._id,
}).populate("workspace");
return res.status(200).send({
serviceAccountWorkspacePermissions,
});
}
/**
* Add a workspace permission to service account with id [serviceAccountId]
* @param req
* @param res
*/
export const addServiceAccountWorkspacePermission = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;
const {
environment,
workspaceId,
read = false,
write = false,
encryptedKey,
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",
});
}
const existingPermission = await ServiceAccountWorkspacePermission.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId),
environment,
});
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,
}).save();
const existingServiceAccountKey = await ServiceAccountKey.findOne({
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId),
});
if (!existingServiceAccountKey) {
await new ServiceAccountKey({
encryptedKey,
nonce,
sender: req.user._id,
serviceAccount: new Types.ObjectId(serviceAccountId),
workspace: new Types.ObjectId(workspaceId),
}).save();
}
return res.status(200).send({
serviceAccountWorkspacePermission,
});
}
/**
* Delete workspace permission from service account with id [serviceAccountId]
* @param req
* @param res
*/
export const deleteServiceAccountWorkspacePermission = async (req: Request, res: Response) => {
const { serviceAccountWorkspacePermissionId } = req.params;
const serviceAccountWorkspacePermission = await ServiceAccountWorkspacePermission.findByIdAndDelete(serviceAccountWorkspacePermissionId);
if (serviceAccountWorkspacePermission) {
const { serviceAccount, workspace } = serviceAccountWorkspacePermission;
const count = await ServiceAccountWorkspacePermission.countDocuments({
serviceAccount,
workspace,
});
if (count === 0) {
await ServiceAccountKey.findOneAndDelete({
serviceAccount,
workspace,
});
}
}
return res.status(200).send({
serviceAccountWorkspacePermission,
});
}
/**
* Delete service account with id [serviceAccountId]
* @param req
* @param res
* @returns
*/
export const deleteServiceAccount = async (req: Request, res: Response) => {
const { serviceAccountId } = req.params;
const serviceAccount = await ServiceAccount.findByIdAndDelete(serviceAccountId);
if (serviceAccount) {
await ServiceAccountKey.deleteMany({
serviceAccount: serviceAccount._id,
});
await ServiceAccountOrganizationPermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId),
});
await ServiceAccountWorkspacePermission.deleteMany({
serviceAccount: new Types.ObjectId(serviceAccountId),
});
}
return res.status(200).send({
serviceAccount,
});
}
/**
* Return service account keys for service account with id [serviceAccountId]
* @param req
* @param res
* @returns
*/
export const getServiceAccountKeys = async (req: Request, res: Response) => {
const workspaceId = req.query.workspaceId as string;
const serviceAccountKeys = await ServiceAccountKey.find({
serviceAccount: req.serviceAccount._id,
...(workspaceId ? { workspace: new Types.ObjectId(workspaceId) } : {}),
});
return res.status(200).send({
serviceAccountKeys,
});
}

View File

@ -1,114 +1,133 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import crypto from 'crypto';
import bcrypt from 'bcrypt';
import {
ServiceTokenData
} from '../../models';
import {
SALT_ROUNDS
} from '../../config';
import { userHasWorkspaceAccess } from '../../ee/helpers/checkMembershipPermissions';
import { ABILITY_READ } from '../../variables/organization';
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";
import Folder from "../../models/folder";
import { getFolderByPath } from "../../services/FolderService";
/**
* Return service token data associated with service token on request
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getServiceTokenData = async (req: Request, res: Response) => res.status(200).json(req.serviceTokenData);
export const getServiceTokenData = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Return Infisical Token data'
#swagger.description = 'Return Infisical Token data'
#swagger.security = [{
"bearerAuth": []
}]
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"serviceTokenData": {
"type": "object",
$ref: "#/components/schemas/ServiceTokenData",
"description": "Details of service token"
}
}
}
}
}
}
*/
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
* environment [environment].
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const createServiceTokenData = async (req: Request, res: Response) => {
let serviceToken, serviceTokenData;
try {
const {
name,
workspaceId,
environment,
encryptedKey,
iv,
tag,
expiresIn
} = req.body;
let serviceTokenData;
const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_READ)
if (!hasAccess) {
throw UnauthorizedRequestError({ message: "You do not have the necessary permission(s) perform this action" })
}
const { name, workspaceId, encryptedKey, iv, tag, expiresIn, permissions, scopes } = req.body;
const secret = crypto.randomBytes(16).toString('hex');
const secretHash = await bcrypt.hash(secret, SALT_ROUNDS);
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 expiresAt;
if (expiresIn) {
expiresAt = new Date();
expiresAt.setSeconds(expiresAt.getSeconds() + expiresIn);
}
serviceTokenData = await new ServiceTokenData({
name,
workspace: workspaceId,
environment,
user: req.user._id,
expiresAt,
secretHash,
encryptedKey,
iv,
tag
}).save();
let user, serviceAccount;
// return service token data without sensitive data
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User) {
user = req.authData.authPayload._id;
}
if (!serviceTokenData) throw new Error('Failed to find service token data');
if (
req.authData.authMode === AUTH_MODE_SERVICE_ACCOUNT &&
req.authData.authPayload instanceof ServiceAccount
) {
serviceAccount = req.authData.authPayload._id;
}
serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
serviceTokenData = await new ServiceTokenData({
name,
workspace: workspaceId,
user,
serviceAccount,
scopes,
lastUsed: new Date(),
expiresAt,
secretHash,
encryptedKey,
iv,
tag,
permissions
}).save();
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to create service token data'
});
}
// return service token data without sensitive data
serviceTokenData = await ServiceTokenData.findById(serviceTokenData._id);
return res.status(200).send({
serviceToken,
serviceTokenData
});
}
if (!serviceTokenData) throw new Error("Failed to find service token data");
const serviceToken = `st.${serviceTokenData._id.toString()}.${secret}`;
return res.status(200).send({
serviceToken,
serviceTokenData
});
};
/**
* Delete service token data with id [serviceTokenDataId].
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const deleteServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData;
try {
const { serviceTokenDataId } = req.params;
const { serviceTokenDataId } = req.params;
serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
const serviceTokenData = await ServiceTokenData.findByIdAndDelete(serviceTokenDataId);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to delete service token data'
});
}
return res.status(200).send({
serviceTokenData
});
}
function UnauthorizedRequestError(arg0: { message: string; }) {
throw new Error('Function not implemented.');
}
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 { NODE_ENV } from '../../config';
import request from '../../config/request';
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
@ -19,128 +19,135 @@ import request from '../../config/request';
*/
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
}: {
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;
} = req.body;
const {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
organizationName,
}: {
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;
} = req.body;
// get user
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'
});
}
// get user
user = await User.findOne({ email });
// 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 || (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 (!user)
throw new Error('Failed to complete account for non-existent user'); // ensure user is non-null
// 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,
});
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user
});
if (!user)
throw new Error("Failed to complete account for non-existent user"); // ensure user is non-null
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);
// initialize default organization and workspace
await initializeDefaultOrg({
organizationName,
user,
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id.toString()
});
// 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(),
});
});
token = tokens.token;
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED,
},
{
user,
status: ACCEPTED,
}
);
// sending a welcome email to new users
if (process.env.LOOPS_API_KEY) {
await request.post("https://app.loops.so/api/v1/events/send", {
"email": email,
"eventName": "Sign Up",
"firstName": firstName,
"lastName": lastName
}, {
headers: {
"Accept": "application/json",
"Authorization": "Bearer " + process.env.LOOPS_API_KEY
},
});
}
// 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: NODE_ENV === 'production' ? true : false
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to complete account setup'
});
}
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(),
});
return res.status(200).send({
message: 'Successfully set up account',
message: "Successfully set up account",
user,
token
token,
});
};
@ -153,98 +160,103 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
*/
export const completeAccountInvite = async (req: Request, res: Response) => {
let user, token, refreshToken;
try {
const {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
} = req.body;
const {
email,
firstName,
lastName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier,
} = req.body;
// get user
user = await User.findOne({ email });
// get user
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 (!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",
});
}
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
status: INVITED
});
const membershipOrg = await MembershipOrg.findOne({
inviteEmail: email,
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({
userId: user._id.toString(),
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag,
salt,
verifier
});
// 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');
if (!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(),
});
});
// update organization membership statuses that are
// invited to completed with user attached
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED
},
{
user,
status: ACCEPTED
}
);
await MembershipOrg.updateMany(
{
inviteEmail: email,
status: INVITED,
},
{
user,
status: ACCEPTED,
}
);
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id.toString()
});
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
});
token = tokens.token;
token = tokens.token;
// store (refresh) token in httpOnly cookie
res.cookie("jid", tokens.refreshToken, {
httpOnly: true,
path: "/",
sameSite: "strict",
secure: await getHttpsEnabled(),
});
// store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, {
httpOnly: true,
path: '/',
sameSite: 'strict',
secure: NODE_ENV === 'production' ? true : false
});
} 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,72 +1,59 @@
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 [err, createdTag] = await to(Tag.create(sanitizedTagToCreate))
if (err) {
if ((err as MongoError).code === 11000) {
throw BadRequestError({ message: "Tags must be unique in a workspace" })
}
throw err
}
res.json(createdTag)
}
const { workspaceId } = req.params;
const { name, slug } = req.body;
const tagToCreate = {
name,
workspace: new Types.ObjectId(workspaceId),
slug,
user: new Types.ObjectId(req.user._id),
};
const createdTag = await new Tag(tagToCreate).save();
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)
if (!tagFromDB) {
throw BadRequestError()
}
const tagFromDB = await Tag.findById(tagId);
if (!tagFromDB) {
throw BadRequestError();
}
// can only delete if the request user is one that belongs to the same workspace as the tag
const membership = await Membership.findOne({
user: req.user,
workspace: tagFromDB.workspace
});
// can only delete if the request user is one that belongs to the same workspace as the tag
const membership = await Membership.findOne({
user: req.user,
workspace: tagFromDB.workspace
});
if (!membership) {
UnauthorizedRequestError({ message: 'Failed to validate membership' });
}
if (!membership) {
UnauthorizedRequestError({ message: "Failed to validate membership" });
}
const result = await Tag.findByIdAndDelete(tagId);
const result = await Tag.findByIdAndDelete(tagId);
// remove the tag from secrets
await Secret.updateMany(
{ tags: { $in: [tagId] } },
{ $pull: { tags: tagId } }
);
// remove the tag from secrets
await Secret.updateMany({ tags: { $in: [tagId] } }, { $pull: { tags: tagId } });
res.json(result);
}
res.json(result);
};
export const getWorkspaceTags = async (req: Request, res: Response) => {
const { workspaceId } = req.params
const workspaceTags = await Tag.find({ workspace: workspaceId })
return res.json({
workspaceTags
})
}
const { workspaceId } = req.params;
const workspaceTags = await Tag.find({
workspace: new Types.ObjectId(workspaceId)
});
return res.json({
workspaceTags
});
};

View File

@ -1,9 +1,14 @@
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 {
MembershipOrg,
User,
MembershipOrg
} from '../../models';
APIKeyData,
TokenVersion
} from "../../models";
import { getSaltRounds } from "../../config";
/**
* Return the current user.
@ -37,21 +42,12 @@ export const getMe = async (req: Request, res: Response) => {
}
}
*/
let user;
try {
user = await User
.findById(req.user._id)
.select('+publicKey +encryptedPrivateKey +iv +tag');
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get current user'
});
}
const user = await User
.findById(req.user._id)
.select("+salt +publicKey +encryptedPrivateKey +iv +tag +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag");
return res.status(200).send({
user
user,
});
}
@ -64,32 +60,23 @@ 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'];
} 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 { 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"];
} else {
req.user.mfaMethods = [];
}
await req.user.save();
const user = req.user;
return res.status(200).send({
user
user,
});
}
@ -126,22 +113,116 @@ export const getMyOrganizations = async (req: Request, res: Response) => {
}
}
*/
let organizations;
try {
organizations = (
await MembershipOrg.find({
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"
});
}
const organizations = (
await MembershipOrg.find({
user: req.user._id,
}).populate("organization")
).map((m) => m.organization);
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,19 @@
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 {
Workspace,
Secret,
Membership,
MembershipOrg,
Integration,
IntegrationAuth,
Key,
IUser,
ServiceToken,
ServiceTokenData
} from '../../models';
Membership,
ServiceTokenData,
Workspace,
} from "../../models";
import {
v2PushSecrets as push,
pullSecrets as pull,
reformatPullSecrets
} from '../../helpers/secret';
import { pushKeys } from '../../helpers/key';
import { postHogClient, EventService } from '../../services';
import { eventPushSecrets } from '../../events';
v2PushSecrets as push,
reformatPullSecrets,
} 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,67 +40,60 @@ interface V2PushSecret {
*/
export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
// upload (encrypted) secrets to workspace with id [workspaceId]
try {
let { secrets }: { secrets: V2PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
const { workspaceId } = req.params;
const postHogClient = await TelemetryService.getPostHogClient();
let { secrets }: { secrets: V2PushSecret[] } = req.body;
const { keys, environment, channel } = req.body;
const { workspaceId } = req.params;
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error("Failed to validate environment");
}
// sanitize secrets
secrets = secrets.filter(
(s: V2PushSecret) => s.secretKeyCiphertext !== '' && s.secretValueCiphertext !== ''
);
// sanitize secrets
secrets = secrets.filter(
(s: V2PushSecret) => s.secretKeyCiphertext !== "" && s.secretValueCiphertext !== ""
);
await push({
userId: req.user._id,
workspaceId,
environment,
secrets,
channel: channel ? channel : 'cli',
ipAddress: req.ip
});
await push({
userId: req.user._id,
workspaceId,
environment,
secrets,
channel: channel ? channel : "cli",
ipAddress: req.realIP,
});
await pushKeys({
userId: req.user._id,
workspaceId,
keys
});
await pushKeys({
userId: req.user._id,
workspaceId,
keys,
});
if (postHogClient) {
postHogClient.capture({
event: 'secrets pushed',
distinctId: req.user.email,
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : 'cli'
}
});
}
if (postHogClient) {
postHogClient.capture({
event: "secrets pushed",
distinctId: req.user.email,
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : "cli",
},
});
}
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId
})
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to upload workspace secrets'
});
}
// trigger event - push secrets
EventService.handleEvent({
event: eventPushSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
}),
});
return res.status(200).send({
message: 'Successfully uploaded workspace secrets'
message: "Successfully uploaded workspace secrets",
});
};
@ -120,58 +106,51 @@ export const pushWorkspaceSecrets = async (req: Request, res: Response) => {
*/
export const pullSecrets = async (req: Request, res: Response) => {
let secrets;
try {
const environment: string = req.query.environment as string;
const channel: string = req.query.channel as string;
const { workspaceId } = req.params;
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;
let userId;
if (req.user) {
userId = req.user._id.toString();
} else if (req.serviceTokenData) {
userId = req.serviceTokenData.user._id
}
// validate environment
const workspaceEnvs = req.membership.workspace.environments;
if (!workspaceEnvs.find(({ slug }: { slug: string }) => slug === environment)) {
throw new Error('Failed to validate environment');
}
let userId;
if (req.user) {
userId = req.user._id.toString();
} else if (req.serviceTokenData) {
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");
}
secrets = await pull({
userId,
workspaceId,
environment,
channel: channel ? channel : 'cli',
ipAddress: req.ip
});
secrets = await pull({
userId,
workspaceId,
environment,
channel: channel ? channel : "cli",
ipAddress: req.realIP,
});
if (channel !== 'cli') {
secrets = reformatPullSecrets({ secrets });
}
if (channel !== "cli") {
secrets = reformatPullSecrets({ secrets });
}
if (postHogClient) {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: 'secrets pulled',
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
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'
});
}
if (postHogClient) {
// capture secrets pushed event in production
postHogClient.capture({
distinctId: req.user.email,
event: "secrets pulled",
properties: {
numberOfSecrets: secrets.length,
environment,
workspaceId,
channel: channel ? channel : "cli",
},
});
}
return res.status(200).send({
secrets
secrets,
});
};
@ -205,22 +184,14 @@ export const getWorkspaceKey = async (req: Request, res: Response) => {
}
*/
let key;
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id
}).populate('sender', '+publicKey');
key = await Key.findOne({
workspace: workspaceId,
receiver: req.user._id,
}).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);
}
@ -228,26 +199,16 @@ export const getWorkspaceServiceTokenData = async (
req: Request,
res: Response
) => {
let serviceTokenData;
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
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'
});
}
const serviceTokenData = await ServiceTokenData
.find({
workspace: workspaceId,
})
.select("+encryptedKey +iv +tag");
return res.status(200).send({
serviceTokenData
serviceTokenData,
});
}
@ -291,23 +252,14 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
}
}
*/
let memberships;
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
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'
});
}
const memberships = await Membership.find({
workspace: workspaceId,
}).populate("user", "+publicKey");
return res.status(200).send({
memberships
memberships,
});
}
@ -371,31 +323,22 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
}
}
*/
let membership;
try {
const {
membershipId
} = req.params;
const { role } = req.body;
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'
});
}
const {
membershipId,
} = req.params;
const { role } = req.body;
const membership = await Membership.findByIdAndUpdate(
membershipId,
{
role,
}, {
new: true,
}
);
return res.status(200).send({
membership
membership,
});
}
@ -442,30 +385,21 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
}
}
*/
let membership;
try {
const {
membershipId
} = req.params;
membership = await Membership.findByIdAndDelete(membershipId);
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'
});
}
const {
membershipId,
} = req.params;
const membership = await Membership.findByIdAndDelete(membershipId);
if (!membership) throw new Error("Failed to delete workspace membership");
await Key.deleteMany({
receiver: membership.user,
workspace: membership.workspace,
});
return res.status(200).send({
membership
membership,
});
}
@ -476,33 +410,23 @@ 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;
const { workspaceId } = req.params;
const { autoCapitalization } = req.body;
workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId
},
{
autoCapitalization
},
{
new: true
}
);
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to change autoCapitalization setting'
});
}
const workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId,
},
{
autoCapitalization,
},
{
new: true,
}
);
return res.status(200).send({
message: 'Successfully changed autoCapitalization setting',
workspace
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) {
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) {
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,420 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { EventService, SecretService } from "../../services";
import { eventPushSecrets } from "../../events";
import { BotService } from "../../services";
import { repackageSecretToRaw } from "../../helpers/secrets";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
/**
* Return secrets for workspace with id [workspaceId] and environment
* [environment] in plaintext
* @param req
* @param res
*/
export const getSecretsRaw = 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 secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
authData: req.authData,
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: new Types.ObjectId(workspaceId),
});
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,
}),
});
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,
}),
});
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,
}),
});
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 secrets = await SecretService.getSecrets({
workspaceId: new Types.ObjectId(workspaceId),
environment,
secretPath,
authData: req.authData,
});
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,
}),
});
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,
}),
});
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,
}),
});
return res.status(200).send({
secret,
});
};

View File

@ -0,0 +1,194 @@
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";
/**
* 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
// 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,
},
});
}
} 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,17 @@
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 workspaceController from "./workspaceController";
import * as actionController from "./actionController";
import * as membershipController from "./membershipController";
import * as cloudProductsController from "./cloudProductsController";
export {
stripeController,
secretController,
secretSnapshotController,
organizationsController,
workspaceController,
actionController,
membershipController
membershipController,
cloudProductsController,
}

View File

@ -2,22 +2,22 @@ import { Request, Response } from "express";
import { Membership, Workspace } from "../../../models";
import { IMembershipPermission } from "../../../models/membership";
import { BadRequestError, UnauthorizedRequestError } from "../../../utils/errors";
import { ABILITY_READ, ABILITY_WRITE, ADMIN, MEMBER } from "../../../variables/organization";
import { Builder } from "builder-pattern"
import { ADMIN, MEMBER } from "../../../variables/organization";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../../variables";
import _ from "lodash";
export const denyMembershipPermissions = async (req: Request, res: Response) => {
const { membershipId } = req.params;
const { permissions } = req.body;
const sanitizedMembershipPermissions: IMembershipPermission[] = permissions.map((permission: IMembershipPermission) => {
if (!permission.ability || !permission.environmentSlug || ![ABILITY_READ, ABILITY_WRITE].includes(permission.ability)) {
if (!permission.ability || !permission.environmentSlug || ![PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS].includes(permission.ability)) {
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)
@ -38,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)) {
@ -58,6 +58,6 @@ export const denyMembershipPermissions = async (req: Request, res: Response) =>
}
res.send({
permissionsDenied: updatedMembershipWithPermissions.deniedPermissions
permissionsDenied: updatedMembershipWithPermissions.deniedPermissions,
})
}

View File

@ -0,0 +1,197 @@
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);
}
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);
}
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,16 +1,15 @@
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
* @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,41 +54,31 @@ import { EESecretService } from '../../services';
}
}
*/
let secretVersions;
try {
const { secretId } = req.params;
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({
secret: secretId
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
} 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
});
}
const secretVersions = await SecretVersion.find({
secret: secretId
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
return res.status(200).send({
secretVersions
});
};
/**
* Roll back secret with id [secretId] to version [version]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const rollbackSecretVersion = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Roll back secret to a version.'
#swagger.description = 'Roll back secret to a version.'
@ -137,88 +126,92 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
}
}
*/
let secret;
try {
const { secretId } = req.params;
const { version } = req.body;
// validate secret version
const oldSecretVersion = await SecretVersion.findOne({
secret: secretId,
version
});
if (!oldSecretVersion) throw new Error('Failed to find secret version');
const {
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
} = oldSecretVersion;
// update secret
secret = await Secret.findByIdAndUpdate(
secretId,
{
$inc: {
version: 1
},
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
},
{
new: true
}
);
if (!secret) throw new Error('Failed to find and update secret');
const { secretId } = req.params;
const { version } = req.body;
// add new secret version
await new SecretVersion({
secret: secretId,
version: secret.version,
workspace,
type,
user,
environment,
isDeleted: false,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag
}).save();
// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: secret.workspace.toString()
});
} 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
});
}
// validate secret version
const oldSecretVersion = await SecretVersion.findOne({
secret: secretId,
version
}).select("+secretBlindIndex");
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
const secret = await Secret.findByIdAndUpdate(
secretId,
{
$inc: {
version: 1
},
workspace,
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");
// add new secret version
await new SecretVersion({
secret: secretId,
version: secret.version,
workspace,
type,
user,
environment,
isDeleted: false,
...(secretBlindIndex ? { secretBlindIndex } : {}),
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
folder,
algorithm,
keyEncoding
}).save();
// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId: secret.workspace,
environment,
folderId: folder
});
return res.status(200).send({
secret
});
};

View File

@ -1,39 +1,45 @@
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]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getSecretSnapshot = async (req: Request, res: Response) => {
let secretSnapshot;
try {
const { secretSnapshotId } = req.params;
const { secretSnapshotId } = req.params;
secretSnapshot = await SecretSnapshot
.findById(secretSnapshotId)
.populate({
path: 'secretVersions',
populate: {
path: 'tags',
model: 'Tag',
}
});
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'
});
}
return res.status(200).send({
secretSnapshot
});
}
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId)
.lean()
.populate<{ secretVersions: ISecretVersion[] }>({
path: "secretVersions",
populate: {
path: "tags",
model: "Tag",
},
})
.populate<{ folderVersion: TFolderRootVersionSchema }>("folderVersion");
if (!secretSnapshot) throw new Error("Failed to find 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,
});
};

View File

@ -1,40 +0,0 @@
import { Request, Response } from 'express';
import * as Sentry from '@sentry/node';
import Stripe from 'stripe';
import { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET } from '../../../config';
const stripe = new Stripe(STRIPE_SECRET_KEY, {
apiVersion: '2022-08-01'
});
/**
* 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 sig = req.headers['stripe-signature'] as string;
event = stripe.webhooks.constructEvent(
req.body,
sig,
STRIPE_WEBHOOK_SECRET // ?
);
} 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,25 +1,29 @@
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,
Log,
SecretVersion,
ISecretVersion
} from '../../models';
import { EESecretService } from '../../services';
import { getLatestSecretVersionIds } from '../../helpers/secretVersion';
FolderVersion,
ISecretVersion,
Log,
SecretSnapshot,
SecretVersion,
TFolderRootVersionSchema,
} from "../../models";
import { EESecretService } from "../../services";
import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
import Folder, { TFolderSchema } from "../../../models/folder";
import { searchByFolderId } from "../../../services/FolderService";
/**
* Return secret snapshots for workspace with id [workspaceId]
* @param req
* @param res
* @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,66 +68,60 @@ import { getLatestSecretVersionIds } from '../../helpers/secretVersion';
}
}
*/
let secretSnapshots;
try {
const { workspaceId } = req.params;
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
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
} 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
});
}
const secretSnapshots = await SecretSnapshot.find({
workspace: workspaceId,
environment,
folderId: folderId || "root",
})
.sort({ createdAt: -1 })
.skip(offset)
.limit(limit);
return res.status(200).send({
secretSnapshots,
});
};
/**
* Return count of secret snapshots for workspace with id [workspaceId]
* @param req
* @param res
* @param req
* @param res
*/
export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Response) => {
let count;
try {
const { workspaceId } = req.params;
count = await SecretSnapshot.countDocuments({
workspace: workspaceId
});
} 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
});
}
export const getWorkspaceSecretSnapshotsCount = async (
req: Request,
res: Response
) => {
const { workspaceId } = req.params;
const { environment, folderId } = req.query;
const count = await SecretSnapshot.countDocuments({
workspace: workspaceId,
environment,
folderId: folderId || "root",
});
return res.status(200).send({
count,
});
};
/**
* Rollback secret snapshot with id [secretSnapshotId] to version [version]
* @param req
* @param res
* @returns
* @param req
* @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,160 +171,329 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
}
}
*/
let secrets;
try {
const { workspaceId } = req.params;
const { version } = req.body;
// validate secret snapshot
const secretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId,
version
}).populate<{ secretVersions: ISecretVersion[]}>('secretVersions');
if (!secretSnapshot) throw new Error('Failed to find secret snapshot');
// 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)
});
// TODO: fix any
const latestSecretVersions: any = (await SecretVersion.find({
_id: {
$in: latestSecretVersionIds.map((s) => s.versionId)
}
}, 'secret version'))
.reduce((accumulator, s) => ({
...accumulator,
[`${s.secret.toString()}`]: s
}), {});
// delete existing secrets
await Secret.deleteMany({
workspace: workspaceId
});
const { workspaceId } = req.params;
const { version, environment, folderId = "root" } = req.body;
// add secrets
secrets = await Secret.insertMany(
secretSnapshot.secretVersions.map((sv) => {
const secretId = sv.secret;
const {
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
createdAt
} = oldSecretVersionsObj[secretId.toString()];
return ({
_id: secretId,
version: latestSecretVersions[secretId.toString()].version + 1,
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash,
secretCommentCiphertext: '',
secretCommentIV: '',
secretCommentTag: '',
createdAt
});
})
);
// add secret versions
await SecretVersion.insertMany(
secrets.map(({
_id,
version,
workspace,
type,
user,
environment,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
}) => ({
_id: new Types.ObjectId(),
secret: _id,
version,
workspace,
type,
user,
environment,
isDeleted: false,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretKeyHash,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretValueHash
}))
);
// update secret versions of restored secrets as not deleted
await SecretVersion.updateMany({
secret: {
$in: secretSnapshot.secretVersions.map((sv) => sv.secret)
}
}, {
isDeleted: false
});
// take secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId
});
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to roll back secret snapshot'
});
// validate secret snapshot
const secretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId,
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");
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);
}
return res.status(200).send({
secrets
});
}
}
// TODO: fix any
const latestSecretVersionIds = await getLatestSecretVersionIds({
secretIds,
});
// TODO: fix any
const latestSecretVersions: any = (
await SecretVersion.find(
{
_id: {
$in: latestSecretVersionIds.map((s) => s.versionId),
},
},
"secret version"
)
).reduce(
(accumulator, s) => ({
...accumulator,
[`${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(secDelQuery);
await Folder.deleteOne({
workspace: workspaceId,
environment,
});
// add secrets
const secrets = await Secret.insertMany(
Object.keys(oldSecretVersionsObj).map((sv) => {
const {
secret: secretId,
workspace,
type,
user,
environment,
secretBlindIndex,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
createdAt,
algorithm,
keyEncoding,
folder: secFolderId,
} = oldSecretVersionsObj[sv];
return {
_id: secretId,
version: latestSecretVersions[secretId.toString()].version + 1,
workspace,
type,
user,
environment,
secretBlindIndex: secretBlindIndex ?? undefined,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
secretCommentCiphertext: "",
secretCommentIV: "",
secretCommentTag: "",
createdAt,
algorithm,
keyEncoding,
folder: secFolderId,
};
})
);
// add secret versions
const secretV = await SecretVersion.insertMany(
secrets.map(
({
_id,
version,
workspace,
type,
user,
environment,
secretBlindIndex,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
algorithm,
keyEncoding,
folder: secFolderId,
}) => ({
_id: new Types.ObjectId(),
secret: _id,
version,
workspace,
type,
user,
environment,
isDeleted: false,
secretBlindIndex: secretBlindIndex ?? undefined,
secretKeyCiphertext,
secretKeyIV,
secretKeyTag,
secretValueCiphertext,
secretValueIV,
secretValueTag,
algorithm,
keyEncoding,
folder: secFolderId,
})
)
);
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: new Types.ObjectId(workspaceId),
environment,
folderId,
});
return res.status(200).send({
secrets,
});
};
/**
* Return (audit) logs for workspace with id [workspaceId]
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getWorkspaceLogs = async (req: Request, res: Response) => {
/*
/*
#swagger.summary = 'Return project (audit) logs'
#swagger.description = 'Return project (audit) logs'
@ -392,43 +559,32 @@ export const getWorkspaceLogs = async (req: Request, res: Response) => {
}
}
*/
let logs
try {
const { workspaceId } = req.params;
const { workspaceId } = req.params;
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
const sortBy: string = req.query.sortBy as string;
const userId: string = req.query.userId as string;
const actionNames: string = req.query.actionNames as string;
logs = await Log.find({
workspace: workspaceId,
...( userId ? { user: userId } : {}),
...(
actionNames
? {
actionNames: {
$in: actionNames.split(',')
}
} : {}
)
})
.sort({ createdAt: sortBy === 'recent' ? -1 : 1 })
.skip(offset)
.limit(limit)
.populate('actions')
.populate('user');
const offset: number = parseInt(req.query.offset as string);
const limit: number = parseInt(req.query.limit as string);
const sortBy: string = req.query.sortBy as string;
const userId: string = req.query.userId as string;
const actionNames: string = req.query.actionNames as string;
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
return res.status(400).send({
message: 'Failed to get workspace logs'
});
}
return res.status(200).send({
logs
});
}
const logs = await Log.find({
workspace: workspaceId,
...(userId ? { user: userId } : {}),
...(actionNames
? {
actionNames: {
$in: actionNames.split(","),
},
}
: {}),
})
.sort({ createdAt: sortBy === "recent" ? -1 : 1 })
.skip(offset)
.limit(limit)
.populate("actions")
.populate("user serviceAccount serviceTokenData");
return res.status(200).send({
logs,
});
};

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
@ -24,39 +23,37 @@ import {
const createActionUpdateSecret = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds
secretIds,
}: {
name: string;
userId: Types.ObjectId;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
let action;
try {
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds,
n: 2
}))
.map((s) => ({
oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id
}));
action = await new Action({
name,
user: userId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create update secret action');
}
const latestSecretVersions = (await getLatestNSecretSecretVersionIds({
secretIds,
n: 2,
}))
.map((s) => ({
oldSecretVersion: s.versions[0]._id,
newSecretVersion: s.versions[1]._id,
}));
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions,
},
}).save();
return action;
}
@ -72,68 +69,66 @@ const createActionUpdateSecret = async ({
const createActionSecret = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds
secretIds,
}: {
name: string;
userId: Types.ObjectId;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
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
}))
.map((s) => ({
newSecretVersion: s.versionId
}));
action = await new Action({
name,
user: userId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions
}
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action create/read/delete secret action');
}
// case: action is adding, deleting, or reading secrets
// -> add new secret versions
const latestSecretVersions = (await getLatestSecretVersionIds({
secretIds,
}))
.map((s) => ({
newSecretVersion: s.versionId,
}));
const action = await new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId,
payload: {
secretVersions: latestSecretVersions,
},
}).save();
return action;
}
/**
* Create an (audit) action for user with id [userId]
* Create an (audit) action for client with id [userId],
* [serviceAccountId], or [serviceTokenDataId]
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {String} obj.userId - id of user associated with action
* @returns
*/
const createActionUser = ({
const createActionClient = ({
name,
userId
userId,
serviceAccountId,
serviceTokenDataId,
}: {
name: string;
userId: Types.ObjectId;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
}) => {
let action;
try {
action = new Action({
name,
user: userId
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create user action');
}
const action = new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
}).save();
return action;
}
@ -149,54 +144,52 @@ const createActionUser = ({
const createActionHelper = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId: Types.ObjectId;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
secretIds?: Types.ObjectId[];
}) => {
let action;
try {
switch (name) {
case ACTION_LOGIN:
case ACTION_LOGOUT:
action = await createActionUser({
name,
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');
action = await createActionSecret({
name,
userId,
workspaceId,
secretIds
});
break;
case ACTION_UPDATE_SECRETS:
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
});
break;
}
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create action');
switch (name) {
case ACTION_LOGIN:
case ACTION_LOGOUT:
action = await createActionClient({
name,
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");
action = await createActionSecret({
name,
userId,
workspaceId,
secretIds,
});
break;
case ACTION_UPDATE_SECRETS:
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,
});
break;
}
return action;
}
export {
createActionHelper
};
createActionHelper,
};

View File

@ -1,8 +1,9 @@
import { Types } from "mongoose";
import _ from "lodash";
import { Membership } from "../../models";
import { ABILITY_READ, ABILITY_WRITE } from "../../variables/organization";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, environment: any, action: any) => {
export const userHasWorkspaceAccess = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string, action: any) => {
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
if (!membershipForWorkspace) {
return false
@ -18,15 +19,15 @@ export const userHasWorkspaceAccess = async (userId: any, workspaceId: any, envi
return true
}
export const userHasWriteOnlyAbility = async (userId: any, workspaceId: any, environment: any) => {
export const userHasWriteOnlyAbility = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string) => {
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
if (!membershipForWorkspace) {
return false
}
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_WRITE });
const isReadDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_READ });
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
const isReadDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
// case: you have write only if read is blocked and write is not
if (isReadDisallowed && !isWriteDisallowed) {
@ -36,15 +37,15 @@ export const userHasWriteOnlyAbility = async (userId: any, workspaceId: any, env
return false
}
export const userHasNoAbility = async (userId: any, workspaceId: any, environment: any) => {
export const userHasNoAbility = async (userId: Types.ObjectId, workspaceId: Types.ObjectId, environment: string) => {
const membershipForWorkspace = await Membership.findOne({ workspace: workspaceId, user: userId })
if (!membershipForWorkspace) {
return true
}
const deniedMembershipPermissions = membershipForWorkspace.deniedPermissions;
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_WRITE });
const isReadBlocked = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: ABILITY_READ });
const isWriteDisallowed = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_WRITE_SECRETS });
const isReadBlocked = _.some(deniedMembershipPermissions, { environmentSlug: environment, ability: PERMISSION_READ_SECRETS });
if (isReadBlocked && isWriteDisallowed) {
return true

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
@ -16,36 +16,35 @@ import {
*/
const createLogHelper = async ({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
actions,
channel,
ipAddress
ipAddress,
}: {
userId: Types.ObjectId;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
actions: IAction[];
channel: string;
ipAddress: string;
}) => {
let log;
try {
log = await new Log({
user: userId,
workspace: workspaceId ?? undefined,
actionNames: actions.map((a) => a.name),
actions,
channel,
ipAddress
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to create log');
}
const log = await new Log({
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
workspace: workspaceId ?? undefined,
actionNames: actions.map((a) => a.name),
actions,
channel,
ipAddress,
}).save();
return log;
}
export {
createLogHelper
}
createLogHelper,
}

View File

@ -1,14 +1,11 @@
import { Types } from 'mongoose';
import * as Sentry from '@sentry/node';
import { Types } from "mongoose";
import { Secret } from "../../models";
import {
Secret,
ISecret
} from '../../models';
import {
SecretSnapshot,
SecretVersion,
ISecretVersion
} from '../models';
FolderVersion,
ISecretVersion,
SecretSnapshot,
SecretVersion,
} 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([
{
$match: {
environment,
workspace: new Types.ObjectId(workspaceId),
secret: {
$in: secretIds,
},
},
},
{
$group: {
_id: "$secret",
version: { $max: "$version" },
versionId: { $max: "$_id" }, // secret version id
},
},
{
$sort: { version: -1 },
},
]).exec()
).map((s) => s.versionId);
const latestFolderVersion = await FolderVersion.findOne({
environment,
workspace: workspaceId,
"nodes.id": folderId,
}).sort({ "nodes.version": -1 });
const latestSecretVersions = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds
}
}
},
{
$group: {
_id: '$secret',
version: { $max: '$version' },
versionId: { $max: '$_id' } // secret version id
}
},
{
$sort: { version: -1 }
}
])
.exec())
.map((s) => s.versionId);
const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId,
}).sort({ version: -1 });
const latestSecretSnapshot = await SecretSnapshot.findOne({
workspace: workspaceId
}).sort({ version: -1 });
const secretSnapshot = await new SecretSnapshot({
workspace: workspaceId,
environment,
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
secretVersions: latestSecretVersions,
folderId,
folderVersion: latestFolderVersion,
}).save();
secretSnapshot = await new SecretSnapshot({
workspace: workspaceId,
version: latestSecretSnapshot ? latestSecretSnapshot.version + 1 : 1,
secretVersions: latestSecretVersions
}).save();
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to take a secret snapshot');
}
return secretSnapshot;
}
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;
}
return newSecretVersions;
};
const markDeletedSecretVersionsHelper = async ({
secretIds
secretIds,
}: {
secretIds: Types.ObjectId[];
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');
}
}
/**
* 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');
}
}
await SecretVersion.updateMany(
{
secret: { $in: secretIds },
},
{
isDeleted: true,
},
{
new: true,
}
);
};
export {
takeSecretSnapshotHelper,
addSecretVersionsHelper,
markDeletedSecretVersionsHelper,
initSecretVersioningHelper
}
takeSecretSnapshotHelper,
addSecretVersionsHelper,
markDeletedSecretVersionsHelper,
};

View File

@ -1,110 +1,82 @@
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]
* @param {Object} obj
* @param {Object} obj.secretIds = ids of secrets to get latest versions for
* @returns
* @returns
*/
const getLatestSecretVersionIds = async ({
secretIds
secretIds,
}: {
secretIds: Types.ObjectId[];
secretIds: Types.ObjectId[];
}) => {
interface LatestSecretVersionId {
_id: Types.ObjectId;
version: number;
versionId: Types.ObjectId;
}
let latestSecretVersionIds: LatestSecretVersionId[];
try {
latestSecretVersionIds = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds
}
}
},
{
$group: {
_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');
}
return latestSecretVersionIds;
}
const latestSecretVersionIds = await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds,
},
},
},
{
$group: {
_id: "$secret",
version: { $max: "$version" },
versionId: { $max: "$_id" }, // id of latest secret version
},
},
{
$sort: { version: -1 },
},
]).exec();
return latestSecretVersionIds;
};
/**
* Return latest [n] secret versions for secrets with ids [secretIds]
* @param {Object} obj
* @param {Object} obj.secretIds = ids of secrets to get latest versions for
* @param {Number} obj.n - number of latest secret versions to return for each secret
* @returns
* @returns
*/
const getLatestNSecretSecretVersionIds = async ({
secretIds,
n
secretIds,
n,
}: {
secretIds: Types.ObjectId[];
n: number;
secretIds: Types.ObjectId[];
n: number;
}) => {
// TODO: optimize query
let latestNSecretVersions;
try {
latestNSecretVersions = (await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds,
},
},
},
{
$sort: { version: -1 },
},
{
$group: {
_id: "$secret",
versions: { $push: "$$ROOT" },
},
},
{
$project: {
_id: 0,
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;
}
// TODO: optimize query
const latestNSecretVersions = await SecretVersion.aggregate([
{
$match: {
secret: {
$in: secretIds,
},
},
},
{
$sort: { version: -1 },
},
{
$group: {
_id: "$secret",
versions: { $push: "$$ROOT" },
},
},
{
$project: {
_id: 0,
secret: "$_id",
versions: { $slice: ["$versions", n] },
},
},
]);
export {
getLatestSecretVersionIds,
getLatestNSecretSecretVersionIds
}
return latestNSecretVersions;
};
export { getLatestSecretVersionIds, getLatestNSecretSecretVersionIds };

View File

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

View File

@ -1,4 +1,4 @@
import { Request, Response, NextFunction } from 'express';
import { NextFunction, Request, Response } from "express";
/**
* Validate if organization hosting meets license requirements to
@ -7,7 +7,7 @@ import { Request, Response, NextFunction } from 'express';
* @param {String[]} obj.acceptedTiers
*/
const requireLicenseAuth = ({
acceptedTiers
acceptedTiers,
}: {
acceptedTiers: string[];
}) => {

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,32 +15,28 @@ import {
const requireSecretSnapshotAuth = ({
acceptedRoles,
}: {
acceptedRoles: string[];
acceptedRoles: Array<"admin" | "member">;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const { secretSnapshotId } = req.params;
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId);
if (!secretSnapshot) {
return next(SecretSnapshotNotFoundError({
message: 'Failed to find secret snapshot'
}));
}
await validateMembership({
userId: req.user._id.toString(),
workspaceId: secretSnapshot.workspace.toString(),
acceptedRoles
});
req.secretSnapshot = secretSnapshot as any;
next();
} catch (err) {
return next(UnauthorizedRequestError({ message: 'Unable to authenticate secret snapshot' }));
const { secretSnapshotId } = req.params;
const secretSnapshot = await SecretSnapshot.findById(secretSnapshotId);
if (!secretSnapshot) {
return next(SecretSnapshotNotFoundError({
message: "Failed to find secret snapshot",
}));
}
await validateMembership({
userId: req.user._id,
workspaceId: secretSnapshot.workspace,
acceptedRoles,
});
req.secretSnapshot = secretSnapshot as any;
next();
}
}

View File

@ -1,16 +1,18 @@
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;
user?: Types.ObjectId,
serviceAccount?: Types.ObjectId,
serviceTokenData?: Types.ObjectId,
workspace?: Types.ObjectId,
payload?: {
secretVersions?: Types.ObjectId[]
@ -28,35 +30,42 @@ 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',
required: true
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
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);
const Action = model<IAction>("Action", actionSchema);
export default Action;

View File

@ -0,0 +1,60 @@
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,
}
);
const FolderVersion = model<TFolderRootVersionSchema>(
"FolderVersion",
folderRootVersionSchema
);
export default FolderVersion;

View File

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

View File

@ -1,16 +1,18 @@
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;
user?: Types.ObjectId;
serviceAccount?: Types.ObjectId;
serviceTokenData?: Types.ObjectId;
workspace?: Types.ObjectId;
actionNames: string[];
actions: Types.ObjectId[];
@ -22,11 +24,19 @@ const logSchema = new Schema<ILog>(
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
ref: "ServiceTokenData",
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace'
ref: "Workspace",
},
actionNames: {
type: [String],
@ -36,28 +46,28 @@ 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);
const Log = model<ILog>("Log", logSchema);
export default Log;

View File

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

View File

@ -1,97 +1,132 @@
import { Schema, model, Types } from 'mongoose';
import { Schema, Types, model } from "mongoose";
import {
SECRET_SHARED,
SECRET_PERSONAL,
} from '../../variables';
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8,
SECRET_PERSONAL,
SECRET_SHARED,
} from "../../variables";
export interface ISecretVersion {
secret: Types.ObjectId;
version: number;
workspace: Types.ObjectId; // new
type: string; // new
user?: Types.ObjectId; // new
environment: string; // new
isDeleted: boolean;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
tags?: string[];
_id: Types.ObjectId;
secret: Types.ObjectId;
version: number;
workspace: Types.ObjectId; // new
type: string; // new
user?: Types.ObjectId; // new
environment: string; // new
isDeleted: boolean;
secretBlindIndex?: string;
secretKeyCiphertext: string;
secretKeyIV: string;
secretKeyTag: string;
secretValueCiphertext: string;
secretValueIV: string;
secretValueTag: string;
algorithm: "aes-256-gcm";
keyEncoding: "utf8" | "base64";
createdAt: string;
folder?: string;
tags?: string[];
}
const secretVersionSchema = new Schema<ISecretVersion>(
{
secret: { // could be deleted
type: Schema.Types.ObjectId,
ref: 'Secret',
required: true
},
version: {
type: Number,
default: 1,
required: true
},
workspace: {
type: Schema.Types.ObjectId,
ref: 'Workspace',
required: true
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: 'User'
},
environment: {
type: String,
required: true
},
isDeleted: { // consider removing field
type: Boolean,
default: false,
required: true
},
secretKeyCiphertext: {
type: String,
required: true
},
secretKeyIV: {
type: String, // symmetric
required: true
},
secretKeyTag: {
type: String, // symmetric
required: true
},
secretValueCiphertext: {
type: String,
required: true
},
secretValueIV: {
type: String, // symmetric
required: true
},
secretValueTag: {
type: String, // symmetric
required: true
},
tags: {
ref: 'Tag',
type: [Schema.Types.ObjectId],
default: []
},
},
{
timestamps: true
}
{
secret: {
// could be deleted
type: Schema.Types.ObjectId,
ref: "Secret",
required: true,
},
version: {
type: Number,
default: 1,
required: true,
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
required: true,
},
type: {
type: String,
enum: [SECRET_SHARED, SECRET_PERSONAL],
required: true,
},
user: {
// user associated with the personal secret
type: Schema.Types.ObjectId,
ref: "User",
},
environment: {
type: String,
required: true,
},
isDeleted: {
// consider removing field
type: Boolean,
default: false,
required: true,
},
secretBlindIndex: {
type: String,
select: false,
},
secretKeyCiphertext: {
type: String,
required: true,
},
secretKeyIV: {
type: String, // symmetric
required: true,
},
secretKeyTag: {
type: String, // symmetric
required: true,
},
secretValueCiphertext: {
type: String,
required: true,
},
secretValueIV: {
type: String, // symmetric
required: true,
},
secretValueTag: {
type: String, // symmetric
required: true,
},
algorithm: {
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
default: ALGORITHM_AES_256_GCM,
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true,
default: ENCODING_SCHEME_UTF8,
},
folder: {
type: String,
required: true,
},
tags: {
ref: "Tag",
type: [Schema.Types.ObjectId],
default: [],
},
},
{
timestamps: true,
}
);
const SecretVersion = model<ISecretVersion>('SecretVersion', secretVersionSchema);
const SecretVersion = model<ISecretVersion>(
"SecretVersion",
secretVersionSchema
);
export default SecretVersion;
export default SecretVersion;

View File

@ -1,15 +1,15 @@
import express from 'express';
import express from "express";
const router = express.Router();
import {
validateRequest
} from '../../../middleware';
import { param } from 'express-validator';
import { actionController } from '../../controllers/v1';
validateRequest,
} from "../../../middleware";
import { param } from "express-validator";
import { actionController } from "../../controllers/v1";
// TODO: put into action controller
router.get(
'/:actionId',
param('actionId').exists().trim(),
"/:actionId",
param("actionId").exists().trim(),
validateRequest,
actionController.getAction
);

View File

@ -0,0 +1,20 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
validateRequest,
} from "../../../middleware";
import { query } from "express-validator";
import { cloudProductsController } from "../../controllers/v1";
router.get(
"/",
requireAuth({
acceptedAuthModes: ["jwt", "apiKey"],
}),
query("billing-cycle").exists().isIn(["monthly", "yearly"]),
validateRequest,
cloudProductsController.getCloudProducts
);
export default router;

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