Compare commits

...

287 Commits

Author SHA1 Message Date
00e1742a30 Merge branch 'main' into patch-k8s-dependency-vulnerability 2023-12-08 15:36:45 -05:00
5055b5402c update kube proxy for helm 2023-12-08 15:35:54 -05:00
ff9418c0c7 patch: loop variable deployment captured by func literal 2023-12-08 15:35:22 -05:00
d03921eef3 update resty + patch kube-proxy 2023-12-08 15:17:01 -05:00
602afdefc3 Merge pull request #1221 from Infisical/k8s-doc-update-secret-type
add docs for k8 secret type and label propagation
2023-12-07 20:12:53 -05:00
5eb505326b add docs for k8 secret type and label propagation 2023-12-07 20:10:11 -05:00
fcf4153d87 Update Chart.yaml 2023-12-07 19:34:08 -05:00
097282c5e1 Merge pull request #1182 from Allex1/secret
Make secret type field configurable
2023-12-07 19:31:39 -05:00
0eeef9a66c revert managed secret name 2023-12-07 19:30:43 -05:00
df0bec8a68 update chart version 2023-12-07 19:28:57 -05:00
13014b5345 create separate struct for managed secret + propagate lables/annotations 2023-12-07 19:26:48 -05:00
66d0cae066 Merge pull request #1220 from akhilmhdh/fix/update-secret-approval
fix(secret-approval): resolved update failure in secret approval mode
2023-12-07 13:19:34 -05:00
8e82222fc5 fix(secret-approval): resolved update failure in secret approval mode and number not increasing on frontend 2023-12-07 23:48:00 +05:30
f822bcd10f Merge pull request #1218 from ntimo/patch-1
Fixed 'SMTP_PASSWORD' default value
2023-12-07 11:54:44 -05:00
c51f8c5838 Fixed 'SMTP_PASSWORD' default value 2023-12-06 21:41:34 +01:00
377a79f17d Make secret type field configurable 2023-12-05 10:13:20 +02:00
2a768a7bc4 Update postgres.mdx 2023-12-04 16:18:50 -08:00
4b41664fa4 chores: clean login 2023-12-02 13:28:32 -05:00
735cf093f0 Merge pull request #1210 from Infisical/hide-blind-index
Hide blind index notice
2023-11-30 18:15:58 -05:00
98906f190c Merge pull request #1205 from Infisical/add-docs-for-folders-cli
add docs for folder cli command
2023-11-30 17:28:49 -05:00
5f80e2f432 Merge pull request #1205 from Infisical/add-docs-for-folders-cli
add docs for folder cli command
2023-11-29 10:03:06 -05:00
afd6a7736a add docs for folder cli command 2023-11-29 09:57:45 -05:00
057fcb164d Add missing secret rotation link in changelog 2023-11-29 17:02:57 +07:00
b575c0e207 Merge pull request #1203 from Infisical/update-changelog
Update Changelog
2023-11-29 17:00:46 +07:00
372afa2111 Update changelog 2023-11-29 16:58:12 +07:00
33017b50f0 add filter for secret pull events 2023-11-28 20:09:32 -08:00
f5de501348 Fix typo in docs 2023-11-28 18:17:16 -08:00
758c3a2423 Merge pull request #1193 from quinton11/feat/cli-folder-cmd
feat: cli-folders-cmd
2023-11-28 19:31:04 -05:00
d01c6e4df9 remove init from folders cmd 2023-11-28 19:30:08 -05:00
ed94d218fd move folders to secrets 2023-11-28 19:27:30 -05:00
adc7be2e84 Merge pull request #1198 from DanielHougaard/daniel/assignment
(feat): Secret reminders
2023-11-28 19:58:40 +04:00
697485f8ed Requested changes 2023-11-28 19:46:59 +04:00
19e2523d0e Merge pull request #1196 from Infisical/infisica-agent
Infisical Agent
2023-11-28 10:24:55 -05:00
8851987ac4 Update secrets.ts 2023-11-28 13:50:47 +04:00
64d862ebe9 More typing updates 2023-11-28 13:50:42 +04:00
70d1cc0e06 Fixed rebase oopsie and updated for days instead of cron 2023-11-28 13:50:33 +04:00
aee91a9558 Updated jobs to work without cron 2023-11-28 13:50:10 +04:00
d6218eaa82 Removed cron libs 2023-11-28 13:49:42 +04:00
8886e57d4f Updated typings 2023-11-28 13:49:28 +04:00
6efb58da1a Fixed UI jumping 2023-11-28 13:49:08 +04:00
f187cc2c26 Removed cron functionality completely 2023-11-28 13:28:00 +04:00
d5c5495475 Fix bug with updating overwritten secrets 2023-11-28 12:07:02 +04:00
8e33692cda Update package-lock.json 2023-11-28 11:36:49 +04:00
6c31e70f4f Update code-of-conduct.mdx 2023-11-28 11:17:09 +04:00
d7026cbbfa Super tiny docs update 2023-11-28 11:13:54 +04:00
df0e0bf988 Fix issue when saving reminders on personal secrets 2023-11-28 11:00:13 +04:00
640366a0ec update path examples 2023-11-28 00:57:12 -05:00
ab5514fcf7 add docs for agent auth and template details 2023-11-28 00:55:06 -05:00
c8951f347b start agent docs overview 2023-11-27 20:08:04 -05:00
d76f556464 remove frontend sv3 changes 2023-11-27 18:32:05 -05:00
6ccaa24e59 add agent refresh and template render 2023-11-27 18:15:08 -05:00
4ec0c9cdbf Merge branch 'daniel/assignment' of https://github.com/DanielHougaard/infisical into daniel/assignment 2023-11-28 01:25:42 +04:00
75cd3bfa35 Cleanup 2023-11-28 01:22:57 +04:00
1db7d50a09 Cleanup 2023-11-28 01:22:48 +04:00
b4980b4a53 Clear when re-opening form 2023-11-28 01:22:35 +04:00
1351ba936f Some linting 2023-11-28 00:55:52 +04:00
57bccaefba UI improvements 2023-11-28 00:54:55 +04:00
86af452888 Removed log 2023-11-28 00:54:55 +04:00
6cef7532da Update sendSecretReminders.ts 2023-11-28 00:54:55 +04:00
74e33144a7 Update secret.ts 2023-11-28 00:54:55 +04:00
a9776eaeb5 Cleanup 2023-11-28 00:54:55 +04:00
3c2a66f722 Lint 2023-11-28 00:54:55 +04:00
d92b1d3cd8 Create secretReminder.handlebars 2023-11-28 00:54:55 +04:00
f560242f1d Sending out the emails 2023-11-28 00:54:55 +04:00
33fc968055 Reminders 2023-11-28 00:54:55 +04:00
f289b99cf1 Packages 2023-11-28 00:54:55 +04:00
019b477d2d Reminder helpers for creating and deleting crons 2023-11-28 00:54:55 +04:00
9ad2d9d218 Added secret reminder handler on updates 2023-11-28 00:54:55 +04:00
a575530ddf Package 2023-11-28 00:51:28 +04:00
33ea019d70 Clear when re-opening form 2023-11-28 00:48:08 +04:00
5ae3b66e2e Some linting 2023-11-28 00:09:31 +04:00
20210d7471 UI improvements 2023-11-27 23:57:35 +04:00
d7262d4291 Added rbac to docs navigation and fixed typos 2023-11-25 18:32:37 -08:00
7a9221769d Added docs for rbac 2023-11-25 18:03:54 -08:00
5e5761424a fix typos in docs 2023-11-25 09:12:17 -08:00
46c76e3984 chore: refactor 2023-11-25 10:11:53 +00:00
b212681d09 feat: cli-folders-cmd 2023-11-25 09:44:51 +00:00
be67b9b341 Added docs for PR Workflows 2023-11-24 22:08:09 -08:00
84e32faac9 Import express-async-errors 2023-11-23 17:59:05 +07:00
b5d5eb87a7 fix typo in image path 2023-11-22 22:54:44 -08:00
bcfb14ca86 added docs for cloudflare workers to the navigation bar 2023-11-22 22:44:25 -08:00
87e2844499 added docs for cloudflare workers 2023-11-22 22:40:14 -08:00
ae4b8ca9b2 Merge pull request #1172 from Shraeyas/cloudflare-workers
Add Integration for Cloudflare Workers
2023-11-22 22:05:35 -08:00
0cff39f918 Merge pull request #1186 from akhilmhdh/feat/audit-log-export
feat(audit-log): improvement in loading time
2023-11-23 12:21:56 +07:00
53ac05694c Update get/export audit logs spec 2023-11-23 12:18:01 +07:00
097a8cae89 Resolve merge conflict 2023-11-23 12:02:41 +07:00
4757ceb938 Merge branch 'Infisical:main' into cloudflare-workers 2023-11-22 10:41:01 -08:00
e3d536ef58 Merge pull request #1187 from akhilmhdh/feat/swc-dev
feat: added swc compiler for ts-node for speed boost
2023-11-22 13:03:45 -05:00
89ae3070ce feat(audit-log): added doc for export endpoint 2023-11-22 17:04:09 +05:30
f3895b70ee feat: added swc compiler for ts-node for speed boost 2023-11-22 13:01:27 +05:30
3478d71e99 unblock gamma pipeline 2023-11-21 17:45:01 -05:00
1f5e458b64 add log details under msg property 2023-11-21 16:25:48 -05:00
6f8373c977 add severity level as string for pino 2023-11-21 16:02:43 -05:00
c55a36b291 add logs for v3 secrets 2023-11-21 16:02:03 -05:00
e4a04bdf0a set log level to info and output to file 2023-11-21 15:11:19 -05:00
4db9a5279f Comment out ST V3 section 2023-11-21 17:32:22 +07:00
c37ff79927 Merge pull request #1184 from Infisical/stv3-roles
Add role-based project access controls to ST V3
2023-11-21 12:26:40 +02:00
98e299c2ac Update rendering custo role slugs in ST V3 table / modal 2023-11-21 17:11:53 +07:00
9d9e830d73 feat(audit-log): added index over workspace key and changed pagination to load more type 2023-11-21 14:36:06 +05:30
d909ff6a97 Merge pull request #1185 from akhilmhdh/feat/pino-cloudwatch
feat(aws-cloudwatch): added support for aws cloudwatch transport in pino
2023-11-20 21:00:55 -05:00
5301bcc91f feat(aws-cloudwatch): added support for aws cloudwatch transport in pino 2023-11-20 16:17:31 +05:30
77438f9282 Replace getUserProjectPermissions with more generic getAuthDataProjectPermissions 2023-11-19 18:10:02 +02:00
122a1e32e1 Replace scopes with role for ST V3 2023-11-19 15:45:49 +02:00
9d2a08dbec Merge pull request #1183 from Infisical/stv3-permissioning
Move ST sections to members page (now access control)
2023-11-18 19:15:51 +02:00
87c2e417d2 Fix lint type issues 2023-11-18 19:11:35 +02:00
341a745843 Move ST sections to members page (now access control) 2023-11-18 18:29:13 +02:00
085ddb2c48 Merge pull request #1178 from akhilmhdh/patch/sign-up
fix: resolved backup key generation in signup, removed owner check and logic race condition error
2023-11-17 10:52:24 -05:00
6734ce50a5 fix: resolved backup key generation in signup, removed owner check in custom role and login race condition 2023-11-16 13:28:30 +05:30
94f893017b add env_file: .env to docker compose 2023-11-15 13:21:18 -05:00
a0dfa5eedf put svt v3 as feature flag 2023-11-14 15:25:17 -05:00
1fea2f1121 remove invite only logic from controller 2023-11-14 14:01:33 -05:00
7fe8999432 Merge pull request #1171 from akhilmhdh/feat/onboarding-exp
New onboarding experience
2023-11-14 12:00:22 -05:00
fca5ae9172 Merge branch 'Infisical:main' into cloudflare-workers 2023-11-14 08:02:47 -08:00
4aacbed28b feat(onboarding): added signup disable for sso and post hog event on admin initalization 2023-11-14 13:01:07 +05:30
9fbf01c19e update migrationAssignSuperadmin 2023-11-13 16:54:20 -05:00
954bc0c5f1 Merge pull request #1174 from RezaRahemtola/main
Docs: Fixing some typos
2023-11-13 12:27:26 -06:00
4ac3669756 feat(onboarding): added migration script for super admin 2023-11-13 23:53:55 +05:30
6b334b3103 docs: Fixing some typos 2023-11-13 15:42:08 +01:00
3aae1b8432 added support note to ansible docs 2023-11-12 15:30:41 -06:00
e462722ec3 updated nsible docs title 2023-11-12 15:29:51 -06:00
f58c560fc0 Update docs navigation 2023-11-12 14:53:30 -06:00
d035fe1008 Update docs navigation 2023-11-12 13:00:35 -06:00
8be0071413 Added docs for ansible and Jenkins 2023-11-12 12:57:38 -06:00
c3ca992777 feat(onboarding): added backup key generation for admin account 2023-11-12 23:30:41 +05:30
829f65cdb7 Update frequent constants for cloudflare workers integration 2023-11-11 01:02:34 +05:30
7785fbafbd Remove target environment input box from cloudflare workers integration 2023-11-11 01:01:11 +05:30
35cff782e1 Add frontend files for cloudflare workers integration 2023-11-11 00:45:17 +05:30
a35643bf6e Add function to sync secrets to cloudflare workers integration 2023-11-11 00:43:54 +05:30
85de985321 Add apps list for cloudflare workers integration 2023-11-11 00:43:13 +05:30
40f5bbbc07 Add window redirects for cloudflare workers integration 2023-11-11 00:41:20 +05:30
85254ba984 Add integration names to DB model schema 2023-11-11 00:36:47 +05:30
4cb586996c Add integration variables 2023-11-11 00:35:00 +05:30
29fa85e499 feat(onboarding): frontend for onboarding users 2023-11-10 13:12:58 +05:30
df7d8e7be9 feat(onboarding): backend api for onboarding users 2023-11-10 13:12:58 +05:30
1de5fd28a9 Merge pull request #1169 from Infisical/api-ref
Update API Reference params, responses, etc. for secrets, folders, environments, and secret imports endpoints
2023-11-08 19:41:39 +02:00
b3cdc4fdd2 Update API reference responses for secrets, folders, environments, and secret imports 2023-11-08 19:31:59 +02:00
5ee79be873 Correct API reference endpoint details/params for secrets, folders, environments, secret imports 2023-11-08 15:58:18 +02:00
cae7e1808d Merge pull request #1118 from techemmy/fix/resolve-api-reference-error-on-request
fix: resolve API reference failing requests
2023-11-07 17:14:02 +02:00
131d5d7207 Merge pull request #1164 from Infisical/depr-middleware
Remove unused authorization middleware
2023-11-05 18:56:31 +02:00
393cfe8953 Remove unused middlewares 2023-11-05 18:51:25 +02:00
5098c0731b Merge pull request #1157 from Infisical/depr-service-account
Remove/deprecate service account and old logging resources, endpoints, logic, etc.
2023-11-05 16:06:07 +02:00
c9ed5f793a Remove action variables 2023-11-05 15:57:04 +02:00
50ce977c55 Remove/deprecate service accounts + old logs/actions 2023-11-05 15:49:24 +02:00
c29a11866e Remove IP allowlisting from project sidebar 2023-11-04 21:16:59 +02:00
b3a468408e Merge pull request #1144 from Infisical/dependabot/npm_and_yarn/frontend/babel/traverse-and-babel/traverse-and-storybook/addon-essentials-and-storybook/csf-tools-and-storybook-7.23.2
chore(deps): bump @babel/traverse, @storybook/addon-essentials, @storybook/csf-tools and storybook in /frontend
2023-11-04 20:35:32 +02:00
d1a26766ca Merge remote-tracking branch 'origin' into dependabot/npm_and_yarn/frontend/babel/traverse-and-babel/traverse-and-storybook/addon-essentials-and-storybook/csf-tools-and-storybook-7.23.2 2023-11-04 20:26:14 +02:00
73c8e8dc0f Merge pull request #1152 from akhilmhdh/fix/key-rogue
fix: changed 2 fold operation of member workspace to one api call
2023-11-04 12:33:08 -04:00
32882848ba Merge pull request #1126 from Infisical/stv3-update
Multipart Update to Authentication (ST V3, Modularization of Auth Validation Methods, SSO logic)
2023-11-03 22:41:30 +02:00
5fb406884d Merge remote-tracking branch 'origin' into stv3-update 2023-11-03 22:38:29 +02:00
176d92546c Split ST V3 modal into option tabs, re-modularized authn methods 2023-11-03 22:37:50 +02:00
1063c12d25 Merge pull request #1153 from akhilmhdh/feat/secret-blind-index-overview
feat: changed enable blindIndex from settings to overview page for attention
2023-11-03 16:02:28 -04:00
3402acb05c update blind indexing message 2023-11-03 16:00:00 -04:00
db7a064961 feat: changed enable blindIndex from settings to overview page for attention 2023-11-03 20:01:51 +05:30
b521d9fa3a fix: changed 2 fold operation of member workspace to one time operation and as batch 2023-11-03 19:11:01 +05:30
73c7b917ab update secret rotation intro 2023-11-02 17:17:20 -04:00
a8470d2133 Merge pull request #1150 from akhilmhdh/fix/mrg-bug-fixes
fix: resolved error in org settings and integrations page alert hidde…
2023-11-02 12:44:50 -04:00
ca8fff320d Merge branch 'main' into fix/mrg-bug-fixes 2023-11-02 12:44:26 -04:00
f9c28ab045 Add ST V3 copy to clipboard, default to is active 2023-11-02 12:13:23 +02:00
d4a5eb12e8 Patch checkly integration 2023-11-02 11:54:01 +02:00
86d82737f4 Merge pull request #1145 from atimapreandrew/checkly-sync-on-group-level
Checkly sync on group level
2023-11-02 11:10:40 +02:00
abbeb67b95 fix: resolved error in org settings and integrations page alert hidden on no integrations 2023-11-02 14:39:13 +05:30
c0c96d6407 Update checkly integration docs 2023-11-02 10:00:43 +02:00
58ff6a43bc Update Checkly groups integration 2023-11-02 09:37:36 +02:00
079a09a3d1 Remove create new org 2023-11-01 14:27:46 -07:00
a07bd5ad40 add log to secretRotationQueue process 2023-11-01 16:49:31 -04:00
9cc99e41b8 Merge pull request #1117 from akhilmhdh/feat/secret-rotation
Secret rotation
2023-11-01 16:39:24 -04:00
f256493cb3 Merge remote-tracking branch 'origin' into checkly-sync-on-group-level 2023-11-01 20:37:15 +02:00
7bf2e96ad3 Merge remote-tracking branch 'origin' into stv3-update 2023-11-01 18:57:31 +02:00
40238788e5 feat(secret-rotation): changed queue logging to pino 2023-11-01 22:24:58 +05:30
75eeda4278 feat(secret-rotation): changed to mysql2 client and refactored queue util functions 2023-11-01 22:22:46 +05:30
c1ea441e3a AJV set strict to false 2023-11-01 22:21:31 +05:30
8b522a3fb5 feat(secret-rotation): updated docs for secret rotation 2023-11-01 22:21:31 +05:30
c36352f05f feat(secret-rotation): updated helper text for options 2023-11-01 22:21:31 +05:30
2de898fdbd feat: backward compatiable enc key 2023-11-01 22:21:31 +05:30
bc68a00265 feat(secret-rotation): updated lottie and added side effect on successfully 2023-11-01 22:21:31 +05:30
1382688e58 add secretRotation to feature set 2023-11-01 22:21:31 +05:30
9248f36edb feat(secret-rotation): added db ssl option in test function 2023-11-01 22:21:31 +05:30
c9c40521b2 feat(secret-rotation): added db ssl support 2023-11-01 22:21:31 +05:30
97e4338335 feat(secret-rotation): implemented frontend ui for secret rotation 2023-11-01 22:21:31 +05:30
82e924baff feat(secret-rotation): implemented api and queue for secret rotation 2023-11-01 22:21:31 +05:30
2350219cc9 Merge pull request #1148 from Infisical/revert-transactions
Remove transactions from delete organization, workspace, user
2023-11-01 17:51:50 +02:00
28d7c72390 Merge remote-tracking branch 'origin' into revert-transactions 2023-11-01 16:41:32 +02:00
e7321e8060 Merge pull request #1146 from Infisical/pino
Replace winston with pino logging
2023-11-01 20:07:33 +05:30
28a2aebe67 chore: removed npx from pino-pretty 2023-11-01 20:06:02 +05:30
20d4f16d33 Move pino-pretty to dev-dep, dev script 2023-11-01 13:20:10 +02:00
7d802b41a8 Fix lint issue 2023-11-01 11:20:09 +02:00
75992e5566 Merge remote-tracking branch 'origin' into pino 2023-11-01 11:16:34 +02:00
911aa3fd8a Merge remote-tracking branch 'origin' into stv3-update 2023-11-01 11:10:30 +02:00
7622a3f518 Remove transactions from delete organization, workspace, user 2023-11-01 11:06:36 +02:00
3b0bd362c9 Refactor requestErrorHandler, adjust request errors to appropriate pino log level 2023-11-01 10:10:39 +02:00
ad4513f926 Replace winston with pino 2023-10-31 15:03:36 +02:00
a2c1a17222 chore(deps): bump @babel/traverse, @storybook/addon-essentials, @storybook/csf-tools and storybook
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) to 7.23.2 and updates ancestor dependencies [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse), [@storybook/addon-essentials](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/essentials), [@storybook/csf-tools](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/csf-tools) and [storybook](https://github.com/storybookjs/storybook/tree/HEAD/code/lib/cli). These dependencies need to be updated together.


Updates `@babel/traverse` from 7.21.5 to 7.23.2
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

Updates `@babel/traverse` from 7.22.5 to 7.23.2
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

Updates `@storybook/addon-essentials` from 7.0.23 to 7.5.2
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v7.5.2/code/addons/essentials)

Updates `@storybook/csf-tools` from 7.0.23 to 7.5.2
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v7.5.2/code/lib/csf-tools)

Updates `storybook` from 7.0.23 to 7.5.2
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v7.5.2/code/lib/cli)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
- dependency-name: "@babel/traverse"
  dependency-type: indirect
- dependency-name: "@storybook/addon-essentials"
  dependency-type: direct:development
- dependency-name: "@storybook/csf-tools"
  dependency-type: indirect
- dependency-name: storybook
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 09:59:40 +00:00
279958d54c Checkly group level sync support 2023-10-31 07:18:59 +01:00
1be46b5e57 Merge pull request #1085 from rtpa25/feat/create-multiple-orgs-under-same-account
feat: adds ability to create multiple orgs under the same account
2023-10-31 11:21:56 +05:30
98d9dd256b remove image name from k8 docs 2023-10-30 16:59:25 -04:00
e5eee14409 Merge pull request #1121 from Infisical/snyk-fix-9c5c22e2d4bdb58631063170328a0670
[Snyk] Security upgrade crypto-js from 4.1.1 to 4.2.0
2023-10-30 14:18:10 -04:00
f3c76c79ee Checkly group level sync support 2023-10-30 18:23:23 +01:00
5bdb6ad6a1 Merge pull request #1141 from akhilmhdh/fix/backward-enc-key
feat: backward compatiable enc key in webhook
2023-10-30 13:19:18 -04:00
c6846f8bf1 feat: updated backward compatiable enc key 2023-10-30 22:41:15 +05:30
46f03f33b0 fix approval plan typo 2023-10-30 12:48:06 -04:00
6280d7eb34 feat: backward compatiable enc key in webhook 2023-10-30 21:30:37 +05:30
29286d2125 Merge pull request #1138 from Shraeyas:develop
Fix bug with copying secret to clipboard with an override
2023-10-29 22:56:14 -04:00
c9f01ce086 Fix bug with copying secret to clipboard with an override 2023-10-29 21:25:36 +05:30
bc43e109eb update zod version for frontend 2023-10-27 18:06:59 -04:00
238c43a360 Merge pull request #1131 from akhilmhdh/fix/build-failing-ts
fix: standalone build failure due to ts error
2023-10-27 17:44:10 -04:00
040a50d599 Merge pull request #1132 from Tchoupinax/main
feat(helm-chart): repair usage of resources key
2023-10-27 17:42:48 -04:00
8a1a3e9ab9 chore(helm-chart): increase the version to 0.4.2 2023-10-27 20:45:35 +02:00
2585d50b29 feat(helm-chart): repair usage of resources key 2023-10-27 20:42:35 +02:00
4792e752c2 update dependency of rate limiter 2023-10-27 10:48:25 -04:00
1d161f6c97 fix: standalone build failure due to ts error 2023-10-27 20:05:51 +05:30
0d94b6deed Merge pull request #1130 from Infisical/revert-1129-revert-1128-mongodb-dep
Revert "Revert "Remove mongodb direct dependency from backend""
2023-10-27 10:26:03 -04:00
75428bb750 Revert "Revert "Remove mongodb direct dependency from backend"" 2023-10-27 10:24:49 -04:00
d90680cc91 Merge pull request #1129 from Infisical/revert-1128-mongodb-dep
Revert "Remove mongodb direct dependency from backend"
2023-10-27 10:21:04 -04:00
031c05b82d Revert "Remove mongodb direct dependency from backend" 2023-10-27 10:20:51 -04:00
d414353258 Merge remote-tracking branch 'origin' into stv3-update 2023-10-27 15:19:39 +01:00
ffc6dcdeb4 Merge pull request #1128 from Infisical/mongodb-dep
Remove mongodb direct dependency from backend
2023-10-27 15:19:01 +01:00
dfc74262ee Remove mongodb direct dependency from backend 2023-10-27 15:16:22 +01:00
59e46ef1d0 Merge pull request #1125 from akhilmhdh/fix/deep-main-page
fix: resolved nav header secret path issues
2023-10-27 10:01:57 -04:00
36e4cd71d3 Merge pull request #1127 from Infisical/update-node-saml
Update subdependencies, node-saml
2023-10-27 14:54:49 +01:00
d60b3d1598 Update subdependencies, node-saml 2023-10-27 14:52:22 +01:00
e555a8d313 Merge remote-tracking branch 'origin' into stv3-update 2023-10-27 12:20:03 +01:00
44ec88acd6 Finish preliminary refactor/modularization of requireAuth logic 2023-10-27 12:03:25 +01:00
15504346cd fix: resolved nav header secret path issues 2023-10-27 12:16:37 +05:30
508ed7f7d6 Merge pull request #1124 from akhilmhdh/fix/folder-create-overview
fix:resolved overview page add secret not working when folder not exist in one level deep
2023-10-26 13:11:23 -04:00
52bcee2785 Modularize SSO provider logic 2023-10-26 13:45:40 +01:00
c097e43a4e fix:resolved overview page add secret not working when folder not existing 2023-10-26 15:14:05 +05:30
65afaa8177 Update ST V3 impl to (rotating) refresh token impl 2023-10-26 09:57:59 +01:00
01cbd4236d fix: update the api reference from the OpenAPI spec in the /backend folder instead 2023-10-26 04:35:14 +01:00
40a9a15709 fix: backend/package.json & backend/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-CRYPTOJS-6028119
2023-10-25 16:41:52 +00:00
abfc69fc75 fix: resolve API reference failing requests 2023-10-25 15:27:07 +01:00
3ea20328dc Removed log 2023-10-25 03:52:53 +04:00
2aa46f5a65 Update sendSecretReminders.ts 2023-10-25 03:47:07 +04:00
f0ca059b17 Update secret.ts 2023-10-25 03:46:00 +04:00
b1369d66c2 Cleanup 2023-10-25 03:43:52 +04:00
151ba2ffc9 Lint 2023-10-25 02:25:58 +04:00
152feba1fa Create secretReminder.handlebars 2023-10-25 02:25:49 +04:00
16125157f3 Sending out the emails 2023-10-25 02:25:39 +04:00
c0592ad904 remove cypress folder from root 2023-10-24 10:41:16 -04:00
32970e4990 Merge pull request #1107 from Infisical/cypress
adding cypress tests
2023-10-24 10:38:47 -04:00
7487b373fe adding cypress test 2023-10-24 10:38:08 -04:00
6df678067c Reminders 2023-10-24 05:16:39 +04:00
b97dc7f599 Packages 2023-10-24 05:16:19 +04:00
96db649cbc Reminder helpers for creating and deleting crons 2023-10-24 05:16:04 +04:00
80ce695355 Added secret reminder handler on updates 2023-10-24 05:15:40 +04:00
93a1725da6 Package 2023-10-24 05:15:01 +04:00
619bbf2027 fix: fixes broken nonePage.tsx 2023-10-24 06:09:12 +05:30
1476d06b7e feat: adds cancel button and uses zod over yup 2023-10-24 06:02:20 +05:30
fb59b02ab4 Merge branch 'Infisical:main' into feat/create-multiple-orgs-under-same-account 2023-10-24 05:42:31 +05:30
fc3db93f8b Merge pull request #1102 from G3root/hasura-cloud
feat: add hasura cloud integration
2023-10-23 20:37:52 +01:00
120f1cb5dd Remove print statements, clean hasura cloud integration frontend 2023-10-23 20:06:35 +01:00
bb9b060fc0 Update syncSecretsHasuraCloud 2023-10-23 19:52:55 +01:00
26605638fa Merge pull request #1110 from techemmy/docs/add-REAMDE-for-contributing-to-the-docs
docs: add README file for instructions on how to get the doc started …
2023-10-23 14:52:17 +01:00
76758732af Merge pull request #1112 from Infisical/auth-jwt-standardization
API Key V2
2023-10-23 12:30:00 +01:00
827d5b25c2 Cleanup comments API Key V2 2023-10-23 12:18:44 +01:00
b32b19bcc1 Finish API Key V2 2023-10-23 11:58:16 +01:00
69b9881cbc docs: add README file for instructions on how to get the doc started in local development 2023-10-22 16:40:33 +01:00
1084323d6d Merge pull request #1014 from G3root/e2e-warning
feat: display warning message in integrations page when e2e is enabled
2023-10-22 14:42:15 +01:00
c98c45157a Merge branch 'main' into e2e-warning 2023-10-22 14:39:09 +01:00
9a500504a4 adding cypress tests 2023-10-21 09:49:31 -07:00
6009dda2d2 Merge pull request #1105 from G3root/fix-batch-delete-integration
fix: batch deleting secrets not getting synced for integrations
2023-10-21 14:42:23 +05:30
d4e8162c41 fix: sync deleted secret 2023-10-21 01:39:08 +05:30
f6ad641858 chore: add logs 2023-10-21 01:38:41 +05:30
32acc370a4 feat: add delete method 2023-10-21 01:36:23 +05:30
ba9b1b45ae update docker docs for self host 2023-10-20 13:36:09 +01:00
e05b26c727 Add docs for Hasura Cloud 2023-10-20 11:25:06 +01:00
4d78f4a824 feat: add create page 2023-10-20 13:22:40 +05:30
47bf483c2e feat: add logo 2023-10-20 13:20:43 +05:30
40e5ecfd7d feat: add sync 2023-10-20 13:20:00 +05:30
0fb0744f09 feat: add get apps 2023-10-20 13:18:26 +05:30
e13b3f72b1 feat: add authorize page 2023-10-18 23:08:13 +05:30
a6e02238ad feat: add hasura cloud 2023-10-18 22:40:34 +05:30
ebe4f70b51 docs: add hasura cloud integration 2023-10-18 22:37:31 +05:30
c3c7316ec0 feat: add to redirect provider 2023-10-18 22:15:48 +05:30
2cd791a433 feat: add integration page 2023-10-18 21:51:35 +05:30
912818eec8 Merge branch 'main' into feat/create-multiple-orgs-under-same-account 2023-10-16 18:55:48 -07:00
840eef7bce feat: improves the flow of account creation 2023-10-13 11:05:24 +05:30
70b9d435d1 feat: adds ability to create multiple orgs under the same account 2023-10-13 10:42:54 +05:30
9546916aad fix: add props 2023-09-30 17:12:52 +05:30
59c861c695 fix: rename variants 2023-09-30 17:07:52 +05:30
2eff06cf06 fix: alert component styles 2023-09-22 23:20:45 +05:30
a024eecf2c chore: remove utils 2023-09-22 23:10:46 +05:30
a2ad9e10b4 chore: enable prop types 2023-09-22 22:37:36 +05:30
7fa4e09874 feat: use alert component 2023-09-20 23:09:03 +05:30
20c4e956aa feat: add warnings 2023-09-19 17:25:37 +05:30
4a227d05ce feat: add className utility 2023-09-19 17:25:03 +05:30
6f57ef03d1 feat: add alert component 2023-09-19 17:24:33 +05:30
257b4b0490 chore: disable prop-types rule 2023-09-19 17:08:54 +05:30
463 changed files with 39965 additions and 15902 deletions

6235
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,13 @@
"@godaddy/terminus": "^4.12.0", "@godaddy/terminus": "^4.12.0",
"@node-saml/passport-saml": "^4.0.4", "@node-saml/passport-saml": "^4.0.4",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/node": "^7.49.0", "@sentry/node": "^7.77.0",
"@sentry/tracing": "^7.48.0", "@sentry/tracing": "^7.48.0",
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
"@ucast/mongo2js": "^1.3.4", "@ucast/mongo2js": "^1.3.4",
"ajv": "^8.12.0",
"argon2": "^0.30.3", "argon2": "^0.30.3",
"aws-sdk": "^2.1364.0", "aws-sdk": "^2.1364.0",
"axios": "^1.3.5", "axios": "^1.3.5",
@ -19,7 +21,7 @@
"bigint-conversion": "^2.4.0", "bigint-conversion": "^2.4.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "^2.8.5", "cors": "^2.8.5",
"crypto-js": "^4.1.1", "crypto-js": "^4.2.0",
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"express": "^4.18.1", "express": "^4.18.1",
"express-async-errors": "^3.1.1", "express-async-errors": "^3.1.1",
@ -29,20 +31,25 @@
"helmet": "^5.1.1", "helmet": "^5.1.1",
"infisical-node": "^1.2.1", "infisical-node": "^1.2.1",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"jmespath": "^0.16.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10", "libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongodb": "^5.7.0",
"mongoose": "^7.4.1", "mongoose": "^7.4.1",
"mysql2": "^3.6.2",
"nanoid": "^3.3.6", "nanoid": "^3.3.6",
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"ora": "^5.4.1",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-github": "^1.1.0", "passport-github": "^1.1.0",
"passport-gitlab2": "^5.0.0", "passport-gitlab2": "^5.0.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
"pg": "^8.11.3",
"pino": "^8.16.1",
"pino-http": "^8.5.1",
"posthog-node": "^2.6.0", "posthog-node": "^2.6.0",
"probot": "^12.3.1", "probot": "^12.3.1",
"query-string": "^7.1.3", "query-string": "^7.1.3",
@ -53,16 +60,19 @@
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3", "typescript": "^4.9.3",
"utility-types": "^3.10.0", "utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6",
"zod": "^3.22.3" "zod": "^3.22.3"
}, },
"overrides": {
"rate-limit-mongo": {
"mongodb": "5.8.0"
}
},
"name": "infisical-api", "name": "infisical-api",
"version": "1.0.0", "version": "1.0.0",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
"start": "node build/index.js", "start": "node build/index.js",
"dev": "nodemon", "dev": "nodemon index.js",
"swagger-autogen": "node ./swagger/index.ts", "swagger-autogen": "node ./swagger/index.ts",
"build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build",
"lint": "eslint . --ext .ts", "lint": "eslint . --ext .ts",
@ -87,6 +97,8 @@
"devDependencies": { "devDependencies": {
"@jest/globals": "^29.3.1", "@jest/globals": "^29.3.1",
"@posthog/plugin-scaffold": "^1.3.4", "@posthog/plugin-scaffold": "^1.3.4",
"@swc/core": "^1.3.99",
"@swc/helpers": "^0.5.3",
"@types/bcrypt": "^5.0.0", "@types/bcrypt": "^5.0.0",
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/bull": "^4.10.0", "@types/bull": "^4.10.0",
@ -94,12 +106,15 @@
"@types/cors": "^2.8.12", "@types/cors": "^2.8.12",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/jest": "^29.5.0", "@types/jest": "^29.5.0",
"@types/jmespath": "^0.15.1",
"@types/jsonwebtoken": "^8.5.9", "@types/jsonwebtoken": "^8.5.9",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/node": "^18.11.3", "@types/node": "^18.11.3",
"@types/nodemailer": "^6.4.6", "@types/nodemailer": "^6.4.6",
"@types/passport": "^1.0.12", "@types/passport": "^1.0.12",
"@types/pg": "^8.10.7",
"@types/picomatch": "^2.3.0", "@types/picomatch": "^2.3.0",
"@types/pino": "^7.0.5",
"@types/supertest": "^2.0.12", "@types/supertest": "^2.0.12",
"@types/swagger-jsdoc": "^6.0.1", "@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3", "@types/swagger-ui-express": "^4.1.3",
@ -113,6 +128,8 @@
"jest-junit": "^15.0.0", "jest-junit": "^15.0.0",
"nodemon": "^2.0.19", "nodemon": "^2.0.19",
"npm": "^8.19.3", "npm": "^8.19.3",
"pino-pretty": "^10.2.3",
"regenerator-runtime": "^0.14.0",
"smee-client": "^1.2.3", "smee-client": "^1.2.3",
"supertest": "^6.3.3", "supertest": "^6.3.3",
"swagger-autogen": "^2.23.5", "swagger-autogen": "^2.23.5",

File diff suppressed because it is too large Load Diff

43
backend/src/bootstrap.ts Normal file
View File

@ -0,0 +1,43 @@
import ora from "ora";
import nodemailer from "nodemailer";
import { getSmtpHost, getSmtpPort } from "./config";
import { logger } from "./utils/logging";
import mongoose from "mongoose";
import { redisClient } from "./services/RedisService";
type BootstrapOpt = {
transporter: nodemailer.Transporter;
};
export const bootstrap = async ({ transporter }: BootstrapOpt) => {
const spinner = ora().start();
spinner.info("Checking configurations...");
spinner.info("Testing smtp connection");
await transporter
.verify()
.then(async () => {
spinner.succeed("SMTP successfully connected");
})
.catch(async (err) => {
spinner.fail(`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`);
logger.error(err);
});
spinner.info("Testing mongodb connection");
if (mongoose.connection.readyState !== mongoose.ConnectionStates.connected) {
spinner.fail("Mongo DB - Failed to connect");
} else {
spinner.succeed("Mongodb successfully connected");
}
spinner.info("Testing redis connection");
const redisPing = await redisClient?.ping();
if (!redisPing) {
spinner.fail("Redis - Failed to connect");
} else {
spinner.succeed("Redis successfully connected");
}
spinner.stop();
};

View File

@ -3,100 +3,171 @@ import { GITLAB_URL } from "../variables";
import InfisicalClient from "infisical-node"; import InfisicalClient from "infisical-node";
export const client = new InfisicalClient({ export const client = new InfisicalClient({
token: process.env.INFISICAL_TOKEN!, token: process.env.INFISICAL_TOKEN!
}); });
export const getPort = async () => (await client.getSecret("PORT")).secretValue || 4000; export const getPort = async () => (await client.getSecret("PORT")).secretValue || 4000;
export const getEncryptionKey = async () => { export const getEncryptionKey = async () => {
const secretValue = (await client.getSecret("ENCRYPTION_KEY")).secretValue; const secretValue = (await client.getSecret("ENCRYPTION_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} };
export const getRootEncryptionKey = async () => { export const getRootEncryptionKey = async () => {
const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue; const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} };
export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true" export const getInviteOnlySignup = async () =>
export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10; (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true";
export const getAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue ?? (await client.getSecret("AUTH_SECRET")).secretValue; export const getSaltRounds = async () =>
export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d"; parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10;
export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m"; export const getAuthSecret = async () =>
export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d"; (await client.getSecret("JWT_AUTH_SECRET")).secretValue ??
export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1) (await client.getSecret("AUTH_SECRET")).secretValue;
export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m"; export const getJwtAuthLifetime = async () =>
export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d";
export const getJwtServiceTokenSecret = async () => (await client.getSecret("JWT_SERVICE_TOKEN_SECRET")).secretValue; export const getJwtMfaLifetime = async () =>
(await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m";
export const getJwtRefreshLifetime = async () =>
(await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d";
export const getJwtServiceSecret = async () =>
(await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1)
export const getJwtSignupLifetime = async () =>
(await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m";
export const getJwtProviderAuthLifetime = async () =>
(await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m";
export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue; export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue;
export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production"; export const getNodeEnv = async () =>
export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; (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 getLokiHost = async () => (await client.getSecret("LOKI_HOST")).secretValue;
export const getClientIdAzure = async () => (await client.getSecret("CLIENT_ID_AZURE")).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 getClientIdHeroku = async () =>
export const getClientIdVercel = async () => (await client.getSecret("CLIENT_ID_VERCEL")).secretValue; (await client.getSecret("CLIENT_ID_HEROKU")).secretValue;
export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; export const getClientIdVercel = async () =>
export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; (await client.getSecret("CLIENT_ID_VERCEL")).secretValue;
export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; export const getClientIdNetlify = async () =>
export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue; (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue;
export const getClientIdGCPSecretManager = async () => (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue; export const getClientIdGitHub = async () =>
export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue; (await client.getSecret("CLIENT_ID_GITHUB")).secretValue;
export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; export const getClientIdGitLab = async () =>
export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue; (await client.getSecret("CLIENT_ID_GITLAB")).secretValue;
export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; export const getClientIdBitBucket = async () =>
export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue;
export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; export const getClientIdGCPSecretManager = async () =>
export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue; (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue;
export const getClientSecretGCPSecretManager = async () => (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue; export const getClientSecretAzure = async () =>
export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue; (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 getClientSecretBitBucket = async () =>
(await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue;
export const getClientSecretGCPSecretManager = async () =>
(await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue;
export const getClientSlugVercel = async () =>
(await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue;
export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; export const getClientIdGoogleLogin = async () =>
export const getClientSecretGoogleLogin = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue; (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue;
export const getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue; export const getClientSecretGoogleLogin = async () =>
export const getClientSecretGitHubLogin = async () => (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue; (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue;
export const getClientIdGitLabLogin = async () => (await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue; export const getClientIdGitHubLogin = async () =>
export const getClientSecretGitLabLogin = async () => (await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue; (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue;
export const getUrlGitLabLogin = async () => (await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL; export const getClientSecretGitHubLogin = async () =>
(await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue;
export const getClientIdGitLabLogin = async () =>
(await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue;
export const getClientSecretGitLabLogin = async () =>
(await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue;
export const getUrlGitLabLogin = async () =>
(await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL;
export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com"; export const getAwsCloudWatchLog = async () => {
export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE"; const logGroupName =
(await client.getSecret("AWS_CLOUDWATCH_LOG_GROUP_NAME")).secretValue || "infisical-log-stream";
const region = (await client.getSecret("AWS_CLOUDWATCH_LOG_REGION")).secretValue;
const accessKeyId = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_ID")).secretValue;
const accessKeySecret = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_SECRET"))
.secretValue;
const interval = parseInt(
(await client.getSecret("AWS_CLOUDWATCH_LOG_INTERVAL")).secretValue || 1000,
10
);
if (!region || !accessKeyId || !accessKeySecret) return;
return { logGroupName, region, accessKeySecret, accessKeyId, interval };
};
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 getSentryDSN = async () => (await client.getSecret("SENTRY_DSN")).secretValue;
export const getSiteURL = async () => (await client.getSecret("SITE_URL")).secretValue; export const getSiteURL = async () => (await client.getSecret("SITE_URL")).secretValue;
export const getSmtpHost = async () => (await client.getSecret("SMTP_HOST")).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 getSmtpSecure = async () =>
export const getSmtpPort = async () => parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587; (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 getSmtpUsername = async () => (await client.getSecret("SMTP_USERNAME")).secretValue;
export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWORD")).secretValue; export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWORD")).secretValue;
export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue; export const getSmtpFromAddress = async () =>
export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical"; (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue;
export const getSmtpFromName = async () =>
(await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical";
export const getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue; export const getSecretScanningWebhookProxy = async () =>
export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue; (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue;
export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue; export const getSecretScanningWebhookSecret = async () =>
export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue; (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue;
export const getSecretScanningGitAppId = async () =>
(await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue;
export const getSecretScanningPrivateKey = async () =>
(await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue;
export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).secretValue; export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).secretValue;
export const getIsInfisicalCloud = async () =>
(await client.getSecret("INFISICAL_CLOUD")).secretValue === "true";
export const getLicenseKey = async () => { export const getLicenseKey = async () => {
const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue; const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} };
export const getLicenseServerKey = async () => { export const getLicenseServerKey = async () => {
const secretValue = (await client.getSecret("LICENSE_SERVER_KEY")).secretValue; const secretValue = (await client.getSecret("LICENSE_SERVER_KEY")).secretValue;
return secretValue === "" ? undefined : secretValue; return secretValue === "" ? undefined : secretValue;
} };
export const getLicenseServerUrl = async () => (await client.getSecret("LICENSE_SERVER_URL")).secretValue || "https://portal.infisical.com"; 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 getTelemetryEnabled = async () =>
(await client.getSecret("TELEMETRY_ENABLED")).secretValue !== "false" && true;
export const getLoopsApiKey = async () => (await client.getSecret("LOOPS_API_KEY")).secretValue; 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 getSmtpConfigured = async () =>
(await client.getSecret("SMTP_HOST")).secretValue == "" ||
(await client.getSecret("SMTP_HOST")).secretValue == undefined
? false
: true;
export const getHttpsEnabled = async () => { export const getHttpsEnabled = async () => {
if ((await getNodeEnv()) != "production") { if ((await getNodeEnv()) != "production") {
// no https for anything other than prod // no https for anything other than prod
return false return false;
} }
if ((await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || (await client.getSecret("HTTPS_ENABLED")).secretValue == "") { if (
(await client.getSecret("HTTPS_ENABLED")).secretValue == undefined ||
(await client.getSecret("HTTPS_ENABLED")).secretValue == ""
) {
// default when no value present // default when no value present
return true return true;
} }
return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true;
} };

View File

@ -0,0 +1,24 @@
import { IServerConfig, ServerConfig } from "../models/serverConfig";
let serverConfig: IServerConfig;
export const serverConfigInit = async () => {
const cfg = await ServerConfig.findOne({});
if (!cfg) {
const cfg = new ServerConfig();
await cfg.save();
serverConfig = cfg;
} else {
serverConfig = cfg;
}
return serverConfig;
};
export const getServerConfig = () => serverConfig;
export const updateServerConfig = async (data: Partial<IServerConfig>) => {
const cfg = await ServerConfig.findByIdAndUpdate(serverConfig._id, data, { new: true });
if (!cfg) throw new Error("Failed to update server config");
serverConfig = cfg;
return serverConfig;
};

View File

@ -0,0 +1,100 @@
import { Request, Response } from "express";
import { getHttpsEnabled } from "../../config";
import { getServerConfig, updateServerConfig as setServerConfig } from "../../config/serverConfig";
import { initializeDefaultOrg, issueAuthTokens } from "../../helpers";
import { validateRequest } from "../../helpers/validation";
import { User } from "../../models";
import { TelemetryService } from "../../services";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/admin";
export const getServerConfigInfo = (_req: Request, res: Response) => {
const config = getServerConfig();
return res.send({ config });
};
export const updateServerConfig = async (req: Request, res: Response) => {
const {
body: { allowSignUp }
} = await validateRequest(reqValidator.UpdateServerConfigV1, req);
const config = await setServerConfig({ allowSignUp });
return res.send({ config });
};
export const adminSignUp = async (req: Request, res: Response) => {
const cfg = getServerConfig();
if (cfg.initialized) throw UnauthorizedRequestError({ message: "Admin has been created" });
const {
body: {
email,
publicKey,
salt,
lastName,
verifier,
firstName,
protectedKey,
protectedKeyIV,
protectedKeyTag,
encryptedPrivateKey,
encryptedPrivateKeyIV,
encryptedPrivateKeyTag
}
} = await validateRequest(reqValidator.SignupV1, req);
let user = await User.findOne({ email });
if (user) throw BadRequestError({ message: "User already exist" });
user = new User({
email,
firstName,
lastName,
encryptionVersion: 2,
protectedKey,
protectedKeyIV,
protectedKeyTag,
publicKey,
encryptedPrivateKey,
iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag,
salt,
verifier,
superAdmin: true
});
await user.save();
await initializeDefaultOrg({ organizationName: "Admin Org", user });
await setServerConfig({ initialized: true });
// issue tokens
const tokens = await issueAuthTokens({
userId: user._id,
ip: req.realIP,
userAgent: req.headers["user-agent"] ?? ""
});
const token = tokens.token;
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "admin initialization",
properties: {
email: user.email,
lastName,
firstName
}
});
}
// 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 admin account",
user,
token
});
};

View File

@ -6,14 +6,8 @@ const jsrp = require("jsrp");
import { LoginSRPDetail, TokenVersion, User } from "../../models"; import { LoginSRPDetail, TokenVersion, User } from "../../models";
import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth"; import { clearTokens, createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user"; import { checkUserDevice } from "../../helpers/user";
import { import { AuthTokenType } from "../../variables";
ACTION_LOGIN,
ACTION_LOGOUT,
AuthTokenType
} from "../../variables";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EELogService } from "../../ee/services";
import { getUserAgentType } from "../../utils/posthog";
import { import {
getAuthSecret, getAuthSecret,
getHttpsEnabled, getHttpsEnabled,
@ -24,10 +18,18 @@ import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth"; import * as reqValidator from "../../validation/auth";
declare module "jsonwebtoken" { declare module "jsonwebtoken" {
export interface AuthnJwtPayload extends jwt.JwtPayload {
authTokenType: AuthTokenType;
}
export interface UserIDJwtPayload extends jwt.JwtPayload { export interface UserIDJwtPayload extends jwt.JwtPayload {
userId: string; userId: string;
refreshVersion?: number; refreshVersion?: number;
} }
export interface ServiceRefreshTokenJwtPayload extends jwt.JwtPayload {
serviceTokenDataId: string;
authTokenType: string;
tokenVersion: number;
}
} }
/** /**
@ -137,19 +139,6 @@ export const login2 = async (req: Request, res: Response) => {
secure: await getHttpsEnabled() secure: await getHttpsEnabled()
}); });
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction &&
(await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
// return (access) token in response // return (access) token in response
return res.status(200).send({ return res.status(200).send({
token: tokens.token, token: tokens.token,
@ -186,19 +175,6 @@ export const logout = async (req: Request, res: Response) => {
secure: (await getHttpsEnabled()) as boolean 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: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send({ return res.status(200).send({
message: "Successfully logged out." message: "Successfully logged out."
}); });

View File

@ -7,7 +7,7 @@ import * as reqValidator from "../../validation/bot";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
@ -28,7 +28,11 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => {
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req); } = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -70,7 +74,11 @@ export const setBotActiveState = async (req: Request, res: Response) => {
} }
const userId = req.user._id; const userId = req.user._id;
const { permission } = await getUserProjectPermissions(userId, bot.workspace.toString()); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: bot.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations

View File

@ -16,6 +16,8 @@ import * as workspaceController from "./workspaceController";
import * as secretScanningController from "./secretScanningController"; import * as secretScanningController from "./secretScanningController";
import * as webhookController from "./webhookController"; import * as webhookController from "./webhookController";
import * as secretImpsController from "./secretImpsController"; import * as secretImpsController from "./secretImpsController";
import * as adminController from "./adminController";
export { export {
authController, authController,
botController, botController,
@ -34,5 +36,6 @@ export {
workspaceController, workspaceController,
secretScanningController, secretScanningController,
webhookController, webhookController,
secretImpsController secretImpsController,
adminController
}; };

View File

@ -10,6 +10,7 @@ import {
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
INTEGRATION_BITBUCKET_API_URL, INTEGRATION_BITBUCKET_API_URL,
INTEGRATION_CHECKLY_API_URL,
INTEGRATION_GCP_SECRET_MANAGER, INTEGRATION_GCP_SECRET_MANAGER,
INTEGRATION_NORTHFLANK_API_URL, INTEGRATION_NORTHFLANK_API_URL,
INTEGRATION_QOVERY_API_URL, INTEGRATION_QOVERY_API_URL,
@ -24,11 +25,10 @@ import * as reqValidator from "../../validation/integrationAuth";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { getIntegrationAuthAccessHelper } from "../../helpers"; import { getIntegrationAuthAccessHelper } from "../../helpers";
import { ObjectId } from "mongodb";
/*** /***
* Return integration authorization with id [integrationAuthId] * Return integration authorization with id [integrationAuthId]
@ -40,15 +40,15 @@ export const getIntegrationAuth = async (req: Request, res: Response) => {
const integrationAuth = await IntegrationAuth.findById(integrationAuthId); const integrationAuth = await IntegrationAuth.findById(integrationAuthId);
if (!integrationAuth) if (!integrationAuth) return res.status(400).send({
return res.status(400).send({ message: "Failed to find integration authorization"
message: "Failed to find integration authorization" });
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -79,7 +79,11 @@ export const oAuthExchange = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.OauthExchangeV1, req); } = await validateRequest(reqValidator.OauthExchangeV1, req);
if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration"); if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration");
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -131,7 +135,11 @@ export const saveIntegrationToken = async (req: Request, res: Response) => {
body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken } body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken }
} = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req); } = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -222,13 +230,14 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => {
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -260,13 +269,14 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => {
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -296,13 +306,14 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -345,6 +356,60 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
}); });
}; };
/**
* Return list of Checkly groups for a specific user
* @param req
* @param res
*/
export const getIntegrationAuthChecklyGroups = async (req: Request, res: Response) => {
const {
params: { integrationAuthId },
query: { accountId }
} = await validateRequest(reqValidator.GetIntegrationAuthChecklyGroupsV1, req);
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations
);
interface ChecklyGroup {
id: number;
name: string;
}
if (accountId && accountId !== "") {
const { data }: { data: ChecklyGroup[] } = (
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"X-Checkly-Account": accountId
}
})
);
return res.status(200).send({
groups: data.map((g: ChecklyGroup) => ({
name: g.name,
groupId: g.id,
}))
});
}
return res.status(200).send({
groups: []
});
}
/** /**
* Return list of Qovery Orgs for a specific user * Return list of Qovery Orgs for a specific user
* @param req * @param req
@ -357,13 +422,14 @@ export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response)
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -409,13 +475,14 @@ export const getIntegrationAuthQoveryProjects = async (req: Request, res: Respon
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -470,13 +537,14 @@ export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Re
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -531,13 +599,14 @@ export const getIntegrationAuthQoveryApps = async (req: Request, res: Response)
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -592,13 +661,14 @@ export const getIntegrationAuthQoveryContainers = async (req: Request, res: Resp
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -653,13 +723,14 @@ export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response)
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -715,13 +786,14 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -808,13 +880,14 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -932,13 +1005,14 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -988,13 +1062,14 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -1076,13 +1151,14 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res:
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
});
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: integrationAuth.workspace
}); });
const { permission } = await getUserProjectPermissions(
req.user._id,
integrationAuth.workspace.toString()
);
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -1145,13 +1221,14 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => {
// TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions // TODO(akhilmhdh): remove class -> static function path and makes these into reusable independent functions
const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({
integrationAuthId: new ObjectId(integrationAuthId) integrationAuthId: new Types.ObjectId(integrationAuthId)
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace.toString() workspaceId: integrationAuth.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations

View File

@ -13,7 +13,7 @@ import * as reqValidator from "../../validation/integration";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@ -51,11 +51,12 @@ export const createIntegration = async (req: Request, res: Response) => {
); );
if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" }); if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integrationAuth.workspace._id.toString() workspaceId: integrationAuth.workspace._id
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -164,10 +165,11 @@ export const updateIntegration = async (req: Request, res: Response) => {
const integration = await Integration.findById(integrationId); const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" }); if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integration.workspace.toString() workspaceId: integration.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -234,10 +236,11 @@ export const deleteIntegration = async (req: Request, res: Response) => {
const integration = await Integration.findById(integrationId); const integration = await Integration.findById(integrationId);
if (!integration) throw BadRequestError({ message: "Integration not found" }); if (!integration) throw BadRequestError({ message: "Integration not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
integration.workspace.toString() workspaceId: integration.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -285,7 +288,11 @@ export const manualSync = async (req: Request, res: Response) => {
body: { workspaceId, environment } body: { workspaceId, environment }
} = await validateRequest(reqValidator.ManualSyncV1, req); } = await validateRequest(reqValidator.ManualSyncV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations

View File

@ -9,7 +9,7 @@ import * as reqValidator from "../../validation/key";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@ -26,7 +26,11 @@ export const uploadKey = async (req: Request, res: Response) => {
body: { key } body: { key }
} = await validateRequest(reqValidator.UploadKeyV1, req); } = await validateRequest(reqValidator.UploadKeyV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Member ProjectPermissionSub.Member

View File

@ -12,7 +12,7 @@ import * as reqValidator from "../../validation/membership";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../utils/errors"; import { BadRequestError } from "../../utils/errors";
@ -63,11 +63,12 @@ export const deleteMembership = async (req: Request, res: Response) => {
if (!membershipToDelete) { if (!membershipToDelete) {
throw new Error("Failed to delete workspace membership that doesn't exist"); throw new Error("Failed to delete workspace membership that doesn't exist");
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
membershipToDelete.workspace.toString() workspaceId: membershipToDelete.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@ -118,10 +119,11 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
throw new Error("Failed to find membership to change role"); throw new Error("Failed to find membership to change role");
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
membershipToChangeRole.workspace.toString() workspaceId: membershipToChangeRole.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@ -191,7 +193,12 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => {
params: { workspaceId }, params: { workspaceId },
body: { email } body: { email }
} = await validateRequest(InviteUserToWorkspaceV1, req); } = await validateRequest(InviteUserToWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Member ProjectPermissionSub.Member

View File

@ -1,4 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose";
import { isValidScope } from "../../helpers"; import { isValidScope } from "../../helpers";
import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models"; import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models";
import { getAllImportedSecrets } from "../../services/SecretImportService"; import { getAllImportedSecrets } from "../../services/SecretImportService";
@ -15,14 +17,14 @@ import * as reqValidator from "../../validation/secretImports";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
export const createSecretImp = async (req: Request, res: Response) => { export const createSecretImp = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Create secret import' #swagger.summary = 'Create secret import'
#swagger.description = 'Create a new secret import for a specified workspace and environment' #swagger.description = 'Create secret import'
#swagger.requestBody = { #swagger.requestBody = {
content: { content: {
@ -32,36 +34,36 @@ export const createSecretImp = async (req: Request, res: Response) => {
"properties": { "properties": {
"workspaceId": { "workspaceId": {
"type": "string", "type": "string",
"description": "ID of the workspace where the secret import will be created", "description": "ID of workspace where to create secret import",
"example": "someWorkspaceId" "example": "someWorkspaceId"
}, },
"environment": { "environment": {
"type": "string", "type": "string",
"description": "Environment to import to", "description": "Slug of environment where to create secret import",
"example": "production" "example": "dev"
}, },
"folderId": { "directory": {
"type": "string", "type": "string",
"description": "Folder ID. Use root for the root folder.", "description": "Path where to create secret import like / or /foo/bar. Default is /",
"example": "my_folder" "example": "/foo/bar"
}, },
"secretImport": { "secretImport": {
"type": "object", "type": "object",
"properties": { "properties": {
"environment": { "environment": {
"type": "string", "type": "string",
"description": "Import from environment", "description": "Slug of environment to import from",
"example": "development" "example": "development"
}, },
"secretPath": { "secretPath": {
"type": "string", "type": "string",
"description": "Import from secret path", "description": "Path where to import from like / or /foo/bar.",
"example": "/user/oauth" "example": "/user/oauth"
} }
} }
} }
}, },
"required": ["workspaceId", "environment", "folderName"] "required": ["workspaceId", "environment", "directory", "secretImport"]
} }
} }
} }
@ -105,7 +107,11 @@ export const createSecretImp = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@ -206,12 +212,12 @@ export const createSecretImp = async (req: Request, res: Response) => {
*/ */
export const updateSecretImport = async (req: Request, res: Response) => { export const updateSecretImport = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Update a secret import' #swagger.summary = 'Update secret import'
#swagger.description = 'Updates an existing secret import based on the provided ID and new import details' #swagger.description = 'Update secret import'
#swagger.parameters['id'] = { #swagger.parameters['id'] = {
in: 'path', in: 'path',
description: 'ID of the secret import to be updated', description: 'ID of secret import to update',
required: true, required: true,
type: 'string', type: 'string',
example: 'import12345' example: 'import12345'
@ -225,19 +231,19 @@ export const updateSecretImport = async (req: Request, res: Response) => {
"properties": { "properties": {
"secretImports": { "secretImports": {
"type": "array", "type": "array",
"description": "List of new secret imports", "description": "List of secret imports to update to",
"items": { "items": {
"type": "object", "type": "object",
"properties": { "properties": {
"environment": { "environment": {
"type": "string", "type": "string",
"description": "Environment of the secret import", "description": "Slug of environment to import from",
"example": "production" "example": "dev"
}, },
"secretPath": { "secretPath": {
"type": "string", "type": "string",
"description": "Path of the secret import", "description": "Path where to import secrets from like / or /foo/bar",
"example": "/path/to/secret" "example": "/foo/bar"
} }
}, },
"required": ["environment", "secretPath"] "required": ["environment", "secretPath"]
@ -313,10 +319,11 @@ export const updateSecretImport = async (req: Request, res: Response) => {
} }
} else { } else {
// non token entry check // non token entry check
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
importSecDoc.workspace.toString() workspaceId: importSecDoc.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@ -364,7 +371,7 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
#swagger.parameters['id'] = { #swagger.parameters['id'] = {
in: 'path', in: 'path',
description: 'ID of the secret import', description: 'ID of parent secret import document from which to delete secret import',
required: true, required: true,
type: 'string', type: 'string',
example: '12345abcde' example: '12345abcde'
@ -378,12 +385,12 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
"properties": { "properties": {
"secretImportEnv": { "secretImportEnv": {
"type": "string", "type": "string",
"description": "Import from environment", "description": "Slug of environment of import to delete",
"example": "someWorkspaceId" "example": "someWorkspaceId"
}, },
"secretImportPath": { "secretImportPath": {
"type": "string", "type": "string",
"description": "Import from secret path", "description": "Path like / or /foo/bar of import to delete",
"example": "production" "example": "production"
} }
}, },
@ -442,10 +449,11 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
importSecDoc.workspace.toString() workspaceId: importSecDoc.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@ -489,12 +497,12 @@ export const deleteSecretImport = async (req: Request, res: Response) => {
*/ */
export const getSecretImports = async (req: Request, res: Response) => { export const getSecretImports = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Retrieve secret imports' #swagger.summary = 'Get secret imports'
#swagger.description = 'Fetches the secret imports based on the workspaceId, environment, and folderId' #swagger.description = 'Get secret imports'
#swagger.parameters['workspaceId'] = { #swagger.parameters['workspaceId'] = {
in: 'query', in: 'query',
description: 'ID of the workspace of secret imports to get', description: 'ID of workspace where to get secret imports from',
required: true, required: true,
type: 'string', type: 'string',
example: 'workspace12345' example: 'workspace12345'
@ -502,15 +510,15 @@ export const getSecretImports = async (req: Request, res: Response) => {
#swagger.parameters['environment'] = { #swagger.parameters['environment'] = {
in: 'query', in: 'query',
description: 'Environment of secret imports to get', description: 'Slug of environment where to get secret imports from',
required: true, required: true,
type: 'string', type: 'string',
example: 'production' example: 'production'
} }
#swagger.parameters['folderId'] = { #swagger.parameters['directory'] = {
in: 'query', in: 'query',
description: 'ID of the folder containing the secret imports. Default: root', description: 'Path where to get secret imports from like / or /foo/bar. Default is /',
required: false, required: false,
type: 'string', type: 'string',
example: 'folder12345' example: 'folder12345'
@ -524,8 +532,7 @@ export const getSecretImports = async (req: Request, res: Response) => {
"type": "object", "type": "object",
"properties": { "properties": {
"secretImport": { "secretImport": {
"type": "object", $ref: '#/definitions/SecretImport'
"description": "Details of a secret import"
} }
} }
} }
@ -551,7 +558,11 @@ export const getSecretImports = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@ -605,7 +616,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {
@ -658,10 +673,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => {
permissionCheckFn = (env: string, secPath: string) => permissionCheckFn = (env: string, secPath: string) =>
isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath); isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath);
} else { } else {
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
importSecDoc.workspace.toString() workspaceId: importSecDoc.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { subject(ProjectPermissionSub.Secrets, {

View File

@ -17,7 +17,7 @@ import {
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/folders"; import * as reqValidator from "../../validation/folders";
@ -27,11 +27,12 @@ const ERR_FOLDER_NOT_FOUND = BadRequestError({ message: "The folder doesn't exis
// verify workspace id/environment // verify workspace id/environment
export const createFolder = async (req: Request, res: Response) => { export const createFolder = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Create a folder' #swagger.summary = 'Create folder'
#swagger.description = 'Create a new folder in a specified workspace and environment' #swagger.description = 'Create folder'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": [],
"bearerAuth": []
}] }]
#swagger.requestBody = { #swagger.requestBody = {
@ -42,23 +43,23 @@ export const createFolder = async (req: Request, res: Response) => {
"properties": { "properties": {
"workspaceId": { "workspaceId": {
"type": "string", "type": "string",
"description": "ID of the workspace where the folder will be created", "description": "ID of the workspace where to create folder",
"example": "someWorkspaceId" "example": "someWorkspaceId"
}, },
"environment": { "environment": {
"type": "string", "type": "string",
"description": "Environment where the folder will reside", "description": "Slug of environment where to create folder",
"example": "production" "example": "production"
}, },
"folderName": { "folderName": {
"type": "string", "type": "string",
"description": "Name of the folder to be created", "description": "Name of folder to create",
"example": "my_folder" "example": "my_folder"
}, },
"parentFolderId": { "directory": {
"type": "string", "type": "string",
"description": "ID of the parent folder under which this folder will be created. If not specified, it will be created at the root level.", "description": "Path where to create folder like / or /foo/bar. Default is /",
"example": "someParentFolderId" "example": "/foo/bar"
} }
}, },
"required": ["workspaceId", "environment", "folderName"] "required": ["workspaceId", "environment", "folderName"]
@ -78,14 +79,21 @@ export const createFolder = async (req: Request, res: Response) => {
"properties": { "properties": {
"id": { "id": {
"type": "string", "type": "string",
"description": "ID of folder",
"example": "someFolderId" "example": "someFolderId"
}, },
"name": { "name": {
"type": "string", "type": "string",
"description": "Name of folder",
"example": "my_folder" "example": "my_folder"
},
"version": {
"type": "number",
"description": "Version of folder",
"example": 1
} }
}, },
"description": "Details of the created folder" "description": "Details of created folder"
} }
} }
} }
@ -117,7 +125,11 @@ export const createFolder = async (req: Request, res: Response) => {
} }
} else { } else {
// user check // user check
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@ -219,18 +231,18 @@ export const createFolder = async (req: Request, res: Response) => {
*/ */
export const updateFolderById = async (req: Request, res: Response) => { export const updateFolderById = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Update a folder by ID' #swagger.summary = 'Update folder'
#swagger.description = 'Update the name of a folder in a specified workspace and environment by its ID' #swagger.description = 'Update folder'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": [],
"bearerAuth": []
}] }]
#swagger.parameters['folderId'] = { #swagger.parameters['folderName'] = {
"description": "ID of the folder to be updated", "description": "Name of folder to update",
"required": true, "required": true,
"type": "string", "type": "string"
"in": "path"
} }
#swagger.requestBody = { #swagger.requestBody = {
@ -241,18 +253,23 @@ export const updateFolderById = async (req: Request, res: Response) => {
"properties": { "properties": {
"workspaceId": { "workspaceId": {
"type": "string", "type": "string",
"description": "ID of the workspace where the folder is located", "description": "ID of workspace where to update folder",
"example": "someWorkspaceId" "example": "someWorkspaceId"
}, },
"environment": { "environment": {
"type": "string", "type": "string",
"description": "Environment where the folder is located", "description": "Slug of environment where to update folder",
"example": "production" "example": "production"
}, },
"name": { "name": {
"type": "string", "type": "string",
"description": "New name for the folder", "description": "Name of folder to update to",
"example": "updated_folder_name" "example": "updated_folder_name"
},
"directory": {
"type": "string",
"description": "Path where to update folder like / or /foo/bar. Default is /",
"example": "/foo/bar"
} }
}, },
"required": ["workspaceId", "environment", "name"] "required": ["workspaceId", "environment", "name"]
@ -269,6 +286,7 @@ export const updateFolderById = async (req: Request, res: Response) => {
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"description": "Success message",
"example": "Successfully updated folder" "example": "Successfully updated folder"
}, },
"folder": { "folder": {
@ -276,11 +294,13 @@ export const updateFolderById = async (req: Request, res: Response) => {
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "Name of updated folder",
"example": "updated_folder_name" "example": "updated_folder_name"
}, },
"id": { "id": {
"type": "string", "type": "string",
"example": "someFolderId" "description": "ID of created folder",
"example": "abc123"
} }
}, },
"description": "Details of the updated folder" "description": "Details of the updated folder"
@ -316,7 +336,11 @@ export const updateFolderById = async (req: Request, res: Response) => {
throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); throw UnauthorizedRequestError({ message: "Folder Permission Denied" });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@ -387,15 +411,16 @@ export const updateFolderById = async (req: Request, res: Response) => {
*/ */
export const deleteFolder = async (req: Request, res: Response) => { export const deleteFolder = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Delete a folder by ID' #swagger.summary = 'Delete folder'
#swagger.description = 'Delete the specified folder from a specified workspace and environment using its ID' #swagger.description = 'Delete folder'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": [],
"bearerAuth": []
}] }]
#swagger.parameters['folderId'] = { #swagger.parameters['folderName'] = {
"description": "ID of the folder to be deleted", "description": "Name of folder to delete",
"required": true, "required": true,
"type": "string", "type": "string",
"in": "path" "in": "path"
@ -409,13 +434,18 @@ export const deleteFolder = async (req: Request, res: Response) => {
"properties": { "properties": {
"workspaceId": { "workspaceId": {
"type": "string", "type": "string",
"description": "ID of the workspace where the folder is located", "description": "ID of the workspace where to delete folder",
"example": "someWorkspaceId" "example": "someWorkspaceId"
}, },
"environment": { "environment": {
"type": "string", "type": "string",
"description": "Environment where the folder is located", "description": "Slug of environment where to delete folder",
"example": "production" "example": "production"
},
"directory": {
"type": "string",
"description": "Path where to delete folder like / or /foo/bar. Default is /",
"example": "/foo/bar"
} }
}, },
"required": ["workspaceId", "environment"] "required": ["workspaceId", "environment"]
@ -432,6 +462,7 @@ export const deleteFolder = async (req: Request, res: Response) => {
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"description": "Success message",
"example": "successfully deleted folders" "example": "successfully deleted folders"
}, },
"folders": { "folders": {
@ -441,15 +472,17 @@ export const deleteFolder = async (req: Request, res: Response) => {
"properties": { "properties": {
"id": { "id": {
"type": "string", "type": "string",
"example": "someFolderId" "description": "ID of deleted folder",
"example": "abc123"
}, },
"name": { "name": {
"type": "string", "type": "string",
"description": "Name of deleted folder",
"example": "someFolderName" "example": "someFolderName"
} }
} }
}, },
"description": "List of IDs and names of the deleted folders" "description": "List of IDs and names of deleted folders"
} }
} }
} }
@ -477,7 +510,11 @@ export const deleteFolder = async (req: Request, res: Response) => {
} }
} else { } else {
// check that user is a member of the workspace // check that user is a member of the workspace
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory })
@ -540,43 +577,37 @@ export const deleteFolder = async (req: Request, res: Response) => {
/** /**
* Get folders for workspace with id [workspaceId] and environment [environment] * Get folders for workspace with id [workspaceId] and environment [environment]
* considering [parentFolderId] and [parentFolderPath] * considering directory/path [directory]
* @param req * @param req
* @param res * @param res
* @returns * @returns
*/ */
export const getFolders = async (req: Request, res: Response) => { export const getFolders = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Retrieve folders based on specific conditions' #swagger.summary = 'Get folders'
#swagger.description = 'Fetches folders from the specified workspace and environment, optionally providing either a parentFolderId or a parentFolderPath to narrow down results' #swagger.description = 'Get folders'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": [],
"bearerAuth": []
}] }]
#swagger.parameters['workspaceId'] = { #swagger.parameters['workspaceId'] = {
"description": "ID of the workspace from which the folders are to be fetched", "description": "ID of the workspace where to get folders from",
"required": true, "required": true,
"type": "string", "type": "string",
"in": "query" "in": "query"
} }
#swagger.parameters['environment'] = { #swagger.parameters['environment'] = {
"description": "Environment where the folder is located", "description": "Slug of environment where to get folders from",
"required": true, "required": true,
"type": "string", "type": "string",
"in": "query" "in": "query"
} }
#swagger.parameters['parentFolderId'] = { #swagger.parameters['directory'] = {
"description": "ID of the parent folder", "description": "Path where to get fodlers from like / or /foo/bar. Default is /",
"required": false,
"type": "string",
"in": "query"
}
#swagger.parameters['parentFolderPath'] = {
"description": "Path of the parent folder, like /folder1/folder2",
"required": false, "required": false,
"type": "string", "type": "string",
"in": "query" "in": "query"
@ -604,23 +635,6 @@ export const getFolders = async (req: Request, res: Response) => {
} }
}, },
"description": "List of folders" "description": "List of folders"
},
"dir": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "parentFolderName"
},
"id": {
"type": "string",
"example": "parentFolderId"
}
}
},
"description": "List of directories"
} }
} }
} }
@ -647,7 +661,10 @@ export const getFolders = async (req: Request, res: Response) => {
} }
} else { } else {
// check that user is a member of the workspace // check that user is a member of the workspace
await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
} }
const folders = await Folder.findOne({ workspace: workspaceId, environment }); const folders = await Folder.findOne({ workspace: workspaceId, environment });

View File

@ -2,10 +2,8 @@ import { Request, Response } from "express";
import { AuthMethod, User } from "../../models"; import { AuthMethod, User } from "../../models";
import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup"; import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup";
import { createToken } from "../../helpers/auth"; import { createToken } from "../../helpers/auth";
import { BadRequestError } from "../../utils/errors";
import { import {
getAuthSecret, getAuthSecret,
getInviteOnlySignup,
getJwtSignupLifetime, getJwtSignupLifetime,
getSmtpConfigured getSmtpConfigured
} from "../../config"; } from "../../config";
@ -68,16 +66,6 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
}); });
} }
if (await getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
const userCount = await User.countDocuments({});
if (userCount != 0) {
throw BadRequestError({
message: "New user sign ups are not allowed at this time. You must be invited to sign up."
});
}
}
// verify email // verify email
if (await getSmtpConfigured()) { if (await getSmtpConfigured()) {
await checkEmailVerification({ await checkEmailVerification({

View File

@ -1,27 +1,36 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { client, getRootEncryptionKey } from "../../config"; import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
import { Webhook } from "../../models"; import { Webhook } from "../../models";
import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService"; import { getWebhookPayload, triggerWebhookRequest } from "../../services/WebhookService";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { BadRequestError, ResourceNotFoundError } from "../../utils/errors";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService } from "../../ee/services";
import { EventType } from "../../ee/models"; import { EventType } from "../../ee/models";
import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64 } from "../../variables"; import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8
} from "../../variables";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/webhooks"; import * as reqValidator from "../../validation/webhooks";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
export const createWebhook = async (req: Request, res: Response) => { export const createWebhook = async (req: Request, res: Response) => {
const { const {
body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath } body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.CreateWebhookV1, req); } = await validateRequest(reqValidator.CreateWebhookV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@ -31,17 +40,31 @@ export const createWebhook = async (req: Request, res: Response) => {
workspace: workspaceId, workspace: workspaceId,
environment, environment,
secretPath, secretPath,
url: webhookUrl, url: webhookUrl
algorithm: ALGORITHM_AES_256_GCM,
keyEncoding: ENCODING_SCHEME_BASE64
}); });
if (webhookSecretKey) { if (webhookSecretKey) {
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey();
const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
webhook.iv = iv; if (rootEncryptionKey) {
webhook.tag = tag; const { ciphertext, iv, tag } = client.encryptSymmetric(webhookSecretKey, rootEncryptionKey);
webhook.encryptedSecretKey = ciphertext; webhook.iv = iv;
webhook.tag = tag;
webhook.encryptedSecretKey = ciphertext;
webhook.algorithm = ALGORITHM_AES_256_GCM;
webhook.keyEncoding = ENCODING_SCHEME_BASE64;
} else if (encryptionKey) {
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
plaintext: webhookSecretKey,
key: encryptionKey
});
webhook.iv = iv;
webhook.tag = tag;
webhook.encryptedSecretKey = ciphertext;
webhook.algorithm = ALGORITHM_AES_256_GCM;
webhook.keyEncoding = ENCODING_SCHEME_UTF8;
}
} }
await webhook.save(); await webhook.save();
@ -79,11 +102,11 @@ export const updateWebhook = async (req: Request, res: Response) => {
if (!webhook) { if (!webhook) {
throw BadRequestError({ message: "Webhook not found!!" }); throw BadRequestError({ message: "Webhook not found!!" });
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
webhook.workspace.toString() workspaceId: webhook.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@ -127,10 +150,11 @@ export const deleteWebhook = async (req: Request, res: Response) => {
throw ResourceNotFoundError({ message: "Webhook not found!!" }); throw ResourceNotFoundError({ message: "Webhook not found!!" });
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
webhook.workspace.toString() workspaceId: webhook.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@ -174,10 +198,11 @@ export const testWebhook = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Webhook not found!!" }); throw BadRequestError({ message: "Webhook not found!!" });
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
webhook.workspace.toString() workspaceId: webhook.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks
@ -217,8 +242,12 @@ export const listWebhooks = async (req: Request, res: Response) => {
const { const {
query: { environment, workspaceId, secretPath } query: { environment, workspaceId, secretPath }
} = await validateRequest(reqValidator.ListWebhooksV1, req); } = await validateRequest(reqValidator.ListWebhooksV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Webhooks ProjectPermissionSub.Webhooks

View File

@ -25,7 +25,7 @@ import * as reqValidator from "../../validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
/** /**
@ -39,7 +39,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req); } = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@ -72,7 +76,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req); } = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@ -195,7 +203,11 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.DeleteWorkspaceV1, req); } = await validateRequest(reqValidator.DeleteWorkspaceV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Workspace ProjectPermissionSub.Workspace
@ -223,7 +235,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
body: { name } body: { name }
} = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req); } = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Workspace ProjectPermissionSub.Workspace
@ -257,7 +273,12 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req); } = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -283,7 +304,11 @@ export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: R
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req); } = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Integrations ProjectPermissionSub.Integrations
@ -309,7 +334,11 @@ export const getWorkspaceServiceTokens = async (req: Request, res: Response) =>
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req); } = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens

View File

@ -8,10 +8,8 @@ import { createToken, issueAuthTokens } from "../../helpers/auth";
import { checkUserDevice } from "../../helpers/user"; import { checkUserDevice } from "../../helpers/user";
import { sendMail } from "../../helpers/nodemailer"; import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors"; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables"; import { AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config"; import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/auth"; import * as reqValidator from "../../validation/auth";
@ -190,19 +188,6 @@ export const login2 = async (req: Request, res: Response) => {
response.protectedKeyTag = user.protectedKeyTag; 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: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.ip
}));
return res.status(200).send(response); return res.status(200).send(response);
} }
@ -219,20 +204,16 @@ export const login2 = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const sendMfaToken = async (req: Request, res: Response) => { export const sendMfaToken = async (req: Request, res: Response) => {
const {
body: { email }
} = await validateRequest(reqValidator.SendMfaTokenV2, req);
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email: req.user.email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: "emailMfa.handlebars", template: "emailMfa.handlebars",
subjectLine: "Infisical MFA code", subjectLine: "Infisical MFA code",
recipients: [email], recipients: [req.user.email],
substitutions: { substitutions: {
code code
} }
@ -251,17 +232,17 @@ export const sendMfaToken = async (req: Request, res: Response) => {
*/ */
export const verifyMfaToken = async (req: Request, res: Response) => { export const verifyMfaToken = async (req: Request, res: Response) => {
const { const {
body: { email, mfaToken } body: { mfaToken }
} = await validateRequest(reqValidator.VerifyMfaTokenV2, req); } = await validateRequest(reqValidator.VerifyMfaTokenV2, req);
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email: req.user.email,
token: mfaToken token: mfaToken
}); });
const user = await User.findOne({ const user = await User.findOne({
email email: req.user.email
}).select( }).select(
"+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices" "+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag +devices"
); );
@ -330,18 +311,5 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
resObj.protectedKeyTag = user.protectedKeyTag; 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: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send(resObj); return res.status(200).send(resObj);
}; };

View File

@ -12,18 +12,15 @@ import {
import { EventType, SecretVersion } from "../../ee/models"; import { EventType, SecretVersion } from "../../ee/models";
import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { EEAuditLogService, EELicenseService } from "../../ee/services";
import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors"; import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors";
import _ from "lodash";
import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/environments"; import * as reqValidator from "../../validation/environments";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { SecretImport } from "../../models"; import { SecretImport } from "../../models";
import { ServiceAccountWorkspacePermission } from "../../models";
import { Webhook } from "../../models"; import { Webhook } from "../../models";
/** /**
@ -39,29 +36,16 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
#swagger.description = 'Create environment' #swagger.description = 'Create environment'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": [],
}] }]
#swagger.parameters['workspaceId'] = { #swagger.parameters['workspaceId'] = {
"description": "ID of project", "description": "ID of workspace where to create environment",
"required": true, "required": true,
"type": "string" "type": "string",
"in": "path"
} }
/*
#swagger.summary = 'Create environment'
#swagger.description = 'Create environment'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of project",
"required": true,
"type": "string"
}
#swagger.requestBody = { #swagger.requestBody = {
content: { content: {
"application/json": { "application/json": {
@ -70,12 +54,12 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
"properties": { "properties": {
"environmentName": { "environmentName": {
"type": "string", "type": "string",
"description": "Name of the environment", "description": "Name of the environment to create",
"example": "development" "example": "development"
}, },
"environmentSlug": { "environmentSlug": {
"type": "string", "type": "string",
"description": "Slug of the environment", "description": "Slug of environment to create",
"example": "dev-environment" "example": "dev-environment"
} }
}, },
@ -86,45 +70,53 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) =>
} }
#swagger.responses[200] = { #swagger.responses[200] = {
content: { content: {
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"example": "Successfully created new environment" "description": "Sucess message",
}, "example": "Successfully created environment"
"workspace": { },
"type": "string", "workspace": {
"example": "someWorkspaceId" "type": "string",
}, "description": "ID of workspace where environment was created",
"environment": { "example": "abc123"
"type": "object", },
"properties": { "environment": {
"name": { "type": "object",
"type": "string", "properties": {
"example": "someEnvironmentName" "name": {
}, "type": "string",
"slug": { "description": "Name of created environment",
"type": "string", "example": "Staging"
"example": "someEnvironmentSlug" },
} "slug": {
} "type": "string",
} "description": "Slug of created environment",
}, "example": "staging"
"description": "Response after creating a new environment" }
} }
} }
} },
} "description": "Details of the created environment"
}
}
}
}
*/ */
const { const {
params: { workspaceId }, params: { workspaceId },
body: { environmentName, environmentSlug } body: { environmentName, environmentSlug }
} = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req); } = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@ -201,7 +193,11 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response)
body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName } body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName }
} = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req); } = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@ -247,15 +243,19 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response)
*/ */
export const renameWorkspaceEnvironment = async (req: Request, res: Response) => { export const renameWorkspaceEnvironment = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Rename workspace environment' #swagger.summary = 'Update environment'
#swagger.description = 'Rename a specific environment within a workspace' #swagger.description = 'Update environment'
#swagger.security = [{
"apiKeyAuth": [],
}]
#swagger.parameters['workspaceId'] = { #swagger.parameters['workspaceId'] = {
"description": "ID of the workspace", "description": "ID of workspace where to update environment",
"required": true, "required": true,
"type": "string", "type": "string",
"in": "path" "in": "path"
} }
#swagger.requestBody = { #swagger.requestBody = {
content: { content: {
@ -265,17 +265,17 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
"properties": { "properties": {
"environmentName": { "environmentName": {
"type": "string", "type": "string",
"description": "New name for the environment", "description": "Name of environment to update to",
"example": "Staging-Renamed" "example": "Staging-Renamed"
}, },
"environmentSlug": { "environmentSlug": {
"type": "string", "type": "string",
"description": "New slug for the environment", "description": "Slug of environment to update to",
"example": "staging-renamed" "example": "staging-renamed"
}, },
"oldEnvironmentSlug": { "oldEnvironmentSlug": {
"type": "string", "type": "string",
"description": "Current slug of the environment to rename", "description": "Current slug of environment",
"example": "staging-old" "example": "staging-old"
} }
}, },
@ -293,21 +293,25 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"description": "Success message",
"example": "Successfully update environment" "example": "Successfully update environment"
}, },
"workspace": { "workspace": {
"type": "string", "type": "string",
"example": "someWorkspaceId" "description": "ID of workspace where environment was updated",
"example": "abc123"
}, },
"environment": { "environment": {
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "Name of updated environment",
"example": "Staging-Renamed" "example": "Staging-Renamed"
}, },
"slug": { "slug": {
"type": "string", "type": "string",
"description": "Slug of updated environment",
"example": "staging-renamed" "example": "staging-renamed"
} }
} }
@ -324,7 +328,11 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentName, environmentSlug, oldEnvironmentSlug } body: { environmentName, environmentSlug, oldEnvironmentSlug }
} = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req); } = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@ -400,11 +408,6 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
{ arrayFilters: [{ "element.environment": oldEnvironmentSlug }] }, { arrayFilters: [{ "element.environment": oldEnvironmentSlug }] },
); );
await ServiceAccountWorkspacePermission.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug }
);
await Webhook.updateMany( await Webhook.updateMany(
{ workspace: workspaceId, environment: oldEnvironmentSlug }, { workspace: workspaceId, environment: oldEnvironmentSlug },
{ environment: environmentSlug } { environment: environmentSlug }
@ -453,19 +456,19 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) =>
*/ */
export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => { export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Delete workspace environment' #swagger.summary = 'Delete environment'
#swagger.description = 'Delete a specific environment from a workspace' #swagger.description = 'Delete environment'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": []
}] }]
#swagger.parameters['workspaceId'] = { #swagger.parameters['workspaceId'] = {
"description": "ID of the workspace", "description": "ID of workspace where to delete environment",
"required": true, "required": true,
"type": "string", "type": "string",
"in": "path" "in": "path"
} }
#swagger.requestBody = { #swagger.requestBody = {
content: { content: {
@ -475,8 +478,8 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
"properties": { "properties": {
"environmentSlug": { "environmentSlug": {
"type": "string", "type": "string",
"description": "Slug of the environment to delete", "description": "Slug of environment to delete",
"example": "dev-environment" "example": "dev"
} }
}, },
"required": ["environmentSlug"] "required": ["environmentSlug"]
@ -493,15 +496,18 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"description": "Success message",
"example": "Successfully deleted environment" "example": "Successfully deleted environment"
}, },
"workspace": { "workspace": {
"type": "string", "type": "string",
"example": "someWorkspaceId" "description": "ID of workspace where environment was deleted",
"example": "abc123"
}, },
"environment": { "environment": {
"type": "string", "type": "string",
"example": "dev-environment" "description": "Slug of deleted environment",
"example": "dev"
} }
}, },
"description": "Response after deleting an environment from a workspace" "description": "Response after deleting an environment from a workspace"
@ -515,7 +521,11 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
body: { environmentSlug } body: { environmentSlug }
} = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req); } = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Environments ProjectPermissionSub.Environments
@ -591,98 +601,4 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) =>
workspace: workspaceId, workspace: workspaceId,
environment: environmentSlug environment: environmentSlug
}); });
}; };
// TODO(akhilmhdh) after rbac this can be completely removed
export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => {
/*
#swagger.summary = 'Get all accessible environments of a workspace'
#swagger.description = 'Fetch all environments that the user has access to in a specified workspace'
#swagger.security = [{
"apiKeyAuth": []
}]
#swagger.parameters['workspaceId'] = {
"description": "ID of the workspace",
"required": true,
"type": "string",
"in": "path"
}
#swagger.responses[200] = {
content: {
"application/json": {
"schema": {
"type": "object",
"properties": {
"accessibleEnvironments": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Development"
},
"slug": {
"type": "string",
"example": "development"
},
"isWriteDenied": {
"type": "boolean",
"example": false
},
"isReadDenied": {
"type": "boolean",
"example": false
}
}
}
}
},
"description": "List of environments the user has access to in the specified workspace"
}
}
}
}
*/
const {
params: { workspaceId }
} = await validateRequest(reqValidator.GetAllAccessibileEnvironmentsOfWorkspaceV2, req);
const { membership: workspacesUserIsMemberOf } = await getUserProjectPermissions(
req.user._id,
workspaceId
);
const accessibleEnvironments: any = [];
const deniedPermission = workspacesUserIsMemberOf.deniedPermissions;
const relatedWorkspace = await Workspace.findById(workspaceId);
if (!relatedWorkspace) {
throw BadRequestError();
}
relatedWorkspace.environments.forEach((environment) => {
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 {
accessibleEnvironments.push({
name: environment.name,
slug: environment.slug,
isWriteDenied: isWriteBlocked,
isReadDenied: isReadBlocked
});
}
});
res.json({ accessibleEnvironments });
};

View File

@ -6,20 +6,20 @@ import * as workspaceController from "./workspaceController";
import * as serviceTokenDataController from "./serviceTokenDataController"; import * as serviceTokenDataController from "./serviceTokenDataController";
import * as secretController from "./secretController"; import * as secretController from "./secretController";
import * as secretsController from "./secretsController"; import * as secretsController from "./secretsController";
import * as serviceAccountsController from "./serviceAccountsController";
import * as environmentController from "./environmentController"; import * as environmentController from "./environmentController";
import * as tagController from "./tagController"; import * as tagController from "./tagController";
import * as membershipController from "./membershipController";
export { export {
authController, authController,
signupController, signupController,
usersController, usersController,
organizationsController, organizationsController,
workspaceController, workspaceController,
serviceTokenDataController, serviceTokenDataController,
secretController, secretController,
secretsController, secretsController,
serviceAccountsController, environmentController,
environmentController, tagController,
tagController, membershipController
} };

View File

@ -0,0 +1,107 @@
import { ForbiddenError } from "@casl/ability";
import { Request, Response } from "express";
import { Types } from "mongoose";
import { getSiteURL } from "../../config";
import { EventType } from "../../ee/models";
import { EEAuditLogService } from "../../ee/services";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService";
import { sendMail } from "../../helpers";
import { validateRequest } from "../../helpers/validation";
import { IUser, Key, Membership, MembershipOrg, Workspace } from "../../models";
import { BadRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/membership";
import { ACCEPTED, MEMBER } from "../../variables";
export const addUserToWorkspace = async (req: Request, res: Response) => {
const {
params: { workspaceId },
body: { members }
} = await validateRequest(reqValidator.AddUserToWorkspaceV2, req);
// check workspace
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw new Error("Failed to find workspace");
// check permission
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member
);
// validate members are part of the organization
const orgMembers = await MembershipOrg.find({
status: ACCEPTED,
_id: { $in: members.map(({ orgMembershipId }) => orgMembershipId) },
organization: workspace.organization
})
.populate<{ user: IUser }>("user")
.select({ _id: 1, user: 1 })
.lean();
if (orgMembers.length !== members.length)
throw BadRequestError({ message: "Org member not found" });
const existingMember = await Membership.find({
workspace: workspaceId,
user: { $in: orgMembers.map(({ user }) => user) }
});
if (existingMember?.length)
throw BadRequestError({ message: "Some users are already part of workspace" });
await Membership.insertMany(
orgMembers.map(({ user }) => ({ user: user._id, workspace: workspaceId, role: MEMBER }))
);
const encKeyGroupedByOrgMemberId = members.reduce<Record<string, (typeof members)[number]>>(
(prev, curr) => ({ ...prev, [curr.orgMembershipId]: curr }),
{}
);
await Key.insertMany(
orgMembers.map(({ user, _id: id }) => ({
encryptedKey: encKeyGroupedByOrgMemberId[id.toString()].workspaceEncryptedKey,
nonce: encKeyGroupedByOrgMemberId[id.toString()].workspaceEncryptedNonce,
sender: req.user._id,
receiver: user._id,
workspace: workspaceId
}))
);
await sendMail({
template: "workspaceInvitation.handlebars",
subjectLine: "Infisical workspace invitation",
recipients: orgMembers.map(({ user }) => user.email),
substitutions: {
inviterFirstName: req.user.firstName,
inviterEmail: req.user.email,
workspaceName: workspace.name,
callback_url: (await getSiteURL()) + "/login"
}
});
await EEAuditLogService.createAuditLog(
req.authData,
{
type: EventType.ADD_BATCH_WORKSPACE_MEMBER,
metadata: orgMembers.map(({ user }) => ({
userId: user._id.toString(),
email: user.email
}))
},
{
workspaceId: new Types.ObjectId(workspaceId)
}
);
return res.status(200).send({
success: true,
data: orgMembers
});
};

View File

@ -1,25 +1,16 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import { Membership, MembershipOrg, Workspace } from "../../models";
Membership,
MembershipOrg,
ServiceAccount,
Workspace
} from "../../models";
import { Role } from "../../ee/models"; import { Role } from "../../ee/models";
import { deleteMembershipOrg } from "../../helpers/membershipOrg"; import { deleteMembershipOrg } from "../../helpers/membershipOrg";
import { import {
createOrganization as create, createOrganization as create,
deleteOrganization, deleteOrganization,
updateSubscriptionOrgQuantity updateSubscriptionOrgQuantity
} from "../../helpers/organization"; } from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg"; import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { import { ACCEPTED, ADMIN, CUSTOM } from "../../variables";
ACCEPTED,
ADMIN,
CUSTOM
} from "../../variables";
import * as reqValidator from "../../validation/organization"; import * as reqValidator from "../../validation/organization";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import { import {
@ -156,7 +147,7 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
OrgPermissionSubjects.Member OrgPermissionSubjects.Member
); );
const isCustomRole = !["admin", "member", "owner"].includes(role); const isCustomRole = !["admin", "member"].includes(role);
if (isCustomRole) { if (isCustomRole) {
const orgRole = await Role.findOne({ slug: role, isOrgRole: true }); const orgRole = await Role.findOne({ slug: role, isOrgRole: true });
if (!orgRole) throw BadRequestError({ message: "Role not found" }); if (!orgRole) throw BadRequestError({ message: "Role not found" });
@ -330,23 +321,6 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}); });
}; };
/**
* 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({
serviceAccounts
});
};
/** /**
* Create new organization named [organizationName] * Create new organization named [organizationName]
* and add user as owner * and add user as owner
@ -354,7 +328,7 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response
* @param res * @param res
* @returns * @returns
*/ */
export const createOrganization = async (req: Request, res: Response) => { export const createOrganization = async (req: Request, res: Response) => {
const { const {
body: { name } body: { name }
} = await validateRequest(reqValidator.CreateOrgv2, req); } = await validateRequest(reqValidator.CreateOrgv2, req);
@ -379,27 +353,27 @@ export const getOrganizationServiceAccounts = async (req: Request, res: Response
/** /**
* Delete organization with id [organizationId] * Delete organization with id [organizationId]
* @param req * @param req
* @param res * @param res
*/ */
export const deleteOrganizationById = async (req: Request, res: Response) => { export const deleteOrganizationById = async (req: Request, res: Response) => {
const { const {
params: { organizationId } params: { organizationId }
} = await validateRequest(reqValidator.DeleteOrgv2, req); } = await validateRequest(reqValidator.DeleteOrgv2, req);
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
user: req.user._id, user: req.user._id,
organization: new Types.ObjectId(organizationId), organization: new Types.ObjectId(organizationId),
role: ADMIN role: ADMIN
}); });
if (!membershipOrg) throw UnauthorizedRequestError(); if (!membershipOrg) throw UnauthorizedRequestError();
const organization = await deleteOrganization({ const organization = await deleteOrganization({
organizationId: new Types.ObjectId(organizationId) organizationId: new Types.ObjectId(organizationId)
}); });
return res.status(200).send({ return res.status(200).send({
organization organization
}); });
} };

View File

@ -11,7 +11,6 @@ import {
ValidationError as RouteValidationError, ValidationError as RouteValidationError,
UnauthorizedRequestError UnauthorizedRequestError
} from "../../utils/errors"; } from "../../utils/errors";
import { AnyBulkWriteOperation } from "mongodb";
import { import {
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
@ -19,7 +18,7 @@ import {
SECRET_SHARED SECRET_SHARED
} from "../../variables"; } from "../../variables";
import { TelemetryService } from "../../services"; import { TelemetryService } from "../../services";
import { ISecret, Secret, User } from "../../models"; import { Secret, User } from "../../models";
import { AccountNotFoundError } from "../../utils/errors"; import { AccountNotFoundError } from "../../utils/errors";
/** /**
@ -145,22 +144,22 @@ export const deleteSecrets = async (req: Request, res: Response) => {
const secretsUserCanDeleteSet: Set<string> = new Set( const secretsUserCanDeleteSet: Set<string> = new Set(
secretIdsUserCanDelete.map((objectId) => objectId._id.toString()) secretIdsUserCanDelete.map((objectId) => objectId._id.toString())
); );
const deleteOperationsToPerform: AnyBulkWriteOperation<ISecret>[] = [];
let numSecretsDeleted = 0; // Filter out IDs that user can delete and then map them to delete operations
secretIdsToDelete.forEach((secretIdToDelete) => { const deleteOperationsToPerform = secretIdsToDelete
if (secretsUserCanDeleteSet.has(secretIdToDelete)) { .filter(secretIdToDelete => {
const deleteOperation = { if (!secretsUserCanDeleteSet.has(secretIdToDelete)) {
deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } } throw RouteValidationError({
}; message: "You cannot delete secrets that you do not have access to"
deleteOperationsToPerform.push(deleteOperation); });
numSecretsDeleted++; }
} else { return true;
throw RouteValidationError({ })
message: "You cannot delete secrets that you do not have access to" .map(secretIdToDelete => ({
}); deleteOne: { filter: { _id: new Types.ObjectId(secretIdToDelete) } }
} }));
});
const numSecretsDeleted = deleteOperationsToPerform.length;
await Secret.bulkWrite(deleteOperationsToPerform); await Secret.bulkWrite(deleteOperationsToPerform);

View File

@ -1,12 +1,8 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models"; import { Folder, ISecret, Secret, ServiceTokenData, Tag } from "../../models";
import { AuditLog, EventType, IAction, SecretVersion } from "../../ee/models"; import { AuditLog, EventType, SecretVersion } from "../../ee/models";
import { import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
K8_USER_AGENT_NAME, K8_USER_AGENT_NAME,
@ -15,7 +11,7 @@ import {
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { EventService } from "../../services"; import { EventService } from "../../services";
import { eventPushSecrets } from "../../events"; import { eventPushSecrets } from "../../events";
import { EEAuditLogService, EELogService, EESecretService } from "../../ee/services"; import { EEAuditLogService, EESecretService } from "../../ee/services";
import { SecretService, TelemetryService } from "../../services"; import { SecretService, TelemetryService } from "../../services";
import { getUserAgentType } from "../../utils/posthog"; import { getUserAgentType } from "../../utils/posthog";
import { PERMISSION_WRITE_SECRETS } from "../../variables"; import { PERMISSION_WRITE_SECRETS } from "../../variables";
@ -43,7 +39,7 @@ import {
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
@ -82,7 +78,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
const createSecrets: any[] = []; const createSecrets: any[] = [];
const updateSecrets: any[] = []; const updateSecrets: any[] = [];
const deleteSecrets: { _id: Types.ObjectId; secretName: string }[] = []; const deleteSecrets: { _id: Types.ObjectId; secretName: string }[] = [];
const actions: IAction[] = [];
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
@ -164,7 +159,11 @@ export const batchSecrets = async (req: Request, res: Response) => {
} }
// not using service token using auth // not using service token using auth
if (!(req.authData.authPayload instanceof ServiceTokenData)) { if (!(req.authData.authPayload instanceof ServiceTokenData)) {
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (createSecrets.length) if (createSecrets.length)
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
@ -224,16 +223,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
await AuditLog.insertMany(auditLogs); await AuditLog.insertMany(auditLogs);
const addAction = (await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: createdSecrets.map((n) => n._id)
})) as IAction;
actions.push(addAction);
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: "secrets added", event: "secrets added",
@ -350,14 +339,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
await AuditLog.insertMany(auditLogs); await AuditLog.insertMany(auditLogs);
const updateAction = (await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: updatedSecrets.map((u) => u._id)
})) as IAction;
actions.push(updateAction);
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: "secrets modified", event: "secrets modified",
@ -428,14 +409,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
await AuditLog.insertMany(auditLogs); await AuditLog.insertMany(auditLogs);
const deleteAction = (await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
userId: req.user._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: deleteSecretIds
})) as IAction;
actions.push(deleteAction);
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
event: "secrets deleted", event: "secrets deleted",
@ -451,17 +424,6 @@ export const batchSecrets = async (req: Request, res: Response) => {
} }
} }
if (actions.length > 0) {
// (EE) create (audit) log
await EELogService.createLog({
userId: req.user._id.toString(),
workspaceId: new Types.ObjectId(workspaceId),
actions,
channel,
ipAddress: req.realIP
});
}
// // trigger event - push secrets // // trigger event - push secrets
await EventService.handleEvent({ await EventService.handleEvent({
event: eventPushSecrets({ event: eventPushSecrets({
@ -731,27 +693,6 @@ export const createSecrets = async (req: Request, res: Response) => {
) )
}); });
const addAction = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId),
secretIds: newlyCreatedSecrets.map((n) => n._id)
});
// (EE) create (audit) log
addAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId),
actions: [addAction],
channel,
ipAddress: req.realIP
}));
// (EE) take a secret snapshot // (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({ await EESecretService.takeSecretSnapshot({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
@ -960,22 +901,6 @@ export const getSecrets = async (req: Request, res: Response) => {
secrets = await Secret.find(secretQuery).populate("tags"); secrets = await Secret.find(secretQuery).populate("tags");
} }
// case: client authorization is via service account
if (req.serviceAccount) {
const secretQuery: any = {
workspace: workspaceId,
environment,
folder: folderId,
user: { $exists: false } // shared secrets only from workspace
};
if (tagIds.length > 0) {
secretQuery.tags = { $in: tagIds };
}
secrets = await Secret.find(secretQuery).populate("tags");
}
// TODO(akhilmhdh) - secret-imp change this to org type // TODO(akhilmhdh) - secret-imp change this to org type
let importedSecrets: any[] = []; let importedSecrets: any[] = [];
if (include_imports) { if (include_imports) {
@ -988,28 +913,6 @@ export const getSecrets = async (req: Request, res: Response) => {
); );
} }
const channel = getUserAgentType(req.headers["user-agent"]);
const readAction = await EELogService.createAction({
name: ACTION_READ_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId as string),
secretIds: secrets.map((n: any) => n._id)
});
readAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(workspaceId as string),
actions: [readAction],
channel,
ipAddress: req.realIP
}));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
req.authData, req.authData,
{ {
@ -1247,27 +1150,6 @@ export const updateSecrets = async (req: Request, res: Response) => {
// }); // });
// }, 10000); // }, 10000);
const updateAction = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
});
// (EE) create (audit) log
updateAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
actions: [updateAction],
channel,
ipAddress: req.realIP
}));
// (EE) take a secret snapshot // (EE) take a secret snapshot
// IMP(akhilmhdh): commented out due to unknown where the environment is // IMP(akhilmhdh): commented out due to unknown where the environment is
// await EESecretService.takeSecretSnapshot({ // await EESecretService.takeSecretSnapshot({
@ -1387,26 +1269,6 @@ export const deleteSecrets = async (req: Request, res: Response) => {
// workspaceId: new Types.ObjectId(key) // workspaceId: new Types.ObjectId(key)
// }) // })
// }); // });
const deleteAction = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id)
});
// (EE) create (audit) log
deleteAction &&
(await EELogService.createLog({
userId: req.user?._id,
serviceAccountId: req.serviceAccount?._id,
serviceTokenDataId: req.serviceTokenData?._id,
workspaceId: new Types.ObjectId(key),
actions: [deleteAction],
channel,
ipAddress: req.realIP
}));
// (EE) take a secret snapshot // (EE) take a secret snapshot
// IMP(akhilmhdh): Not sure how to take secretSnapshot // IMP(akhilmhdh): Not sure how to take secretSnapshot

View File

@ -1,306 +0,0 @@
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 as any).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

@ -11,7 +11,7 @@ import * as reqValidator from "../../validation/serviceTokenData";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { Types } from "mongoose"; import { Types } from "mongoose";
@ -75,7 +75,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
const { const {
body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv } body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv }
} = await validateRequest(reqValidator.CreateServiceTokenV2, req); } = await validateRequest(reqValidator.CreateServiceTokenV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens
@ -151,10 +156,11 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId); let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId);
if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" }); if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
serviceTokenData.workspace.toString() workspaceId: serviceTokenData.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens

View File

@ -7,7 +7,7 @@ import { validateRequest } from "../../helpers/validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import * as reqValidator from "../../validation/tags"; import * as reqValidator from "../../validation/tags";
@ -17,7 +17,11 @@ export const createWorkspaceTag = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req); } = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.Tags ProjectPermissionSub.Tags
@ -45,10 +49,11 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
throw BadRequestError(); throw BadRequestError();
} }
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
tagFromDB.workspace.toString() workspaceId: tagFromDB.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Tags ProjectPermissionSub.Tags
@ -66,7 +71,12 @@ export const getWorkspaceTags = async (req: Request, res: Response) => {
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceTagsV2, req); } = await validateRequest(reqValidator.GetWorkspaceTagsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Tags ProjectPermissionSub.Tags

View File

@ -16,7 +16,7 @@ import * as reqValidator from "../../validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../ee/services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
@ -272,7 +272,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req); } = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@ -352,7 +356,11 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) =>
body: { role } body: { role }
} = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req); } = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@ -420,7 +428,11 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) =>
params: { workspaceId, membershipId } params: { workspaceId, membershipId }
} = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req); } = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.Member ProjectPermissionSub.Member
@ -452,7 +464,11 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => {
body: { autoCapitalization } body: { autoCapitalization }
} = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req); } = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.Settings ProjectPermissionSub.Settings

View File

@ -8,10 +8,8 @@ import { createToken, issueAuthTokens, validateProviderAuthToken } from "../../h
import { checkUserDevice } from "../../helpers/user"; import { checkUserDevice } from "../../helpers/user";
import { sendMail } from "../../helpers/nodemailer"; import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services"; import { TokenService } from "../../services";
import { EELogService } from "../../ee/services";
import { BadRequestError, InternalServerError } from "../../utils/errors"; import { BadRequestError, InternalServerError } from "../../utils/errors";
import { ACTION_LOGIN, AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables"; import { AuthTokenType, TOKEN_EMAIL_MFA } from "../../variables";
import { getUserAgentType } from "../../utils/posthog"; // TODO: move this
import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config"; import { getAuthSecret, getHttpsEnabled, getJwtMfaLifetime } from "../../config";
import { AuthMethod } from "../../models/user"; import { AuthMethod } from "../../models/user";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
@ -215,19 +213,6 @@ export const login2 = async (req: Request, res: Response) => {
response.protectedKeyTag = user.protectedKeyTag; 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: getUserAgentType(req.headers["user-agent"]),
ipAddress: req.realIP
}));
return res.status(200).send(response); return res.status(200).send(response);
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
import { Request, Response } from "express";
import { APIKeyDataV2 } from "../../models";
/**
* Return API keys belonging to current user.
* @param req
* @param res
* @returns
*/
export const getMyAPIKeys = async (req: Request, res: Response) => {
const apiKeyData = await APIKeyDataV2.find({
user: req.user._id
});
return res.status(200).send({
apiKeyData
});
}

View File

@ -1,9 +1,9 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
import { Secret, ServiceTokenDataV3 } from "../../models"; import { Membership, Secret, ServiceTokenDataV3, User } from "../../models";
import { SecretService } from "../../services"; import { SecretService } from "../../services";
import { getUserProjectPermissions } from "../../ee/services/ProjectRoleService"; import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService";
import { UnauthorizedRequestError } from "../../utils/errors"; import { UnauthorizedRequestError } from "../../utils/errors";
import * as reqValidator from "../../validation/workspace"; import * as reqValidator from "../../validation/workspace";
@ -19,9 +19,22 @@ export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response)
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req); } = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
if (membership.role !== "admin") authData: req.authData,
throw UnauthorizedRequestError({ message: "User must be an admin" }); workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
const secretsWithoutBlindIndex = await Secret.countDocuments({ const secretsWithoutBlindIndex = await Secret.countDocuments({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
@ -41,9 +54,22 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req); } = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
if (membership.role !== "admin") authData: req.authData,
throw UnauthorizedRequestError({ message: "User must be an admin" }); workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
const secrets = await Secret.find({ const secrets = await Secret.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
@ -65,9 +91,22 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => {
body: { secretsToUpdate } body: { secretsToUpdate }
} = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req); } = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); await getAuthDataProjectPermissions({
if (membership.role !== "admin") authData: req.authData,
throw UnauthorizedRequestError({ message: "User must be an admin" }); workspaceId: new Types.ObjectId(workspaceId)
});
if (req.authData.authPayload instanceof User) {
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
if (membership.role !== "admin")
throw UnauthorizedRequestError({ message: "User must be an admin" });
}
// get secret blind index salt // get secret blind index salt
const salt = await SecretService.getSecretBlindIndexSalt({ const salt = await SecretService.getSecretBlindIndexSalt({
@ -109,7 +148,7 @@ export const getWorkspaceServiceTokenData = async (req: Request, res: Response)
const serviceTokenData = await ServiceTokenDataV3.find({ const serviceTokenData = await ServiceTokenDataV3.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
}); }).populate("customRole");
return res.status(200).send({ return res.status(200).send({
serviceTokenData serviceTokenData

View File

@ -1,32 +0,0 @@
import { Request, Response } from "express";
import { Action } from "../../models";
import { ActionNotFoundError } from "../../../utils/errors";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/action";
export const getAction = async (req: Request, res: Response) => {
let action;
try {
const {
params: { actionId }
} = await validateRequest(reqValidator.GetActionV1, req);
action = await Action.findById(actionId).populate([
"payload.secretVersions.oldSecretVersion",
"payload.secretVersions.newSecretVersion"
]);
if (!action)
throw ActionNotFoundError({
message: "Failed to find action"
});
} catch (err) {
throw ActionNotFoundError({
message: "Failed to find action"
});
}
return res.status(200).send({
action
});
};

View File

@ -4,12 +4,13 @@ import * as organizationsController from "./organizationsController";
import * as ssoController from "./ssoController"; import * as ssoController from "./ssoController";
import * as usersController from "./usersController"; import * as usersController from "./usersController";
import * as workspaceController from "./workspaceController"; import * as workspaceController from "./workspaceController";
import * as actionController from "./actionController";
import * as membershipController from "./membershipController"; import * as membershipController from "./membershipController";
import * as cloudProductsController from "./cloudProductsController"; import * as cloudProductsController from "./cloudProductsController";
import * as roleController from "./roleController"; import * as roleController from "./roleController";
import * as secretApprovalPolicyController from "./secretApprovalPolicyController"; import * as secretApprovalPolicyController from "./secretApprovalPolicyController";
import * as secretApprovalRequestController from "./secretApprovalRequestsController"; import * as secretApprovalRequestController from "./secretApprovalRequestsController";
import * as secretRotationProviderController from "./secretRotationProviderController";
import * as secretRotationController from "./secretRotationController";
export { export {
secretController, secretController,
@ -18,10 +19,11 @@ export {
ssoController, ssoController,
usersController, usersController,
workspaceController, workspaceController,
actionController,
membershipController, membershipController,
cloudProductsController, cloudProductsController,
roleController, roleController,
secretApprovalPolicyController, secretApprovalPolicyController,
secretApprovalRequestController secretApprovalRequestController,
secretRotationProviderController,
secretRotationController
}; };

View File

@ -1,4 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose";
import { Membership, User } from "../../../models";
import { import {
CreateRoleSchema, CreateRoleSchema,
DeleteRoleSchema, DeleteRoleSchema,
@ -11,7 +13,7 @@ import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
adminProjectPermissions, adminProjectPermissions,
getUserProjectPermissions, getAuthDataProjectPermissions,
memberProjectPermissions, memberProjectPermissions,
viewerProjectPermission viewerProjectPermission
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
@ -39,7 +41,10 @@ export const createRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "user doesn't have the permission." }); throw BadRequestError({ message: "user doesn't have the permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." }); throw BadRequestError({ message: "User doesn't have the permission." });
} }
@ -82,7 +87,11 @@ export const updateRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." }); throw BadRequestError({ message: "User doesn't have the org permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." }); throw BadRequestError({ message: "User doesn't have the workspace permission." });
} }
@ -134,7 +143,11 @@ export const deleteRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." }); throw BadRequestError({ message: "User doesn't have the org permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, role.workspace.toString()); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: role.workspace
});
if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." }); throw BadRequestError({ message: "User doesn't have the workspace permission." });
} }
@ -162,7 +175,11 @@ export const getRoles = async (req: Request, res: Response) => {
throw BadRequestError({ message: "User doesn't have the org permission." }); throw BadRequestError({ message: "User doesn't have the org permission." });
} }
} else { } else {
const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) { if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) {
throw BadRequestError({ message: "User doesn't have the workspace permission." }); throw BadRequestError({ message: "User doesn't have the workspace permission." });
} }
@ -212,12 +229,13 @@ export const getUserPermissions = async (req: Request, res: Response) => {
const { const {
params: { orgId } params: { orgId }
} = await validateRequest(GetUserPermission, req); } = await validateRequest(GetUserPermission, req);
const { permission } = await getUserOrgPermissions(req.user._id, orgId); const { permission, membership } = await getUserOrgPermissions(req.user._id, orgId);
res.status(200).json({ res.status(200).json({
data: { data: {
permissions: packRules(permission.rules) permissions: packRules(permission.rules),
membership
} }
}); });
}; };
@ -226,11 +244,24 @@ export const getUserWorkspacePermissions = async (req: Request, res: Response) =
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetUserProjectPermission, req); } = await validateRequest(GetUserProjectPermission, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
let membership;
if (req.authData.authPayload instanceof User) {
membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
})
}
res.status(200).json({ res.status(200).json({
data: { data: {
permissions: packRules(permission.rules) permissions: packRules(permission.rules),
membership
} }
}); });
}; };

View File

@ -1,10 +1,11 @@
import { Types } from "mongoose";
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import { SecretApprovalPolicy } from "../../models/secretApprovalPolicy"; import { SecretApprovalPolicy } from "../../models/secretApprovalPolicy";
@ -19,7 +20,11 @@ export const createSecretApprovalPolicy = async (req: Request, res: Response) =>
body: { approvals, secretPath, approvers, environment, workspaceId, name } body: { approvals, secretPath, approvers, environment, workspaceId, name }
} = await validateRequest(reqValidator.CreateSecretApprovalRule, req); } = await validateRequest(reqValidator.CreateSecretApprovalRule, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@ -49,10 +54,11 @@ export const updateSecretApprovalPolicy = async (req: Request, res: Response) =>
const secretApproval = await SecretApprovalPolicy.findById(id); const secretApproval = await SecretApprovalPolicy.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND; if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
secretApproval.workspace.toString() workspaceId: secretApproval.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@ -78,10 +84,11 @@ export const deleteSecretApprovalPolicy = async (req: Request, res: Response) =>
const secretApproval = await SecretApprovalPolicy.findById(id); const secretApproval = await SecretApprovalPolicy.findById(id);
if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND; if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND;
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
secretApproval.workspace.toString() workspaceId: secretApproval.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@ -99,7 +106,11 @@ export const getSecretApprovalPolicy = async (req: Request, res: Response) => {
query: { workspaceId } query: { workspaceId }
} = await validateRequest(reqValidator.GetSecretApprovalRuleList, req); } = await validateRequest(reqValidator.GetSecretApprovalRuleList, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
@ -117,7 +128,11 @@ export const getSecretApprovalPolicyOfBoard = async (req: Request, res: Response
query: { workspaceId, environment, secretPath } query: { workspaceId, environment, secretPath }
} = await validateRequest(reqValidator.GetSecretApprovalPolicyOfABoard, req); } = await validateRequest(reqValidator.GetSecretApprovalPolicyOfABoard, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { secretPath, environment }) subject(ProjectPermissionSub.Secrets, { secretPath, environment })

View File

@ -1,7 +1,6 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import { getUserProjectPermissions } from "../../services/ProjectRoleService";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import { Folder } from "../../../models"; import { Folder, Membership, User } from "../../../models";
import { ApprovalStatus, SecretApprovalRequest } from "../../models/secretApprovalRequest"; import { ApprovalStatus, SecretApprovalRequest } from "../../models/secretApprovalRequest";
import * as reqValidator from "../../validation/secretApprovalRequest"; import * as reqValidator from "../../validation/secretApprovalRequest";
import { getFolderWithPathFromId } from "../../../services/FolderService"; import { getFolderWithPathFromId } from "../../../services/FolderService";
@ -17,7 +16,15 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response)
query: { workspaceId } query: { workspaceId }
} = await validateRequest(reqValidator.getSecretApprovalRequestCount, req); } = await validateRequest(reqValidator.getSecretApprovalRequestCount, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const approvalRequestCount = await SecretApprovalRequest.aggregate([ const approvalRequestCount = await SecretApprovalRequest.aggregate([
{ {
$match: { $match: {
@ -65,7 +72,14 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) =>
query: { status, committer, workspaceId, environment, limit, offset } query: { status, committer, workspaceId, environment, limit, offset }
} = await validateRequest(reqValidator.getSecretApprovalRequests, req); } = await validateRequest(reqValidator.getSecretApprovalRequests, req);
const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); if (!(req.authData.authPayload instanceof User)) return;
const membership = await Membership.findOne({
user: req.authData.authPayload._id,
workspace: new Types.ObjectId(workspaceId)
});
if (!membership) throw UnauthorizedRequestError();
const query = { const query = {
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
@ -148,10 +162,15 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
// allow to fetch only if its admin or is the committer or approver // allow to fetch only if its admin or is the committer or approver
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&
@ -190,10 +209,15 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&
secretApprovalRequest.committer !== membership.id && secretApprovalRequest.committer !== membership.id &&
@ -227,10 +251,15 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) =>
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&
secretApprovalRequest.committer !== membership.id && secretApprovalRequest.committer !== membership.id &&
@ -272,10 +301,14 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo
if (!secretApprovalRequest) if (!secretApprovalRequest)
throw BadRequestError({ message: "Secret approval request not found" }); throw BadRequestError({ message: "Secret approval request not found" });
const { membership } = await getUserProjectPermissions( if (!(req.authData.authPayload instanceof User)) return;
req.user._id,
secretApprovalRequest.workspace.toString() const membership = await Membership.findOne({
); user: req.authData.authPayload._id,
workspace: secretApprovalRequest.workspace
});
if (!membership) throw UnauthorizedRequestError();
if ( if (
membership.role !== "admin" && membership.role !== "admin" &&

View File

@ -5,7 +5,7 @@ import { Folder, Secret } from "../../../models";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { BadRequestError } from "../../../utils/errors"; import { BadRequestError } from "../../../utils/errors";
import * as reqValidator from "../../../validation"; import * as reqValidator from "../../../validation";
@ -74,7 +74,11 @@ export const getSecretVersions = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Failed to find secret" }); throw BadRequestError({ message: "Failed to find secret" });
} }
const { permission } = await getUserProjectPermissions(req.user._id, secret.workspace.toString()); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: secret.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@ -157,10 +161,12 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => {
if (!toBeUpdatedSec) { if (!toBeUpdatedSec) {
throw BadRequestError({ message: "Failed to find secret" }); throw BadRequestError({ message: "Failed to find secret" });
} }
const { permission } = await getUserProjectPermissions(
req.user._id, const { permission } = await getAuthDataProjectPermissions({
toBeUpdatedSec.workspace.toString() authData: req.authData,
); workspaceId: toBeUpdatedSec.workspace
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback

View File

@ -0,0 +1,110 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../validation/secretRotation";
import * as secretRotationService from "../../secretRotation/service";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
export const createSecretRotation = async (req: Request, res: Response) => {
const {
body: {
provider,
customProvider,
interval,
outputs,
secretPath,
environment,
workspaceId,
inputs
}
} = await validateRequest(reqValidator.createSecretRotationV1, req);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation
);
const secretRotation = await secretRotationService.createSecretRotation({
workspaceId,
inputs,
environment,
secretPath,
outputs,
interval,
customProvider,
provider
});
return res.send({ secretRotation });
};
export const restartSecretRotations = async (req: Request, res: Response) => {
const {
body: { id }
} = await validateRequest(reqValidator.restartSecretRotationV1, req);
const doc = await secretRotationService.getSecretRotationById({ id });
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: doc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SecretRotation
);
const secretRotation = await secretRotationService.restartSecretRotation({ id });
return res.send({ secretRotation });
};
export const deleteSecretRotations = async (req: Request, res: Response) => {
const {
params: { id }
} = await validateRequest(reqValidator.removeSecretRotationV1, req);
const doc = await secretRotationService.getSecretRotationById({ id });
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: doc.workspace
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation
);
const secretRotations = await secretRotationService.deleteSecretRotation({ id });
return res.send({ secretRotations });
};
export const getSecretRotations = async (req: Request, res: Response) => {
const {
query: { workspaceId }
} = await validateRequest(reqValidator.getSecretRotationV1, req);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRotation
);
const secretRotations = await secretRotationService.getSecretRotationOfWorkspace(workspaceId);
return res.send({ secretRotations });
};

View File

@ -0,0 +1,33 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../validation/secretRotationProvider";
import * as secretRotationProviderService from "../../secretRotation/service";
import {
ProjectPermissionActions,
ProjectPermissionSub,
getAuthDataProjectPermissions
} from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability";
export const getProviderTemplates = async (req: Request, res: Response) => {
const {
params: { workspaceId }
} = await validateRequest(reqValidator.getSecretRotationProvidersV1, req);
const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRotation
);
const rotationProviderList = await secretRotationProviderService.getProviderTemplate({
workspaceId
});
return res.send(rotationProviderList);
};

View File

@ -4,7 +4,7 @@ import { validateRequest } from "../../../helpers/validation";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import * as reqValidator from "../../../validation/secretSnapshot"; import * as reqValidator from "../../../validation/secretSnapshot";
import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models"; import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models";
@ -33,10 +33,11 @@ export const getSecretSnapshot = async (req: Request, res: Response) => {
if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); if (!secretSnapshot) throw new Error("Failed to find secret snapshot");
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
secretSnapshot.workspace.toString() workspaceId: secretSnapshot.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback

View File

@ -17,7 +17,6 @@ import {
FolderVersion, FolderVersion,
IPType, IPType,
ISecretVersion, ISecretVersion,
Log,
SecretSnapshot, SecretSnapshot,
SecretVersion, SecretVersion,
ServiceActor, ServiceActor,
@ -28,7 +27,6 @@ import {
} from "../../models"; } from "../../models";
import { EESecretService } from "../../services"; import { EESecretService } from "../../services";
import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion";
// import Folder, { TFolderSchema } from "../../../models/folder";
import { getFolderByPath, searchByFolderId } from "../../../services/FolderService"; import { getFolderByPath, searchByFolderId } from "../../../services/FolderService";
import { EEAuditLogService, EELicenseService } from "../../services"; import { EEAuditLogService, EELicenseService } from "../../services";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
@ -38,7 +36,6 @@ import {
DeleteWorkspaceTrustedIpV1, DeleteWorkspaceTrustedIpV1,
GetWorkspaceAuditLogActorFilterOptsV1, GetWorkspaceAuditLogActorFilterOptsV1,
GetWorkspaceAuditLogsV1, GetWorkspaceAuditLogsV1,
GetWorkspaceLogsV1,
GetWorkspaceSecretSnapshotsCountV1, GetWorkspaceSecretSnapshotsCountV1,
GetWorkspaceSecretSnapshotsV1, GetWorkspaceSecretSnapshotsV1,
GetWorkspaceTrustedIpsV1, GetWorkspaceTrustedIpsV1,
@ -48,7 +45,7 @@ import {
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError } from "../../../utils/errors"; import { BadRequestError } from "../../../utils/errors";
@ -109,7 +106,11 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) =
query: { environment, directory, offset, limit } query: { environment, directory, offset, limit }
} = await validateRequest(GetWorkspaceSecretSnapshotsV1, req); } = await validateRequest(GetWorkspaceSecretSnapshotsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@ -150,7 +151,11 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon
query: { environment, directory } query: { environment, directory }
} = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req); } = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@ -240,7 +245,11 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
body: { directory, environment, version } body: { directory, environment, version }
} = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req); } = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback ProjectPermissionSub.SecretRollback
@ -564,128 +573,102 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons
}; };
/** /**
* Return (audit) logs for workspace with id [workspaceId] * Return audit logs for workspace with id [workspaceId]
* @param req * @param req
* @param res * @param res
* @returns
*/ */
export const getWorkspaceLogs = async (req: Request, res: Response) => { export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
/* /*
#swagger.summary = 'Return project (audit) logs' #swagger.summary = 'Return audit logs'
#swagger.description = 'Return project (audit) logs' #swagger.description = 'Return audit logs'
#swagger.security = [{ #swagger.security = [{
"apiKeyAuth": [] "apiKeyAuth": []
}] }]
#swagger.parameters['workspaceId'] = { #swagger.parameters['workspaceId'] = {
"description": "ID of project", "description": "ID of the workspace where to get folders from",
"required": true, "required": true,
"type": "string" "type": "string",
} "in": "path"
}
#swagger.parameters['userId'] = { #swagger.parameters['offset'] = {
"description": "ID of project member", "description": "Number of logs to skip before starting to return logs for pagination",
"required": false, "required": false,
"type": "string" "type": "string"
} }
#swagger.parameters['offset'] = { #swagger.parameters['limit'] = {
"description": "Number of logs to skip", "description": "Maximum number of logs to return for pagination",
"required": false, "required": false,
"type": "string" "type": "string"
} }
#swagger.parameters['limit'] = { #swagger.parameters['startDate'] = {
"description": "Maximum number of logs to return", "description": "Filter logs from this date in ISO-8601 format",
"required": false, "required": false,
"type": "string" "type": "string"
} }
#swagger.parameters['sortBy'] = { #swagger.parameters['endDate'] = {
"description": "Order to sort the logs by", "description": "Filter logs till this date in ISO-8601 format",
"schema": { "required": false,
"type": "string", "type": "string"
"@enum": ["oldest", "recent"] }
},
"required": false
}
#swagger.parameters['actionNames'] = { #swagger.parameters['eventType'] = {
"description": "Names of log actions (comma-separated)", "description": "Filter by type of event such as get-secrets, get-secret, create-secret, update-secret, delete-secret, etc.",
"required": false, "required": false,
"type": "string" "type": "string",
} }
#swagger.parameters['userAgentType'] = {
"description": "Filter by type of user agent such as web, cli, k8-operator, or other",
"required": false,
"type": "string",
}
#swagger.parameters['actor'] = {
"description": "Filter by actor such as user or service",
"required": false,
"type": "string"
}
#swagger.responses[200] = { #swagger.responses[200] = {
content: { content: {
"application/json": { "application/json": {
schema: { schema: {
"type": "object", "type": "object",
"properties": { "properties": {
"logs": { "auditLogs": {
"type": "array", "type": "array",
"items": { "items": {
$ref: "#/components/schemas/Log" $ref: "#/components/schemas/AuditLog",
}, },
"description": "Project logs" "description": "List of audit log"
} },
} }
} }
} }
} }
} }
*/ */
const {
query: { limit, offset, userId, sortBy, actionNames },
params: { workspaceId }
} = await validateRequest(GetWorkspaceLogsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs
);
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
});
};
/**
* Return audit logs for workspace with id [workspaceId]
* @param req
* @param res
*/
export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
const { const {
query: { limit, offset, endDate, eventType, startDate, userAgentType, actor }, query: { limit, offset, endDate, eventType, startDate, userAgentType, actor },
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogsV1, req); } = await validateRequest(GetWorkspaceAuditLogsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs ProjectPermissionSub.AuditLogs
); );
const query = { const query = {
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
...(eventType ...(eventType
@ -719,14 +702,9 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => {
} }
: {}) : {})
}; };
const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit); const auditLogs = await AuditLog.find(query).sort({ createdAt: -1 }).skip(offset).limit(limit);
const totalCount = await AuditLog.countDocuments(query);
return res.status(200).send({ return res.status(200).send({
auditLogs, auditLogs
totalCount
}); });
}; };
@ -740,7 +718,11 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req); } = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.AuditLogs ProjectPermissionSub.AuditLogs
@ -774,7 +756,7 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
name: serviceTokenData.name name: serviceTokenData.name
} }
})); }));
const serviceV3Actors: ServiceActorV3[] = ( const serviceV3Actors: ServiceActorV3[] = (
await ServiceTokenDataV3.find({ await ServiceTokenDataV3.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
@ -786,12 +768,8 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
name: serviceTokenData.name name: serviceTokenData.name
} }
})); }));
const actors = [ const actors = [...userActors, ...serviceActors, ...serviceV3Actors];
...userActors,
...serviceActors,
...serviceV3Actors
];
return res.status(200).send({ return res.status(200).send({
actors actors
@ -808,7 +786,11 @@ export const getWorkspaceTrustedIps = async (req: Request, res: Response) => {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(GetWorkspaceTrustedIpsV1, req); } = await validateRequest(GetWorkspaceTrustedIpsV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read, ProjectPermissionActions.Read,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList
@ -834,7 +816,11 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => {
body: { comment, isActive, ipAddress: ip } body: { comment, isActive, ipAddress: ip }
} = await validateRequest(AddWorkspaceTrustedIpV1, req); } = await validateRequest(AddWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList
@ -900,7 +886,11 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => {
body: { ipAddress: ip, comment } body: { ipAddress: ip, comment }
} = await validateRequest(UpdateWorkspaceTrustedIpV1, req); } = await validateRequest(UpdateWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList
@ -992,7 +982,11 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => {
params: { workspaceId, trustedIpId } params: { workspaceId, trustedIpId }
} = await validateRequest(DeleteWorkspaceTrustedIpV1, req); } = await validateRequest(DeleteWorkspaceTrustedIpV1, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
ProjectPermissionSub.IpAllowList ProjectPermissionSub.IpAllowList

View File

@ -0,0 +1,101 @@
import { Request, Response } from "express";
import { Types } from "mongoose";
import { APIKeyDataV2 } from "../../../models/apiKeyDataV2";
import { validateRequest } from "../../../helpers/validation";
import { BadRequestError } from "../../../utils/errors";
import * as reqValidator from "../../../validation";
import { createToken } from "../../../helpers";
import { AuthTokenType } from "../../../variables";
import { getAuthSecret } from "../../../config";
/**
* Create API key data v2
* @param req
* @param res
*/
export const createAPIKeyData = async (req: Request, res: Response) => {
const {
body: {
name
}
} = await validateRequest(reqValidator.CreateAPIKeyV3, req);
const apiKeyData = await new APIKeyDataV2({
name,
user: req.user._id,
usageCount: 0,
}).save();
const apiKey = createToken({
payload: {
authTokenType: AuthTokenType.API_KEY,
apiKeyDataId: apiKeyData._id.toString(),
userId: req.user._id.toString()
},
secret: await getAuthSecret()
});
return res.status(200).send({
apiKeyData,
apiKey
});
}
/**
* Update API key data v2 with id [apiKeyDataId]
* @param req
* @param res
*/
export const updateAPIKeyData = async (req: Request, res: Response) => {
const {
params: { apiKeyDataId },
body: {
name,
}
} = await validateRequest(reqValidator.UpdateAPIKeyV3, req);
const apiKeyData = await APIKeyDataV2.findOneAndUpdate(
{
_id: new Types.ObjectId(apiKeyDataId),
user: req.user._id
},
{
name
},
{
new: true
}
);
if (!apiKeyData) throw BadRequestError({
message: "Failed to update API key"
});
return res.status(200).send({
apiKeyData
});
}
/**
* Delete API key data v2 with id [apiKeyDataId]
* @param req
* @param res
*/
export const deleteAPIKeyData = async (req: Request, res: Response) => {
const {
params: { apiKeyDataId }
} = await validateRequest(reqValidator.DeleteAPIKeyV3, req);
const apiKeyData = await APIKeyDataV2.findOneAndDelete({
_id: new Types.ObjectId(apiKeyDataId),
user: req.user._id
});
if (!apiKeyData) throw BadRequestError({
message: "Failed to delete API key"
});
return res.status(200).send({
apiKeyData
});
}

View File

@ -1,5 +1,7 @@
import * as serviceTokenDataController from "./serviceTokenDataController"; import * as serviceTokenDataController from "./serviceTokenDataController";
import * as apiKeyDataController from "./apiKeyDataController";
export { export {
serviceTokenDataController serviceTokenDataController,
apiKeyDataController
} }

View File

@ -1,3 +1,4 @@
import jwt from "jsonwebtoken";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import {
@ -7,13 +8,11 @@ import {
ServiceTokenDataV3Key, ServiceTokenDataV3Key,
Workspace Workspace
} from "../../../models"; } from "../../../models";
import { import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
IServiceTokenV3Scope,
IServiceTokenV3TrustedIp
} from "../../../models/serviceTokenDataV3";
import { import {
ActorType, ActorType,
EventType EventType,
Role
} from "../../models"; } from "../../models";
import { validateRequest } from "../../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/serviceTokenDataV3"; import * as reqValidator from "../../../validation/serviceTokenDataV3";
@ -21,16 +20,17 @@ import { createToken } from "../../../helpers/auth";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getAuthDataProjectPermissions
} from "../../services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError, ResourceNotFoundError } from "../../../utils/errors"; import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors";
import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
import { EEAuditLogService, EELicenseService } from "../../services"; import { EEAuditLogService, EELicenseService } from "../../services";
import { getJwtServiceTokenSecret } from "../../../config"; import { getAuthSecret } from "../../../config";
import { ADMIN, AuthTokenType, CUSTOM, MEMBER, VIEWER } from "../../../variables";
/** /**
* Return project key for service token * Return project key for service token V3
* @param req * @param req
* @param res * @param res
*/ */
@ -57,7 +57,100 @@ export const getServiceTokenDataKey = async (req: Request, res: Response) => {
} }
/** /**
* Create service token data * Return access and refresh token as per refresh operation
* @param req
* @param res
*/
export const refreshToken = async (req: Request, res: Response) => {
const {
body: {
refresh_token
}
} = await validateRequest(reqValidator.RefreshTokenV3, req);
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
jwt.verify(refresh_token, await getAuthSecret())
);
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_REFRESH_TOKEN) throw UnauthorizedRequestError();
let serviceTokenData = await ServiceTokenDataV3.findOne({
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
isActive: true
});
if (!serviceTokenData) throw UnauthorizedRequestError();
if (decodedToken.tokenVersion !== serviceTokenData.tokenVersion) {
// raise alarm
throw UnauthorizedRequestError();
}
const response: {
refresh_token?: string;
access_token: string;
expires_in: number;
token_type: string;
} = {
refresh_token,
access_token: "",
expires_in: 0,
token_type: "Bearer"
};
if (serviceTokenData.isRefreshTokenRotationEnabled) {
serviceTokenData = await ServiceTokenDataV3.findByIdAndUpdate(
serviceTokenData._id,
{
$inc: {
tokenVersion: 1
}
},
{
new: true
}
);
if (!serviceTokenData) throw BadRequestError();
response.refresh_token = createToken({
payload: {
serviceTokenDataId: serviceTokenData._id.toString(),
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
tokenVersion: serviceTokenData.tokenVersion
},
secret: await getAuthSecret()
});
}
response.access_token = createToken({
payload: {
serviceTokenDataId: serviceTokenData._id.toString(),
authTokenType: AuthTokenType.SERVICE_ACCESS_TOKEN,
tokenVersion: serviceTokenData.tokenVersion
},
expiresIn: serviceTokenData.accessTokenTTL,
secret: await getAuthSecret()
});
response.expires_in = serviceTokenData.accessTokenTTL;
await ServiceTokenDataV3.findByIdAndUpdate(
serviceTokenData._id,
{
refreshTokenLastUsed: new Date(),
$inc: { refreshTokenUsageCount: 1 }
},
{
new: true
}
);
return res.status(200).send(response);
}
/**
* Create service token data V3
* @param req * @param req
* @param res * @param res
* @returns * @returns
@ -68,14 +161,20 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
name, name,
workspaceId, workspaceId,
publicKey, publicKey,
scopes, role,
trustedIps, trustedIps,
expiresIn, expiresIn,
accessTokenTTL,
isRefreshTokenRotationEnabled,
encryptedKey, // for ServiceTokenDataV3Key encryptedKey, // for ServiceTokenDataV3Key
nonce // for ServiceTokenDataV3Key nonce, // for ServiceTokenDataV3Key
} }
} = await validateRequest(reqValidator.CreateServiceTokenV3, req); } = await validateRequest(reqValidator.CreateServiceTokenV3, req);
const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); const { permission } = await getAuthDataProjectPermissions({
authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId)
});
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.ServiceTokens ProjectPermissionSub.ServiceTokens
@ -84,6 +183,19 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
const workspace = await Workspace.findById(workspaceId); const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw BadRequestError({ message: "Workspace not found" }); if (!workspace) throw BadRequestError({ message: "Workspace not found" });
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
let customRole;
if (isCustomRole) {
customRole = await Role.findOne({
slug: role,
isOrgRole: false,
workspace: workspace._id
});
if (!customRole) throw BadRequestError({ message: "Role not found" });
}
const plan = await EELicenseService.getPlan(workspace.organization); const plan = await EELicenseService.getPlan(workspace.organization);
// validate trusted ips // validate trusted ips
@ -118,11 +230,16 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
user, user,
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
publicKey, publicKey,
usageCount: 0, refreshTokenUsageCount: 0,
accessTokenUsageCount: 0,
tokenVersion: 1,
trustedIps: reformattedTrustedIps, trustedIps: reformattedTrustedIps,
scopes, role: isCustomRole ? CUSTOM : role,
customRole,
isActive, isActive,
expiresAt expiresAt,
accessTokenTTL,
isRefreshTokenRotationEnabled
}).save(); }).save();
await new ServiceTokenDataV3Key({ await new ServiceTokenDataV3Key({
@ -133,22 +250,23 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
}).save(); }).save();
const token = createToken({ const refreshToken = createToken({
payload: { payload: {
_id: serviceTokenData._id.toString() serviceTokenDataId: serviceTokenData._id.toString(),
authTokenType: AuthTokenType.SERVICE_REFRESH_TOKEN,
tokenVersion: serviceTokenData.tokenVersion
}, },
expiresIn, secret: await getAuthSecret()
secret: await getJwtServiceTokenSecret()
}); });
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
req.authData, req.authData,
{ {
type: EventType.CREATE_SERVICE_TOKEN_V3, type: EventType.CREATE_SERVICE_TOKEN_V3, // TODO: update
metadata: { metadata: {
name, name,
isActive, isActive,
scopes: scopes as Array<IServiceTokenV3Scope>, role,
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>, trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt expiresAt
} }
@ -160,12 +278,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
return res.status(200).send({ return res.status(200).send({
serviceTokenData, serviceTokenData,
serviceToken: `stv3.${token}` refreshToken
}); });
} }
/** /**
* Update service token data with id [serviceTokenDataId] * Update service token V3 data with id [serviceTokenDataId]
* @param req * @param req
* @param res * @param res
* @returns * @returns
@ -176,9 +294,11 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
body: { body: {
name, name,
isActive, isActive,
scopes, role,
trustedIps, trustedIps,
expiresIn expiresIn,
accessTokenTTL,
isRefreshTokenRotationEnabled
} }
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req); } = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
@ -187,10 +307,10 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
message: "Service token not found" message: "Service token not found"
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
serviceTokenData.workspace.toString() workspaceId: serviceTokenData.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit, ProjectPermissionActions.Edit,
@ -200,6 +320,20 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
const workspace = await Workspace.findById(serviceTokenData.workspace); const workspace = await Workspace.findById(serviceTokenData.workspace);
if (!workspace) throw BadRequestError({ message: "Workspace not found" }); if (!workspace) throw BadRequestError({ message: "Workspace not found" });
let customRole;
if (role) {
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role);
if (isCustomRole) {
customRole = await Role.findOne({
slug: role,
isOrgRole: false,
workspace: workspace._id
});
if (!customRole) throw BadRequestError({ message: "Role not found" });
}
}
const plan = await EELicenseService.getPlan(workspace.organization); const plan = await EELicenseService.getPlan(workspace.organization);
// validate trusted ips // validate trusted ips
@ -231,15 +365,25 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
{ {
name, name,
isActive, isActive,
scopes, role: customRole ? CUSTOM : role,
...(customRole ? {
customRole
} : {}),
...(role && !customRole ? { // non-custom role
$unset: {
customRole: 1
}
} : {}),
trustedIps: reformattedTrustedIps, trustedIps: reformattedTrustedIps,
expiresAt expiresAt,
accessTokenTTL,
isRefreshTokenRotationEnabled
}, },
{ {
new: true new: true
} }
); );
if (!serviceTokenData) throw BadRequestError({ if (!serviceTokenData) throw BadRequestError({
message: "Failed to update service token" message: "Failed to update service token"
}); });
@ -251,7 +395,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
metadata: { metadata: {
name: serviceTokenData.name, name: serviceTokenData.name,
isActive, isActive,
scopes: scopes as Array<IServiceTokenV3Scope>, role,
trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>, trustedIps: reformattedTrustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt expiresAt
} }
@ -282,10 +426,10 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
message: "Service token not found" message: "Service token not found"
}); });
const { permission } = await getUserProjectPermissions( const { permission } = await getAuthDataProjectPermissions({
req.user._id, authData: req.authData,
serviceTokenData.workspace.toString() workspaceId: serviceTokenData.workspace
); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
@ -309,7 +453,7 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => {
metadata: { metadata: {
name: serviceTokenData.name, name: serviceTokenData.name,
isActive: serviceTokenData.isActive, isActive: serviceTokenData.isActive,
scopes: serviceTokenData.scopes as Array<IServiceTokenV3Scope>, role: serviceTokenData.role,
trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>, trustedIps: serviceTokenData.trustedIps as Array<IServiceTokenV3TrustedIp>,
expiresAt: serviceTokenData.expiresAt expiresAt: serviceTokenData.expiresAt
} }

View File

@ -1,195 +0,0 @@
import { Types } from "mongoose";
import { Action } from "../models";
import {
getLatestNSecretSecretVersionIds,
getLatestSecretVersionIds,
} from "../helpers/secretVersion";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
} from "../../variables";
/**
* Create an (audit) action for updating secrets
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {Types.ObjectId} obj.secretIds - ids of relevant secrets
* @returns {Action} action - new action
*/
const createActionUpdateSecret = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
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;
}
/**
* Create an (audit) action for creating, reading, and deleting
* secrets
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {Types.ObjectId} obj.secretIds - ids of relevant secrets
* @returns {Action} action - new action
*/
const createActionSecret = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId: Types.ObjectId;
secretIds: Types.ObjectId[];
}) => {
// 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 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 createActionClient = ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
}) => {
const action = new Action({
name,
user: userId,
serviceAccount: serviceAccountId,
serviceTokenData: serviceTokenDataId,
}).save();
return action;
}
/**
* Create an (audit) action.
* @param {Object} obj
* @param {Object} obj.name - name of action
* @param {Types.ObjectId} obj.userId - id of user associated with action
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with action
* @param {Types.ObjectId[]} obj.secretIds - ids of secrets associated with action
*/
const createActionHelper = async ({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
secretIds?: Types.ObjectId[];
}) => {
let 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,
};

View File

@ -1,50 +0,0 @@
import { Types } from "mongoose";
import {
IAction,
Log,
} from "../models";
/**
* Create an (audit) log
* @param {Object} obj
* @param {Types.ObjectId} obj.userId - id of user associated with the log
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the log
* @param {IAction[]} obj.actions - actions to include in log
* @param {String} obj.channel - channel (web/cli/auto) associated with the log
* @param {String} obj.ipAddress - ip address associated with the log
* @returns {Log} log - new audit log
*/
const createLogHelper = async ({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
actions,
channel,
ipAddress,
}: {
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
actions: IAction[];
channel: string;
ipAddress: string;
}) => {
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,
}

View File

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

View File

@ -1,43 +0,0 @@
import { NextFunction, Request, Response } from "express";
import { SecretSnapshotNotFoundError } from "../../utils/errors";
import { SecretSnapshot } from "../models";
import {
validateMembership,
} from "../../helpers/membership";
/**
* Validate if user on request has proper membership for secret snapshot
* @param {Object} obj
* @param {String[]} obj.acceptedRoles - accepted workspace roles
* @param {String[]} obj.acceptedStatuses - accepted workspace statuses
* @param {String[]} obj.location - location of [workspaceId] on request (e.g. params, body) for parsing
*/
const requireSecretSnapshotAuth = ({
acceptedRoles,
}: {
acceptedRoles: Array<"admin" | "member">;
}) => {
return async (req: Request, res: Response, next: NextFunction) => {
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();
}
}
export default requireSecretSnapshotAuth;

View File

@ -1,69 +0,0 @@
import { Schema, Types, model } from "mongoose";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_READ_SECRETS,
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[]
}
}
const actionSchema = new Schema<IAction>(
{
name: {
type: String,
required: true,
enum: [
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS,
],
},
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
ref: "ServiceTokenData",
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
},
payload: {
secretVersions: [{
oldSecretVersion: {
type: Schema.Types.ObjectId,
ref: "SecretVersion",
},
newSecretVersion: {
type: Schema.Types.ObjectId,
ref: "SecretVersion",
},
}],
},
}, {
timestamps: true,
}
);
export const Action = model<IAction>("Action", actionSchema);

View File

@ -1,76 +1,70 @@
import { Schema, Types, model } from "mongoose"; import { Schema, Types, model } from "mongoose";
import { import { ActorType, EventType, UserAgentType } from "./enums";
ActorType, import { Actor, Event } from "./types";
EventType,
UserAgentType
} from "./enums";
import {
Actor,
Event
} from "./types";
export interface IAuditLog { export interface IAuditLog {
actor: Actor; actor: Actor;
organization: Types.ObjectId; organization: Types.ObjectId;
workspace: Types.ObjectId; workspace: Types.ObjectId;
ipAddress: string; ipAddress: string;
event: Event; event: Event;
userAgent: string; userAgent: string;
userAgentType: UserAgentType; userAgentType: UserAgentType;
expiresAt: Date; expiresAt: Date;
} }
const auditLogSchema = new Schema<IAuditLog>( const auditLogSchema = new Schema<IAuditLog>(
{ {
actor: { actor: {
type: { type: {
type: String, type: String,
enum: ActorType, enum: ActorType,
required: true required: true
}, },
metadata: { metadata: {
type: Schema.Types.Mixed type: Schema.Types.Mixed
} }
},
organization: {
type: Schema.Types.ObjectId,
required: false
},
workspace: {
type: Schema.Types.ObjectId,
required: false
},
ipAddress: {
type: String,
required: true
},
event: {
type: {
type: String,
enum: EventType,
required: true
},
metadata: {
type: Schema.Types.Mixed
}
},
userAgent: {
type: String,
required: true
},
userAgentType: {
type: String,
enum: UserAgentType,
required: true
},
expiresAt: {
type: Date,
expires: 0
}
}, },
{ organization: {
timestamps: true type: Schema.Types.ObjectId,
required: false
},
workspace: {
type: Schema.Types.ObjectId,
required: false,
index: true
},
ipAddress: {
type: String,
required: true
},
event: {
type: {
type: String,
enum: EventType,
required: true
},
metadata: {
type: Schema.Types.Mixed
}
},
userAgent: {
type: String,
required: true
},
userAgentType: {
type: String,
enum: UserAgentType,
required: true
},
expiresAt: {
type: Date,
expires: 0
} }
},
{
timestamps: true
}
); );
export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema); export const AuditLog = model<IAuditLog>("AuditLog", auditLogSchema);

View File

@ -1,7 +1,8 @@
export enum ActorType { export enum ActorType {
USER = "user", USER = "user",
SERVICE = "service", SERVICE = "service",
SERVICE_V3 = "service-v3" SERVICE_V3 = "service-v3",
// Machine = "machine"
} }
export enum UserAgentType { export enum UserAgentType {
@ -38,6 +39,7 @@ export enum EventType {
UPDATE_ENVIRONMENT = "update-environment", UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment", DELETE_ENVIRONMENT = "delete-environment",
ADD_WORKSPACE_MEMBER = "add-workspace-member", ADD_WORKSPACE_MEMBER = "add-workspace-member",
ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members",
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member", REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
CREATE_FOLDER = "create-folder", CREATE_FOLDER = "create-folder",
UPDATE_FOLDER = "update-folder", UPDATE_FOLDER = "update-folder",

View File

@ -1,11 +1,5 @@
import { import { ActorType, EventType } from "./enums";
ActorType, import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3";
EventType
} from "./enums";
import {
IServiceTokenV3Scope,
IServiceTokenV3TrustedIp
} from "../../../models/serviceTokenDataV3";
interface UserActorMetadata { interface UserActorMetadata {
userId: string; userId: string;
@ -28,14 +22,15 @@ export interface ServiceActor {
} }
export interface ServiceActorV3 { export interface ServiceActorV3 {
type: ActorType.SERVICE_V3; type: ActorType.SERVICE_V3;
metadata: ServiceActorMetadata; metadata: ServiceActorMetadata;
} }
export type Actor = // export interface MachineActor {
| UserActor // type: ActorType.Machine;
| ServiceActor // }
| ServiceActorV3;
export type Actor = UserActor | ServiceActor | ServiceActorV3;
interface GetSecretsEvent { interface GetSecretsEvent {
type: EventType.GET_SECRETS; type: EventType.GET_SECRETS;
@ -226,36 +221,36 @@ interface DeleteServiceTokenEvent {
} }
interface CreateServiceTokenV3Event { interface CreateServiceTokenV3Event {
type: EventType.CREATE_SERVICE_TOKEN_V3; type: EventType.CREATE_SERVICE_TOKEN_V3;
metadata: { metadata: {
name: string; name: string;
isActive: boolean; isActive: boolean;
scopes: Array<IServiceTokenV3Scope>; role: string;
trustedIps: Array<IServiceTokenV3TrustedIp>; trustedIps: Array<IServiceTokenV3TrustedIp>;
expiresAt?: Date; expiresAt?: Date;
} };
} }
interface UpdateServiceTokenV3Event { interface UpdateServiceTokenV3Event {
type: EventType.UPDATE_SERVICE_TOKEN_V3; type: EventType.UPDATE_SERVICE_TOKEN_V3;
metadata: { metadata: {
name?: string; name?: string;
isActive?: boolean; isActive?: boolean;
scopes?: Array<IServiceTokenV3Scope>; role?: string;
trustedIps?: Array<IServiceTokenV3TrustedIp>; trustedIps?: Array<IServiceTokenV3TrustedIp>;
expiresAt?: Date; expiresAt?: Date;
} };
} }
interface DeleteServiceTokenV3Event { interface DeleteServiceTokenV3Event {
type: EventType.DELETE_SERVICE_TOKEN_V3; type: EventType.DELETE_SERVICE_TOKEN_V3;
metadata: { metadata: {
name: string; name: string;
isActive: boolean; isActive: boolean;
scopes: Array<IServiceTokenV3Scope>; role: string;
expiresAt?: Date; expiresAt?: Date;
trustedIps: Array<IServiceTokenV3TrustedIp>; trustedIps: Array<IServiceTokenV3TrustedIp>;
} };
} }
interface CreateEnvironmentEvent { interface CreateEnvironmentEvent {
@ -292,6 +287,14 @@ interface AddWorkspaceMemberEvent {
}; };
} }
interface AddBatchWorkspaceMemberEvent {
type: EventType.ADD_BATCH_WORKSPACE_MEMBER;
metadata: Array<{
userId: string;
email: string;
}>;
}
interface RemoveWorkspaceMemberEvent { interface RemoveWorkspaceMemberEvent {
type: EventType.REMOVE_WORKSPACE_MEMBER; type: EventType.REMOVE_WORKSPACE_MEMBER;
metadata: { metadata: {
@ -427,15 +430,15 @@ interface UpdateUserRole {
} }
interface UpdateUserDeniedPermissions { interface UpdateUserDeniedPermissions {
type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS, type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS;
metadata: { metadata: {
userId: string; userId: string;
email: string; email: string;
deniedPermissions: { deniedPermissions: {
environmentSlug: string; environmentSlug: string;
ability: string; ability: string;
}[] }[];
} };
} }
interface SecretApprovalMerge { interface SecretApprovalMerge {
type: EventType.SECRET_APPROVAL_MERGED; type: EventType.SECRET_APPROVAL_MERGED;
@ -499,6 +502,7 @@ export type Event =
| UpdateEnvironmentEvent | UpdateEnvironmentEvent
| DeleteEnvironmentEvent | DeleteEnvironmentEvent
| AddWorkspaceMemberEvent | AddWorkspaceMemberEvent
| AddBatchWorkspaceMemberEvent
| RemoveWorkspaceMemberEvent | RemoveWorkspaceMemberEvent
| CreateFolderEvent | CreateFolderEvent
| UpdateFolderEvent | UpdateFolderEvent

View File

@ -1,9 +1,7 @@
export * from "./secretSnapshot"; export * from "./secretSnapshot";
export * from "./secretVersion"; export * from "./secretVersion";
export * from "./folderVersion"; export * from "./folderVersion";
export * from "./log";
export * from "./role"; export * from "./role";
export * from "./action";
export * from "./ssoConfig"; export * from "./ssoConfig";
export * from "./trustedIp"; export * from "./trustedIp";
export * from "./auditLog"; export * from "./auditLog";

View File

@ -1,72 +0,0 @@
import { Schema, Types, model } from "mongoose";
import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_READ_SECRETS,
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[];
channel: string;
ipAddress?: string;
}
const logSchema = new Schema<ILog>(
{
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
serviceAccount: {
type: Schema.Types.ObjectId,
ref: "ServiceAccount",
},
serviceTokenData: {
type: Schema.Types.ObjectId,
ref: "ServiceTokenData",
},
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace",
},
actionNames: {
type: [String],
enum: [
ACTION_LOGIN,
ACTION_LOGOUT,
ACTION_ADD_SECRETS,
ACTION_UPDATE_SECRETS,
ACTION_READ_SECRETS,
ACTION_DELETE_SECRETS,
],
required: true,
},
actions: [{
type: Schema.Types.ObjectId,
ref: "Action",
required: true,
}],
channel: {
type: String,
enum: ["web", "cli", "auto", "k8-operator", "other"],
required: true,
},
ipAddress: {
type: String,
},
},
{
timestamps: true,
}
);
export const Log = model<ILog>("Log", logSchema);

View File

@ -1,8 +0,0 @@
import express from "express";
const router = express.Router();
import { actionController } from "../../controllers/v1";
// TODO: put into action controller
router.get("/:actionId", actionController.getAction);
export default router;

View File

@ -4,12 +4,13 @@ import organizations from "./organizations";
import sso from "./sso"; import sso from "./sso";
import users from "./users"; import users from "./users";
import workspace from "./workspace"; import workspace from "./workspace";
import action from "./action";
import cloudProducts from "./cloudProducts"; import cloudProducts from "./cloudProducts";
import secretScanning from "./secretScanning"; import secretScanning from "./secretScanning";
import roles from "./role"; import roles from "./role";
import secretApprovalPolicy from "./secretApprovalPolicy"; import secretApprovalPolicy from "./secretApprovalPolicy";
import secretApprovalRequest from "./secretApprovalRequest"; import secretApprovalRequest from "./secretApprovalRequest";
import secretRotationProvider from "./secretRotationProvider";
import secretRotation from "./secretRotation";
export { export {
secret, secret,
@ -18,10 +19,11 @@ export {
sso, sso,
users, users,
workspace, workspace,
action,
cloudProducts, cloudProducts,
secretScanning, secretScanning,
roles, roles,
secretApprovalPolicy, secretApprovalPolicy,
secretApprovalRequest secretApprovalRequest,
secretRotationProvider,
secretRotation
}; };

View File

@ -0,0 +1,41 @@
import express from "express";
import { AuthMode } from "../../../variables";
import { requireAuth } from "../../../middleware";
import { secretRotationController } from "../../controllers/v1";
const router = express.Router();
router.post(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretRotationController.createSecretRotation
);
router.post(
"/restart",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretRotationController.restartSecretRotations
);
router.get(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretRotationController.getSecretRotations
);
router.delete(
"/:id",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretRotationController.deleteSecretRotations
);
export default router;

View File

@ -0,0 +1,17 @@
import express from "express";
import { AuthMode } from "../../../variables";
import { requireAuth } from "../../../middleware";
import { secretRotationProviderController } from "../../controllers/v1";
const router = express.Router();
router.get(
"/:workspaceId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
secretRotationProviderController.getProviderTemplates
);
export default router;

View File

@ -28,14 +28,6 @@ router.post(
workspaceController.rollbackWorkspaceSecretSnapshot workspaceController.rollbackWorkspaceSecretSnapshot
); );
router.get(
"/:workspaceId/logs",
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
workspaceController.getWorkspaceLogs
);
router.get( router.get(
"/:workspaceId/audit-logs", "/:workspaceId/audit-logs",
requireAuth({ requireAuth({

View File

@ -0,0 +1,31 @@
import express from "express";
const router = express.Router();
import { requireAuth } from "../../../middleware";
import { AuthMode } from "../../../variables";
import { apiKeyDataController } from "../../controllers/v3";
router.post(
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
apiKeyDataController.createAPIKeyData
);
router.patch(
"/:apiKeyDataId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
apiKeyDataController.updateAPIKeyData
);
router.delete(
"/:apiKeyDataId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
apiKeyDataController.deleteAPIKeyData
);
export default router;

View File

@ -1,5 +1,7 @@
import serviceTokenData from "./serviceTokenData"; import serviceTokenData from "./serviceTokenData";
import apiKeyData from "./apiKeyData";
export { export {
serviceTokenData serviceTokenData,
apiKeyData
} }

View File

@ -7,11 +7,16 @@ import { serviceTokenDataController } from "../../controllers/v3";
router.get( router.get(
"/me/key", "/me/key",
requireAuth({ requireAuth({
acceptedAuthModes: [AuthMode.SERVICE_TOKEN_V3] acceptedAuthModes: [AuthMode.SERVICE_ACCESS_TOKEN]
}), }),
serviceTokenDataController.getServiceTokenDataKey serviceTokenDataController.getServiceTokenDataKey
); );
router.post(
"/me/token",
serviceTokenDataController.refreshToken
);
router.post( router.post(
"/", "/",
requireAuth({ requireAuth({

View File

View File

@ -0,0 +1,91 @@
import { Schema, model } from "mongoose";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8
} from "../../variables";
import { ISecretRotation } from "./types";
const secretRotationSchema = new Schema(
{
workspace: {
type: Schema.Types.ObjectId,
ref: "Workspace"
},
provider: {
type: String,
required: true
},
customProvider: {
type: Schema.Types.ObjectId,
ref: "SecretRotationProvider"
},
environment: {
type: String,
required: true
},
secretPath: {
type: String,
required: true
},
interval: {
type: Number,
required: true
},
lastRotatedAt: {
type: String
},
status: {
type: String,
enum: ["success", "failed"]
},
statusMessage: {
type: String
},
// encrypted data on input keys and secrets got
encryptedData: {
type: String,
select: false
},
encryptedDataIV: {
type: String,
select: false
},
encryptedDataTag: {
type: String,
select: false
},
algorithm: {
// the encryption algorithm used
type: String,
enum: [ALGORITHM_AES_256_GCM],
required: true,
select: false,
default: ALGORITHM_AES_256_GCM
},
keyEncoding: {
type: String,
enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64],
required: true,
select: false,
default: ENCODING_SCHEME_UTF8
},
outputs: [
{
key: {
type: String,
required: true
},
secret: {
type: Schema.Types.ObjectId,
ref: "Secret"
}
}
]
},
{
timestamps: true
}
);
export const SecretRotation = model<ISecretRotation>("SecretRotation", secretRotationSchema);

View File

@ -0,0 +1,288 @@
import Queue, { Job } from "bull";
import { client, getEncryptionKey, getRootEncryptionKey } from "../../../config";
import { BotService, EventService, TelemetryService } from "../../../services";
import { SecretRotation } from "../models";
import { rotationTemplates } from "../templates";
import {
ISecretRotationData,
ISecretRotationEncData,
ISecretRotationProviderTemplate,
TProviderFunctionTypes
} from "../types";
import {
decryptSymmetric128BitHexKeyUTF8,
encryptSymmetric128BitHexKeyUTF8
} from "../../../utils/crypto";
import { ISecret, Secret } from "../../../models";
import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8, SECRET_SHARED } from "../../../variables";
import { EESecretService } from "../../services";
import { SecretVersion } from "../../models";
import { eventPushSecrets } from "../../../events";
import { logger } from "../../../utils/logging";
import {
secretRotationPreSetFn,
secretRotationRemoveFn,
secretRotationSetFn,
secretRotationTestFn
} from "./queue.utils";
const secretRotationQueue = new Queue("secret-rotation-service", process.env.REDIS_URL as string);
secretRotationQueue.process(async (job: Job) => {
logger.info(`secretRotationQueue.process: [rotationDocument=${job.data.rotationDocId}]`);
const rotationStratDocId = job.data.rotationDocId;
const secretRotation = await SecretRotation.findById(rotationStratDocId)
.select("+encryptedData +encryptedDataTag +encryptedDataIV +keyEncoding")
.populate<{
outputs: [
{
key: string;
secret: ISecret;
}
];
}>("outputs.secret");
const infisicalRotationProvider = rotationTemplates.find(
({ name }) => name === secretRotation?.provider
);
try {
if (!infisicalRotationProvider || !secretRotation)
throw new Error("Failed to find rotation strategy");
if (secretRotation.outputs.some(({ secret }) => !secret))
throw new Error("Secrets not found in dashboard");
const workspaceId = secretRotation.workspace;
// deep copy
const provider = JSON.parse(
JSON.stringify(infisicalRotationProvider)
) as ISecretRotationProviderTemplate;
// decrypt user provided inputs for secret rotation
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey();
let decryptedData = "";
if (rootEncryptionKey && secretRotation.keyEncoding === ENCODING_SCHEME_BASE64) {
// case: encoding scheme is base64
decryptedData = client.decryptSymmetric(
secretRotation.encryptedData,
rootEncryptionKey,
secretRotation.encryptedDataIV,
secretRotation.encryptedDataTag
);
} else if (encryptionKey && secretRotation.keyEncoding === ENCODING_SCHEME_UTF8) {
// case: encoding scheme is utf8
decryptedData = decryptSymmetric128BitHexKeyUTF8({
ciphertext: secretRotation.encryptedData,
iv: secretRotation.encryptedDataIV,
tag: secretRotation.encryptedDataTag,
key: encryptionKey
});
}
const variables = JSON.parse(decryptedData) as ISecretRotationEncData;
// rotation set cycle
const newCredential: ISecretRotationData = {
inputs: variables.inputs,
outputs: {},
internal: {}
};
// special glue code for database
if (provider.template.functions.set.type === TProviderFunctionTypes.DB) {
const lastCred = variables.creds.at(-1);
if (lastCred && variables.creds.length === 1) {
newCredential.internal.username =
lastCred.internal.username === variables.inputs.username1
? variables.inputs.username2
: variables.inputs.username1;
} else {
newCredential.internal.username = lastCred
? lastCred.internal.username
: variables.inputs.username1;
}
}
if (provider.template.functions.set?.pre) {
secretRotationPreSetFn(provider.template.functions.set.pre, newCredential);
}
await secretRotationSetFn(provider.template.functions.set, newCredential);
await secretRotationTestFn(provider.template.functions.test, newCredential);
if (variables.creds.length === 2) {
const deleteCycleCred = variables.creds.pop();
if (deleteCycleCred && provider.template.functions.remove) {
const deleteCycleVar = { inputs: variables.inputs, ...deleteCycleCred };
await secretRotationRemoveFn(provider.template.functions.remove, deleteCycleVar);
}
}
variables.creds.unshift({ outputs: newCredential.outputs, internal: newCredential.internal });
const { ciphertext, iv, tag } = client.encryptSymmetric(
JSON.stringify(variables),
rootEncryptionKey
);
// save the rotation state
await SecretRotation.findByIdAndUpdate(rotationStratDocId, {
encryptedData: ciphertext,
encryptedDataIV: iv,
encryptedDataTag: tag,
status: "success",
statusMessage: "Rotated successfully",
lastRotatedAt: new Date().toUTCString()
});
const key = await BotService.getWorkspaceKeyWithBot({
workspaceId: secretRotation.workspace
});
const encryptedSecrets = secretRotation.outputs.map(({ key: outputKey, secret }) => ({
secret,
value: encryptSymmetric128BitHexKeyUTF8({
plaintext:
typeof newCredential.outputs[outputKey] === "object"
? JSON.stringify(newCredential.outputs[outputKey])
: String(newCredential.outputs[outputKey]),
key
})
}));
// now save the secret do a bulk update
// can't use the updateSecret function due to various parameter required issue
// REFACTOR(akhilmhdh): secret module should be lot more flexible. Ability to update bulk or individually by blindIndex, by id etc
await Secret.bulkWrite(
encryptedSecrets.map(({ secret, value }) => ({
updateOne: {
filter: {
workspace: workspaceId,
environment: secretRotation.environment,
_id: secret._id,
type: SECRET_SHARED
},
update: {
$inc: {
version: 1
},
secretValueCiphertext: value.ciphertext,
secretValueIV: value.iv,
secretValueTag: value.tag
}
}
}))
);
await EESecretService.addSecretVersions({
secretVersions: encryptedSecrets.map(({ secret, value }) => {
const {
_id,
version,
workspace,
type,
folder,
secretBlindIndex,
secretKeyIV,
secretKeyTag,
secretKeyCiphertext,
skipMultilineEncoding,
environment,
algorithm,
keyEncoding
} = secret;
return new SecretVersion({
secret: _id,
version: version + 1,
workspace: workspace,
type,
folder,
environment,
isDeleted: false,
secretBlindIndex: secretBlindIndex,
secretKeyCiphertext: secretKeyCiphertext,
secretKeyIV: secretKeyIV,
secretKeyTag: secretKeyTag,
secretValueCiphertext: value.ciphertext,
secretValueIV: value.iv,
secretValueTag: value.tag,
algorithm,
keyEncoding,
skipMultilineEncoding
});
})
});
// akhilmhdh: @tony need to do something about this as its depend on authData which is not possibile in here
// await EEAuditLogService.createAuditLog(
// {actor:ActorType.Machine},
// {
// type: EventType.UPDATE_SECRETS,
// metadata: {
// environment,
// secretPath,
// secrets: secretsToBeUpdated.map(({ _id, version, secretBlindIndex }) => ({
// secretId: _id.toString(),
// secretKey: secretBlindIndexToKey[secretBlindIndex || ""],
// secretVersion: version + 1
// }))
// }
// },
// {
// workspaceId
// }
// );
const folderId = encryptedSecrets?.[0]?.secret?.folder;
// (EE) take a secret snapshot
await EESecretService.takeSecretSnapshot({
workspaceId,
environment: secretRotation.environment,
folderId
});
await EventService.handleEvent({
event: eventPushSecrets({
workspaceId: secretRotation.workspace,
environment: secretRotation.environment,
secretPath: secretRotation.secretPath
})
});
const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) {
postHogClient.capture({
event: "secrets rotated",
properties: {
numberOfSecrets: encryptedSecrets.length,
environment: secretRotation.environment,
workspaceId,
folderId
}
});
}
} catch (err) {
logger.error(err);
await SecretRotation.findByIdAndUpdate(rotationStratDocId, {
status: "failed",
statusMessage: (err as Error).message,
lastRotatedAt: new Date().toUTCString()
});
}
return Promise.resolve();
});
const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000;
export const startSecretRotationQueue = async (rotationDocId: string, interval: number) => {
// when migration to bull mq just use the option immedite to trigger repeatable immediately
secretRotationQueue.add({ rotationDocId }, { jobId: rotationDocId, removeOnComplete: true });
return secretRotationQueue.add(
{ rotationDocId },
{ repeat: { every: daysToMillisecond(interval) }, jobId: rotationDocId }
);
};
export const removeSecretRotationQueue = async (rotationDocId: string, interval: number) => {
return secretRotationQueue.removeRepeatable({ every: interval * 1000, jobId: rotationDocId });
};

View File

@ -0,0 +1,179 @@
import axios from "axios";
import jmespath from "jmespath";
import { customAlphabet } from "nanoid";
import { Client as PgClient } from "pg";
import mysql from "mysql2";
import {
ISecretRotationData,
TAssignOp,
TDbProviderClients,
TDbProviderFunction,
TDirectAssignOp,
THttpProviderFunction,
TProviderFunction,
TProviderFunctionTypes
} from "../types";
const REGEX = /\${([^}]+)}/g;
const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const nanoId = customAlphabet(SLUG_ALPHABETS, 10);
export const interpolate = (data: any, getValue: (key: string) => unknown) => {
if (!data) return;
if (typeof data === "number") return data;
if (typeof data === "string") {
return data.replace(REGEX, (_a, b) => getValue(b) as string);
}
if (typeof data === "object" && Array.isArray(data)) {
data.forEach((el, index) => {
data[index] = interpolate(el, getValue);
});
}
if (typeof data === "object") {
if ((data as { ref: string })?.ref) return getValue((data as { ref: string }).ref);
const temp = data as Record<string, unknown>; // for converting ts object to record type
Object.keys(temp).forEach((key) => {
temp[key as keyof typeof temp] = interpolate(data[key as keyof typeof temp], getValue);
});
}
return data;
};
const getInterpolationValue = (variables: ISecretRotationData) => (key: string) => {
if (key.includes("|")) {
const [keyword, ...arg] = key.split("|").map((el) => el.trim());
switch (keyword) {
case "random": {
return nanoId(parseInt(arg[0], 10));
}
default: {
throw Error(`Interpolation key not found - ${key}`);
}
}
}
const [type, keyName] = key.split(".").map((el) => el.trim());
return variables[type as keyof ISecretRotationData][keyName];
};
export const secretRotationHttpFn = async (
func: THttpProviderFunction,
variables: ISecretRotationData
) => {
// string interpolation
const headers = interpolate(func.header, getInterpolationValue(variables));
const url = interpolate(func.url, getInterpolationValue(variables));
const body = interpolate(func.body, getInterpolationValue(variables));
// axios will automatically throw error if req status is not between 2xx range
return axios({ method: func.method, url, headers, data: body });
};
export const secretRotationDbFn = async (
func: TDbProviderFunction,
variables: ISecretRotationData
) => {
const { type, client, pre, ...dbConnection } = func;
const { username, password, host, database, port, query, ca } = interpolate(
dbConnection,
getInterpolationValue(variables)
);
const ssl = ca ? { rejectUnauthorized: false, ca } : undefined;
if (host === "localhost" || host === "127.0.0.1") throw new Error("Invalid db host");
if (client === TDbProviderClients.Pg) {
const pgClient = new PgClient({ user: username, password, host, database, port, ssl });
await pgClient.connect();
const res = await pgClient.query(query);
await pgClient.end();
return res.rows[0];
} else if (client === TDbProviderClients.Sql) {
const sqlClient = mysql.createPool({
user: username,
password,
host,
database,
port,
connectionLimit: 1,
ssl
});
const res = await new Promise((resolve, reject) => {
sqlClient.query(query, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
await new Promise((resolve, reject) => {
sqlClient.end(function (err) {
if (err) return reject(err);
return resolve({});
});
});
return (res as any)?.[0];
}
};
export const secretRotationPreSetFn = (
op: Record<string, TDirectAssignOp>,
variables: ISecretRotationData
) => {
const getValFn = getInterpolationValue(variables);
Object.entries(op || {}).forEach(([key, assignFn]) => {
const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string];
variables[type][keyName] = interpolate(assignFn.value, getValFn);
});
};
export const secretRotationSetFn = async (
func: TProviderFunction,
variables: ISecretRotationData
) => {
const getValFn = getInterpolationValue(variables);
// http setter
if (func.type === TProviderFunctionTypes.HTTP) {
const res = await secretRotationHttpFn(func, variables);
Object.entries(func.setter || {}).forEach(([key, assignFn]) => {
const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string];
if (assignFn.assign === TAssignOp.JmesPath) {
variables[type][keyName] = jmespath.search(res.data, assignFn.path);
} else if (assignFn.value) {
variables[type][keyName] = interpolate(assignFn.value, getValFn);
}
});
// db setter
} else if (func.type === TProviderFunctionTypes.DB) {
const data = await secretRotationDbFn(func, variables);
Object.entries(func.setter || {}).forEach(([key, assignFn]) => {
const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string];
if (assignFn.assign === TAssignOp.JmesPath) {
if (typeof data === "object") {
variables[type][keyName] = jmespath.search(data, assignFn.path);
}
} else if (assignFn.value) {
variables[type][keyName] = interpolate(assignFn.value, getValFn);
}
});
}
};
export const secretRotationTestFn = async (
func: TProviderFunction,
variables: ISecretRotationData
) => {
if (func.type === TProviderFunctionTypes.HTTP) {
await secretRotationHttpFn(func, variables);
} else if (func.type === TProviderFunctionTypes.DB) {
await secretRotationDbFn(func, variables);
}
};
export const secretRotationRemoveFn = async (
func: TProviderFunction,
variables: ISecretRotationData
) => {
if (!func) return;
if (func.type === TProviderFunctionTypes.HTTP) {
// string interpolation
return await secretRotationHttpFn(func, variables);
}
};

View File

@ -0,0 +1,130 @@
import { ISecretRotationEncData, TCreateSecretRotation, TGetProviderTemplates } from "./types";
import { rotationTemplates } from "./templates";
import { SecretRotation } from "./models";
import { client, getEncryptionKey, getRootEncryptionKey } from "../../config";
import { BadRequestError } from "../../utils/errors";
import Ajv from "ajv";
import { removeSecretRotationQueue, startSecretRotationQueue } from "./queue/queue";
import {
ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8
} from "../../variables";
import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto";
const ajv = new Ajv({ strict: false });
export const getProviderTemplate = async ({ workspaceId }: TGetProviderTemplates) => {
return {
custom: [],
providers: rotationTemplates
};
};
export const createSecretRotation = async ({
workspaceId,
secretPath,
environment,
provider,
interval,
inputs,
outputs
}: TCreateSecretRotation) => {
const rotationTemplate = rotationTemplates.find(({ name }) => name === provider);
if (!rotationTemplate) throw BadRequestError({ message: "Provider not found" });
const formattedInputs: Record<string, unknown> = {};
Object.entries(inputs).forEach(([key, value]) => {
const type = rotationTemplate.template.inputs.properties[key].type;
if (type === "string") {
formattedInputs[key] = value;
return;
}
if (type === "integer") {
formattedInputs[key] = parseInt(value as string, 10);
return;
}
formattedInputs[key] = JSON.parse(value as string);
});
// ensure input one follows the correct schema
const valid = ajv.validate(rotationTemplate.template.inputs, formattedInputs);
if (!valid) {
throw BadRequestError({ message: ajv.errors?.[0].message });
}
const encData: Partial<ISecretRotationEncData> = {
inputs: formattedInputs,
creds: []
};
const secretRotation = new SecretRotation({
workspace: workspaceId,
provider,
environment,
secretPath,
interval,
outputs: Object.entries(outputs).map(([key, secret]) => ({ key, secret }))
});
const encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey();
if (rootEncryptionKey) {
const { ciphertext, iv, tag } = client.encryptSymmetric(
JSON.stringify(encData),
rootEncryptionKey
);
secretRotation.encryptedDataIV = iv;
secretRotation.encryptedDataTag = tag;
secretRotation.encryptedData = ciphertext;
secretRotation.algorithm = ALGORITHM_AES_256_GCM;
secretRotation.keyEncoding = ENCODING_SCHEME_BASE64;
} else if (encryptionKey) {
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
plaintext: JSON.stringify(encData),
key: encryptionKey
});
secretRotation.encryptedDataIV = iv;
secretRotation.encryptedDataTag = tag;
secretRotation.encryptedData = ciphertext;
secretRotation.algorithm = ALGORITHM_AES_256_GCM;
secretRotation.keyEncoding = ENCODING_SCHEME_UTF8;
}
await secretRotation.save();
await startSecretRotationQueue(secretRotation._id.toString(), interval);
return secretRotation;
};
export const deleteSecretRotation = async ({ id }: { id: string }) => {
const doc = await SecretRotation.findByIdAndRemove(id);
if (!doc) throw BadRequestError({ message: "Rotation not found" });
await removeSecretRotationQueue(doc._id.toString(), doc.interval);
return doc;
};
export const restartSecretRotation = async ({ id }: { id: string }) => {
const secretRotation = await SecretRotation.findById(id);
if (!secretRotation) throw BadRequestError({ message: "Rotation not found" });
await removeSecretRotationQueue(secretRotation._id.toString(), secretRotation.interval);
await startSecretRotationQueue(secretRotation._id.toString(), secretRotation.interval);
return secretRotation;
};
export const getSecretRotationById = async ({ id }: { id: string }) => {
const doc = await SecretRotation.findById(id);
if (!doc) throw BadRequestError({ message: "Rotation not found" });
return doc;
};
export const getSecretRotationOfWorkspace = async (workspaceId: string) => {
const secretRotations = await SecretRotation.find({
workspace: workspaceId
}).populate("outputs.secret");
return secretRotations;
};

View File

@ -0,0 +1,28 @@
import { ISecretRotationProviderTemplate } from "../types";
import { MYSQL_TEMPLATE } from "./mysql";
import { POSTGRES_TEMPLATE } from "./postgres";
import { SENDGRID_TEMPLATE } from "./sendgrid";
export const rotationTemplates: ISecretRotationProviderTemplate[] = [
{
name: "sendgrid",
title: "Twilio Sendgrid",
image: "sendgrid.png",
description: "Rotate Twilio Sendgrid API keys",
template: SENDGRID_TEMPLATE
},
{
name: "postgres",
title: "PostgreSQL",
image: "postgres.png",
description: "Rotate PostgreSQL/CockroachDB user credentials",
template: POSTGRES_TEMPLATE
},
{
name: "mysql",
title: "MySQL",
image: "mysql.png",
description: "Rotate MySQL@7/MariaDB user credentials",
template: MYSQL_TEMPLATE
}
];

View File

@ -0,0 +1,83 @@
import { TAssignOp, TDbProviderClients, TProviderFunctionTypes } from "../types";
export const MYSQL_TEMPLATE = {
inputs: {
type: "object" as const,
properties: {
admin_username: { type: "string" as const },
admin_password: { type: "string" as const },
host: { type: "string" as const },
database: { type: "string" as const },
port: { type: "integer" as const, default: "3306" },
username1: {
type: "string",
default: "infisical-sql-user1",
desc: "This user must be created in your database"
},
username2: {
type: "string",
default: "infisical-sql-user2",
desc: "This user must be created in your database"
},
ca: { type: "string", desc: "SSL certificate for db auth(string)" }
},
required: [
"admin_username",
"admin_password",
"host",
"database",
"username1",
"username2",
"port"
],
additionalProperties: false
},
outputs: {
db_username: { type: "string" },
db_password: { type: "string" }
},
internal: {
rotated_password: { type: "string" },
username: { type: "string" }
},
functions: {
set: {
type: TProviderFunctionTypes.DB as const,
client: TDbProviderClients.Sql,
username: "${inputs.admin_username}",
password: "${inputs.admin_password}",
host: "${inputs.host}",
database: "${inputs.database}",
port: "${inputs.port}",
ca: "${inputs.ca}",
query: "ALTER USER ${internal.username} IDENTIFIED BY '${internal.rotated_password}'",
setter: {
"outputs.db_username": {
assign: TAssignOp.Direct as const,
value: "${internal.username}"
},
"outputs.db_password": {
assign: TAssignOp.Direct as const,
value: "${internal.rotated_password}"
}
},
pre: {
"internal.rotated_password": {
assign: TAssignOp.Direct as const,
value: "${random | 32}"
}
}
},
test: {
type: TProviderFunctionTypes.DB as const,
client: TDbProviderClients.Sql,
username: "${internal.username}",
password: "${internal.rotated_password}",
host: "${inputs.host}",
database: "${inputs.database}",
port: "${inputs.port}",
ca: "${inputs.ca}",
query: "SELECT NOW()"
}
}
};

View File

@ -0,0 +1,83 @@
import { TAssignOp, TDbProviderClients, TProviderFunctionTypes } from "../types";
export const POSTGRES_TEMPLATE = {
inputs: {
type: "object" as const,
properties: {
admin_username: { type: "string" as const },
admin_password: { type: "string" as const },
host: { type: "string" as const },
database: { type: "string" as const },
port: { type: "integer" as const, default: "5432" },
username1: {
type: "string",
default: "infisical-pg-user1",
desc: "This user must be created in your database"
},
username2: {
type: "string",
default: "infisical-pg-user2",
desc: "This user must be created in your database"
},
ca: { type: "string", desc: "SSL certificate for db auth(string)" }
},
required: [
"admin_username",
"admin_password",
"host",
"database",
"username1",
"username2",
"port"
],
additionalProperties: false
},
outputs: {
db_username: { type: "string" },
db_password: { type: "string" }
},
internal: {
rotated_password: { type: "string" },
username: { type: "string" }
},
functions: {
set: {
type: TProviderFunctionTypes.DB as const,
client: TDbProviderClients.Pg,
username: "${inputs.admin_username}",
password: "${inputs.admin_password}",
host: "${inputs.host}",
database: "${inputs.database}",
port: "${inputs.port}",
ca: "${inputs.ca}",
query: "ALTER USER ${internal.username} WITH PASSWORD '${internal.rotated_password}'",
setter: {
"outputs.db_username": {
assign: TAssignOp.Direct as const,
value: "${internal.username}"
},
"outputs.db_password": {
assign: TAssignOp.Direct as const,
value: "${internal.rotated_password}"
}
},
pre: {
"internal.rotated_password": {
assign: TAssignOp.Direct as const,
value: "${random | 32}"
}
}
},
test: {
type: TProviderFunctionTypes.DB as const,
client: TDbProviderClients.Pg,
username: "${internal.username}",
password: "${internal.rotated_password}",
host: "${inputs.host}",
database: "${inputs.database}",
port: "${inputs.port}",
ca: "${inputs.ca}",
query: "SELECT NOW()"
}
}
};

View File

@ -0,0 +1,63 @@
import { TAssignOp, TProviderFunctionTypes } from "../types";
export const SENDGRID_TEMPLATE = {
inputs: {
type: "object" as const,
properties: {
admin_api_key: { type: "string" as const, desc: "Sendgrid admin api key to create new keys" },
api_key_scopes: {
type: "array",
items: { type: "string" as const },
desc: "Scopes for created tokens by rotation(Array)"
}
},
required: ["admin_api_key", "api_key_scopes"],
additionalProperties: false
},
outputs: {
api_key: { type: "string" }
},
internal: {
api_key_id: { type: "string" }
},
functions: {
set: {
type: TProviderFunctionTypes.HTTP as const,
url: "https://api.sendgrid.com/v3/api_keys",
method: "POST",
header: {
Authorization: "Bearer ${inputs.admin_api_key}"
},
body: {
name: "infisical-${random | 16}",
scopes: { ref: "inputs.api_key_scopes" }
},
setter: {
"outputs.api_key": {
assign: TAssignOp.JmesPath as const,
path: "api_key"
},
"internal.api_key_id": {
assign: TAssignOp.JmesPath as const,
path: "api_key_id"
}
}
},
remove: {
type: TProviderFunctionTypes.HTTP as const,
url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}",
header: {
Authorization: "Bearer ${inputs.admin_api_key}"
},
method: "DELETE"
},
test: {
type: TProviderFunctionTypes.HTTP as const,
url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}",
header: {
Authorization: "Bearer ${inputs.admin_api_key}"
},
method: "GET"
}
}
};

View File

@ -0,0 +1,131 @@
import { Document, Types } from "mongoose";
export interface ISecretRotation extends Document {
_id: Types.ObjectId;
name: string;
interval: number;
provider: string;
customProvider: Types.ObjectId;
workspace: Types.ObjectId;
environment: string;
secretPath: string;
outputs: Array<{
key: string;
secret: Types.ObjectId;
}>;
status?: "success" | "failed";
lastRotatedAt?: string;
statusMessage?: string;
encryptedData: string;
encryptedDataIV: string;
encryptedDataTag: string;
algorithm: string;
keyEncoding: string;
}
export type ISecretRotationEncData = {
inputs: Record<string, unknown>;
creds: Array<{
outputs: Record<string, unknown>;
internal: Record<string, unknown>;
}>;
};
export type ISecretRotationData = {
inputs: Record<string, unknown>;
outputs: Record<string, unknown>;
internal: Record<string, unknown>;
};
export type ISecretRotationProviderTemplate = {
name: string;
title: string;
image?: string;
description?: string;
template: TProviderTemplate;
};
export enum TProviderFunctionTypes {
HTTP = "http",
DB = "database"
}
export enum TDbProviderClients {
// postgres, cockroack db, amazon red shift
Pg = "pg",
// mysql and maria db
Sql = "sql"
}
export enum TAssignOp {
Direct = "direct",
JmesPath = "jmesopath"
}
export type TJmesPathAssignOp = {
assign: TAssignOp.JmesPath;
path: string;
};
export type TDirectAssignOp = {
assign: TAssignOp.Direct;
value: string;
};
export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp;
export type THttpProviderFunction = {
type: TProviderFunctionTypes.HTTP;
url: string;
method: string;
header?: Record<string, string>;
query?: Record<string, string>;
body?: Record<string, unknown>;
setter?: Record<string, TAssignFunction>;
pre?: Record<string, TDirectAssignOp>;
};
export type TDbProviderFunction = {
type: TProviderFunctionTypes.DB;
client: TDbProviderClients;
username: string;
password: string;
host: string;
database: string;
port: string;
query: string;
setter?: Record<string, TAssignFunction>;
pre?: Record<string, TDirectAssignOp>;
};
export type TProviderFunction = THttpProviderFunction | TDbProviderFunction;
export type TProviderTemplate = {
inputs: {
type: "object";
properties: Record<string, { type: string; [x: string]: unknown; desc?: string }>;
required?: string[];
};
outputs: Record<string, unknown>;
functions: {
set: TProviderFunction;
remove?: TProviderFunction;
test: TProviderFunction;
};
};
// function type args
export type TGetProviderTemplates = {
workspaceId: string;
};
export type TCreateSecretRotation = {
provider: string;
customProvider?: string;
workspaceId: string;
secretPath: string;
environment: string;
interval: number;
inputs: Record<string, unknown>;
outputs: Record<string, string>;
};

View File

@ -38,6 +38,7 @@ interface FeatureSet {
trial_end: number | null; trial_end: number | null;
has_used_trial: boolean; has_used_trial: boolean;
secretApproval: boolean; secretApproval: boolean;
secretRotation: boolean;
} }
/** /**
@ -74,7 +75,8 @@ class EELicenseService {
status: null, status: null,
trial_end: null, trial_end: null,
has_used_trial: true, has_used_trial: true,
secretApproval: false secretApproval: false,
secretRotation: true,
} }
public localFeatureSet: NodeCache; public localFeatureSet: NodeCache;

View File

@ -1,91 +0,0 @@
import { Types } from "mongoose";
import {
IAction,
} from "../models";
import {
createLogHelper,
} from "../helpers/log";
import {
createActionHelper,
} from "../helpers/action";
import EELicenseService from "./EELicenseService";
/**
* Class to handle Enterprise Edition log actions
*/
class EELogService {
/**
* Create an (audit) log
* @param {Object} obj
* @param {String} obj.userId - id of user associated with the log
* @param {String} obj.workspaceId - id of workspace associated with the log
* @param {Action} obj.actions - actions to include in log
* @param {String} obj.channel - channel (web/cli/auto) associated with the log
* @param {String} obj.ipAddress - ip address associated with the log
* @returns {Log} log - new audit log
*/
static async createLog({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
actions,
channel,
ipAddress,
}: {
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
actions: IAction[];
channel: string;
ipAddress: string;
}) {
if (!EELicenseService.isLicenseValid) return null;
return await createLogHelper({
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
actions,
channel,
ipAddress,
})
}
/**
* Create an (audit) action
* @param {Object} obj
* @param {String} obj.name - name of action
* @param {Types.ObjectId} obj.userId - id of user associated with the action
* @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the action
* @param {ObjectId[]} obj.secretIds - ids of secrets associated with the action
* @returns {Action} action - new action
*/
static async createAction({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
}: {
name: string;
userId?: Types.ObjectId;
serviceAccountId?: Types.ObjectId;
serviceTokenDataId?: Types.ObjectId;
workspaceId?: Types.ObjectId;
secretIds?: Types.ObjectId[];
}) {
return await createActionHelper({
name,
userId,
serviceAccountId,
serviceTokenDataId,
workspaceId,
secretIds,
});
}
}
export default EELogService;

View File

@ -1,3 +1,4 @@
import { Types } from "mongoose";
import { import {
AbilityBuilder, AbilityBuilder,
ForcedSubject, ForcedSubject,
@ -6,11 +7,14 @@ import {
buildMongoQueryMatcher, buildMongoQueryMatcher,
createMongoAbility createMongoAbility
} from "@casl/ability"; } from "@casl/ability";
import { Membership } from "../../models"; import { UnauthorizedRequestError } from "../../utils/errors";
import { IRole } from "../models/role";
import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors";
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js"; import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
import picomatch from "picomatch"; import picomatch from "picomatch";
import { AuthData } from "../../interfaces/middleware";
import { ActorType, IRole } from "../models";
import { Membership, ServiceTokenData, ServiceTokenDataV3 } from "../../models";
import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables";
import { checkIPAgainstBlocklist } from "../../utils/ip";
const $glob: FieldInstruction<string> = { const $glob: FieldInstruction<string> = {
type: "field", type: "field",
@ -50,7 +54,8 @@ export enum ProjectPermissionSub {
Workspace = "workspace", Workspace = "workspace",
Secrets = "secrets", Secrets = "secrets",
SecretRollback = "secret-rollback", SecretRollback = "secret-rollback",
SecretApproval = "secret-approval" SecretApproval = "secret-approval",
SecretRotation = "secret-rotation"
} }
type SubjectFields = { type SubjectFields = {
@ -74,6 +79,7 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.Settings] | [ProjectPermissionActions, ProjectPermissionSub.Settings]
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens] | [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval] | [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
| [ProjectPermissionActions, ProjectPermissionSub.SecretRotation]
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace] | [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace]
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace] | [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace]
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback] | [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
@ -92,6 +98,11 @@ const buildAdminPermission = () => {
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
@ -162,6 +173,7 @@ const buildMemberPermission = () => {
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
@ -214,6 +226,7 @@ const buildViewerPermission = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
@ -230,31 +243,89 @@ const buildViewerPermission = () => {
export const viewerProjectPermission = buildViewerPermission(); export const viewerProjectPermission = buildViewerPermission();
export const getUserProjectPermissions = async (userId: string, workspaceId: string) => { /**
// TODO(akhilmhdh): speed this up by pulling from cache later * Return permissions for user/service pertaining to workspace with id [workspaceId]
const membership = await Membership.findOne({ *
user: userId, * Note: should not rely on this function for ST V2 authorization logic
workspace: workspaceId * b/c ST V2 does not support role-based access control
}) */
.populate<{ export const getAuthDataProjectPermissions = async ({
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] }; authData,
}>("customRole") workspaceId
.exec(); }: {
authData: AuthData;
workspaceId: Types.ObjectId;
}) => {
let role: "admin" | "member" | "viewer" | "custom";
let customRole;
switch (authData.actor.type) {
case ActorType.USER: {
const membership = await Membership.findOne({
user: authData.authPayload._id,
workspace: workspaceId
})
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
if (!membership || (membership.role === "custom" && !membership.customRole)) {
throw UnauthorizedRequestError();
}
role = membership.role;
customRole = membership.customRole;
break;
}
case ActorType.SERVICE: {
const serviceTokenData = await ServiceTokenData.findById(authData.authPayload._id);
if (!serviceTokenData || !serviceTokenData.workspace.equals(workspaceId)) throw UnauthorizedRequestError();
role = "viewer";
break;
}
case ActorType.SERVICE_V3: {
const serviceTokenData = await ServiceTokenDataV3
.findById(authData.authPayload._id)
.populate<{
customRole: IRole & { permissions: RawRuleOf<MongoAbility<ProjectPermissionSet>>[] };
}>("customRole")
.exec();
if (!serviceTokenData || (serviceTokenData.role === "custom" && !serviceTokenData.customRole)) {
throw UnauthorizedRequestError();
}
if (!membership || (membership.role === "custom" && !membership.customRole)) { checkIPAgainstBlocklist({
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" }); ipAddress: authData.ipAddress,
trustedIps: serviceTokenData.trustedIps
});
role = serviceTokenData.role;
customRole = serviceTokenData.customRole;
break;
}
default:
throw UnauthorizedRequestError();
} }
if (membership.role === "admin") return { permission: adminProjectPermissions, membership }; switch (role) {
if (membership.role === "member") return { permission: memberProjectPermissions, membership }; case ADMIN:
if (membership.role === "viewer") return { permission: viewerProjectPermission, membership }; return { permission: adminProjectPermissions };
case MEMBER:
if (membership.role === "custom") { return { permission: memberProjectPermissions };
const permission = createMongoAbility<ProjectPermissionSet>(membership.customRole.permissions, { case VIEWER:
conditionsMatcher return { permission: viewerProjectPermission };
}); case CUSTOM: {
return { permission, membership }; if (!customRole) throw UnauthorizedRequestError();
return {
permission: createMongoAbility<ProjectPermissionSet>(
customRole.permissions,
{ conditionsMatcher }
)
};
}
default:
throw UnauthorizedRequestError();
} }
}
throw BadRequestError({ message: "User role not found" });
};

View File

@ -1,13 +1,11 @@
import EELicenseService from "./EELicenseService"; import EELicenseService from "./EELicenseService";
import EESecretService from "./EESecretService"; import EESecretService from "./EESecretService";
import EELogService from "./EELogService";
import EEAuditLogService from "./EEAuditLogService"; import EEAuditLogService from "./EEAuditLogService";
import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService" import GithubSecretScanningService from "./GithubSecretScanning/GithubSecretScanningService"
export { export {
EELicenseService, EELicenseService,
EESecretService, EESecretService,
EELogService,
EEAuditLogService, EEAuditLogService,
GithubSecretScanningService GithubSecretScanningService
} }

View File

@ -0,0 +1,32 @@
import { z } from "zod";
export const createSecretRotationV1 = z.object({
body: z.object({
workspaceId: z.string().trim(),
secretPath: z.string().trim(),
environment: z.string().trim(),
interval: z.number().min(1),
provider: z.string().trim(),
customProvider: z.string().trim().optional(),
inputs: z.record(z.unknown()),
outputs: z.record(z.string())
})
});
export const restartSecretRotationV1 = z.object({
body: z.object({
id: z.string().trim()
})
});
export const getSecretRotationV1 = z.object({
query: z.object({
workspaceId: z.string().trim()
})
});
export const removeSecretRotationV1 = z.object({
params: z.object({
id: z.string().trim()
})
});

View File

@ -0,0 +1,7 @@
import { z } from "zod";
export const getSecretRotationProvidersV1 = z.object({
params: z.object({
workspaceId: z.string()
})
});

View File

@ -1,360 +1,13 @@
import { Request } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import bcrypt from "bcrypt"; import { ITokenVersion, TokenVersion } from "../models";
import { import { UnauthorizedRequestError } from "../utils/errors";
APIKeyData,
ITokenVersion,
IUser,
ServiceTokenData,
ServiceTokenDataV3,
TokenVersion,
User,
} from "../models";
import {
APIKeyDataNotFoundError,
AccountNotFoundError,
BadRequestError,
ServiceTokenDataNotFoundError,
UnauthorizedRequestError,
} from "../utils/errors";
import { import {
getAuthSecret, getAuthSecret,
getJwtAuthLifetime, getJwtAuthLifetime,
getJwtRefreshLifetime, getJwtRefreshLifetime
getJwtServiceTokenSecret
} from "../config"; } from "../config";
import { import { AuthTokenType } from "../variables";
AuthMode,
AuthTokenType
} from "../variables";
import {
ServiceTokenAuthData,
ServiceTokenV3AuthData,
UserAuthData
} from "../interfaces/middleware";
import { ActorType } from "../ee/models";
import { getUserAgentType } from "../utils/posthog";
/**
*
* @param {Object} obj
* @param {Object} obj.headers - HTTP request headers object
*/
export const validateAuthMode = ({
headers,
acceptedAuthModes,
}: {
headers: { [key: string]: string | string[] | undefined },
acceptedAuthModes: AuthMode[]
}) => {
const apiKey = headers["x-api-key"];
const authHeader = headers["authorization"];
let authMode, authTokenValue;
if (apiKey === undefined && authHeader === undefined) {
// case: no auth or X-API-KEY header present
throw BadRequestError({ message: "Missing Authorization or X-API-KEY in request header." });
}
if (typeof apiKey === "string") {
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
authMode = AuthMode.API_KEY;
authTokenValue = apiKey;
}
if (typeof authHeader === "string") {
// case: treat request authentication type as via Authorization header (i.e. either JWT or service token)
const [tokenType, tokenValue] = <[string, string]>authHeader.split(" ", 2) ?? [null, null]
if (tokenType === null)
throw BadRequestError({ message: "Missing Authorization Header in the request header." });
if (tokenType.toLowerCase() !== "bearer")
throw BadRequestError({ message: `The provided authentication type '${tokenType}' is not supported.` });
if (tokenValue === null)
throw BadRequestError({ message: "Missing Authorization Body in the request header." });
const parts = tokenValue.split(".");
switch (parts[0]) {
case "st":
authMode = AuthMode.SERVICE_TOKEN;
authTokenValue = tokenValue;
break;
case "stv3":
authMode = AuthMode.SERVICE_TOKEN_V3;
authTokenValue = parts.slice(1).join(".");
break;
default:
authMode = AuthMode.JWT;
authTokenValue = tokenValue;
}
}
if (!authMode || !authTokenValue) throw BadRequestError({ message: "Missing valid Authorization or X-API-KEY in request header." });
if (!acceptedAuthModes.includes(authMode)) throw BadRequestError({ message: "The provided authentication type is not supported." });
return ({
authMode,
authTokenValue,
});
}
/**
* Return user payload corresponding to JWT token [authTokenValue]
* @param {Object} obj
* @param {String} obj.authTokenValue - JWT token value
* @returns {User} user - user corresponding to JWT token
*/
export const getAuthUserPayload = async ({
req,
authTokenValue,
}: {
req: Request,
authTokenValue: string;
}): Promise<UserAuthData> => {
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(authTokenValue, await getAuthSecret())
);
if (decodedToken.authTokenType !== AuthTokenType.ACCESS_TOKEN) throw UnauthorizedRequestError();
const user = await User.findOne({
_id: new Types.ObjectId(decodedToken.userId),
}).select("+publicKey +accessVersion");
if (!user) throw AccountNotFoundError({ message: "Failed to find user" });
if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" });
const tokenVersion = await TokenVersion.findOneAndUpdate({
_id: new Types.ObjectId(decodedToken.tokenVersionId),
user: user._id,
}, {
lastUsed: new Date(),
});
if (!tokenVersion) throw UnauthorizedRequestError({
message: "Failed to validate access token",
});
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
message: "Failed to validate access token",
});
return {
actor: {
type: ActorType.USER,
metadata: {
userId: user._id.toString(),
email: user.email
}
},
authPayload: user,
ipAddress: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgentType: getUserAgentType(req.headers["user-agent"])
}
}
/**
* Return service token data payload corresponding to service token [authTokenValue]
* @param {Object} obj
* @param {String} obj.authTokenValue - service token value
* @returns {ServiceTokenData} serviceTokenData - service token data
*/
export const getAuthSTDPayload = async ({
req,
authTokenValue,
}: {
req: Request,
authTokenValue: string;
}): Promise<ServiceTokenAuthData> => {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
const serviceTokenData = await ServiceTokenData
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
if (!serviceTokenData) {
throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
// case: service token expired
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
throw UnauthorizedRequestError({
message: "Failed to authenticate expired service token",
});
}
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({
message: "Failed to authenticate service token",
});
const serviceTokenDataToReturn = await ServiceTokenData
.findOneAndUpdate({
_id: new Types.ObjectId(TOKEN_IDENTIFIER),
}, {
lastUsed: new Date(),
}, {
new: true,
})
.select("+encryptedKey +iv +tag")
if (!serviceTokenDataToReturn) throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
return {
actor: {
type: ActorType.SERVICE,
metadata: {
serviceId: serviceTokenDataToReturn._id.toString(),
name: serviceTokenDataToReturn.name
}
},
authPayload: serviceTokenDataToReturn,
ipAddress: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgentType: getUserAgentType(req.headers["user-agent"])
}
}
/**
* Return service token data V3 payload corresponding to service token [authTokenValue]
* @param {Object} obj
* @param {String} obj.authTokenValue - service token value
* @returns {ServiceTokenData} serviceTokenData - service token data
*/
export const getAuthSTDV3Payload = async ({
req,
authTokenValue,
}: {
req: Request,
authTokenValue: string;
}): Promise<ServiceTokenV3AuthData> => {
const decodedToken = <jwt.UserIDJwtPayload>(
jwt.verify(authTokenValue, await getJwtServiceTokenSecret())
);
const serviceTokenData = await ServiceTokenDataV3.findOneAndUpdate(
{
_id: new Types.ObjectId(decodedToken._id),
isActive: true
},
{
lastUsed: new Date(),
$inc: { usageCount: 1 }
},
{
new: true
}
);
if (!serviceTokenData) {
throw UnauthorizedRequestError({
message: "Failed to authenticate"
});
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
// case: service token expired
await ServiceTokenDataV3.findByIdAndUpdate(
serviceTokenData._id,
{
isActive: false
},
{
new: true
}
);
throw UnauthorizedRequestError({
message: "Failed to authenticate",
});
}
return {
actor: {
type: ActorType.SERVICE_V3,
metadata: {
serviceId: serviceTokenData._id.toString(),
name: serviceTokenData.name
}
},
authPayload: serviceTokenData,
ipAddress: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgentType: getUserAgentType(req.headers["user-agent"])
}
}
/**
* Return API key data payload corresponding to API key [authTokenValue]
* @param {Object} obj
* @param {String} obj.authTokenValue - API key value
* @returns {APIKeyData} apiKeyData - API key data
*/
export const getAuthAPIKeyPayload = async ({
req,
authTokenValue,
}: {
req: Request,
authTokenValue: string;
}): Promise<UserAuthData> => {
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
let apiKeyData = await APIKeyData
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
.populate<{ user: IUser }>("user", "+publicKey");
if (!apiKeyData) {
throw APIKeyDataNotFoundError({ message: "Failed to find API key data" });
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
// case: API key expired
await APIKeyData.findByIdAndDelete(apiKeyData._id);
throw UnauthorizedRequestError({
message: "Failed to authenticate expired API key",
});
}
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
if (!isMatch) throw UnauthorizedRequestError({
message: "Failed to authenticate API key",
});
apiKeyData = await APIKeyData.findOneAndUpdate({
_id: new Types.ObjectId(TOKEN_IDENTIFIER),
}, {
lastUsed: new Date(),
}, {
new: true,
});
if (!apiKeyData) {
throw APIKeyDataNotFoundError({ message: "Failed to find API key data" });
}
const user = await User.findById(apiKeyData.user).select("+publicKey");
if (!user) {
throw AccountNotFoundError({
message: "Failed to find user",
});
}
return {
actor: {
type: ActorType.USER,
metadata: {
userId: user._id.toString(),
email: user.email
}
},
authPayload: user,
ipAddress: req.realIP,
userAgent: req.headers["user-agent"] ?? "",
userAgentType: getUserAgentType(req.headers["user-agent"])
}
}
/** /**
* Return newly issued (JWT) auth and refresh tokens to user with id [userId] * Return newly issued (JWT) auth and refresh tokens to user with id [userId]

View File

@ -1,5 +1,5 @@
import mongoose from "mongoose"; import mongoose from "mongoose";
import { getLogger } from "../utils/logger"; import { logger } from "../utils/logging";
/** /**
* Initialize database connection * Initialize database connection
@ -18,10 +18,10 @@ export const initDatabaseHelper = async ({
// allow empty strings to pass the required validator // allow empty strings to pass the required validator
mongoose.Schema.Types.String.checkRequired(v => typeof v === "string"); mongoose.Schema.Types.String.checkRequired(v => typeof v === "string");
(await getLogger("database")).info("Database connection established"); logger.info("Database connection established");
} catch (err) { } catch (err) {
(await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`); logger.error(err, "Unable to establish database connection");
} }
return mongoose.connection; return mongoose.connection;

View File

@ -1,4 +1,4 @@
import mongoose, { Types, mongo } from "mongoose"; import { Types } from "mongoose";
import { import {
Bot, Bot,
BotKey, BotKey,
@ -23,13 +23,11 @@ import {
Workspace Workspace
} from "../models"; } from "../models";
import { import {
Action,
AuditLog, AuditLog,
FolderVersion, FolderVersion,
GitAppInstallationSession, GitAppInstallationSession,
GitAppOrganizationInstallation, GitAppOrganizationInstallation,
GitRisks, GitRisks,
Log,
Role, Role,
SSOConfig, SSOConfig,
SecretApprovalPolicy, SecretApprovalPolicy,
@ -55,7 +53,7 @@ import {
import { import {
createBotOrg createBotOrg
} from "./botOrg"; } from "./botOrg";
import { InternalServerError, ResourceNotFoundError } from "../utils/errors"; import { ResourceNotFoundError } from "../utils/errors";
/** /**
* Create an organization with name [name] * Create an organization with name [name]
@ -111,311 +109,203 @@ export const createOrganization = async ({
* @returns * @returns
*/ */
export const deleteOrganization = async ({ export const deleteOrganization = async ({
organizationId, organizationId
existingSession
}: { }: {
organizationId: Types.ObjectId; organizationId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => { }) => {
let session; const organization = await Organization.findByIdAndDelete(
organizationId
if (existingSession) { );
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
try { if (!organization) throw ResourceNotFoundError();
const organization = await Organization.findByIdAndDelete(
organizationId, await MembershipOrg.deleteMany({
{ organization: organization._id
session });
}
await BotOrg.deleteMany({
organization: organization._id
});
await SSOConfig.deleteMany({
organization: organization._id
});
await Role.deleteMany({
organization: organization._id
});
await IncidentContactOrg.deleteMany({
organization: organization._id
});
await GitRisks.deleteMany({
organization: organization._id
});
await GitAppInstallationSession.deleteMany({
organization: organization._id
});
await GitAppOrganizationInstallation.deleteMany({
organization: organization._id
});
const workspaceIds = await Workspace.distinct("_id", {
organization: organization._id
});
await Workspace.deleteMany({
organization: organization._id
});
await Membership.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Key.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Bot.deleteMany({
workspace: {
$in: workspaceIds
}
});
await BotKey.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretBlindIndexData.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Secret.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretVersion.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretSnapshot.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretImport.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Folder.deleteMany({
workspace: {
$in: workspaceIds
}
});
await FolderVersion.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Webhook.deleteMany({
workspace: {
$in: workspaceIds
}
});
await TrustedIP.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Tag.deleteMany({
workspace: {
$in: workspaceIds
}
});
await IntegrationAuth.deleteMany({
workspace: {
$in: workspaceIds
}
});
await Integration.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceToken.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceTokenData.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceTokenDataV3.deleteMany({
workspace: {
$in: workspaceIds
}
});
await ServiceTokenDataV3Key.deleteMany({
workspace: {
$in: workspaceIds
}
});
await AuditLog.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretApprovalPolicy.deleteMany({
workspace: {
$in: workspaceIds
}
});
await SecretApprovalRequest.deleteMany({
workspace: {
$in: workspaceIds
}
});
if (organization.customerId) {
// delete from stripe here
await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}`
); );
if (!organization) throw ResourceNotFoundError();
await MembershipOrg.deleteMany({
organization: organization._id
}, {
session
});
await BotOrg.deleteMany({
organization: organization._id
}, {
session
});
await SSOConfig.deleteMany({
organization: organization._id
}, {
session
});
await Role.deleteMany({
organization: organization._id
}, {
session
});
await IncidentContactOrg.deleteMany({
organization: organization._id
}, {
session
});
await GitRisks.deleteMany({
organization: organization._id
}, {
session
});
await GitAppInstallationSession.deleteMany({
organization: organization._id
}, {
session
});
await GitAppOrganizationInstallation.deleteMany({
organization: organization._id
}, {
session
});
const workspaceIds = await Workspace.distinct("_id", {
organization: organization._id
});
await Workspace.deleteMany({
organization: organization._id
}, {
session
});
await Membership.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Key.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Bot.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await BotKey.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretBlindIndexData.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Secret.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretVersion.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretSnapshot.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretImport.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Folder.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await FolderVersion.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Webhook.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await TrustedIP.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Tag.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await IntegrationAuth.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Integration.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceToken.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceTokenData.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceTokenDataV3.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await ServiceTokenDataV3Key.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await AuditLog.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Log.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await Action.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretApprovalPolicy.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
await SecretApprovalRequest.deleteMany({
workspace: {
$in: workspaceIds
}
}, {
session
});
if (organization.customerId) {
// delete from stripe here
await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}`
);
}
return organization;
} catch (err) {
if (!existingSession) {
await session.abortTransaction();
}
throw InternalServerError({
message: "Failed to delete organization"
});
} finally {
if (!existingSession) {
await session.commitTransaction();
session.endSession();
}
} }
return organization;
} }
/** /**

View File

@ -0,0 +1,58 @@
import { ISecret } from "../models";
import {
createRecurringSecretReminder,
deleteRecurringSecretReminder,
updateRecurringSecretReminder
} from "../queues/reminders/sendSecretReminders";
type TPartialSecret = Pick<
ISecret,
"_id" | "secretReminderRepeatDays" | "secretReminderNote" | "workspace"
>;
type TPartialSecretDeleteReminder = Pick<ISecret, "_id" | "secretReminderRepeatDays">;
export const createReminder = async (oldSecret: TPartialSecret, newSecret: TPartialSecret) => {
if (oldSecret._id !== newSecret._id) {
throw new Error("Secret id's don't match");
}
if (!newSecret.secretReminderRepeatDays) {
throw new Error("No repeat days provided");
}
const secretId = oldSecret._id.toString();
const workspaceId = oldSecret.workspace.toString();
if (oldSecret.secretReminderRepeatDays) {
// This will first delete the existing recurring job, and then create a new one.
await updateRecurringSecretReminder({
workspaceId,
secretId,
repeatDays: newSecret.secretReminderRepeatDays,
note: newSecret.secretReminderNote
});
} else {
// This will create a new recurring job.
await createRecurringSecretReminder({
workspaceId,
secretId,
repeatDays: newSecret.secretReminderRepeatDays,
note: newSecret.secretReminderNote
});
}
};
export const deleteReminder = async (secret: TPartialSecretDeleteReminder) => {
if (!secret._id) {
throw new Error("No secret id provided");
}
if (!secret.secretReminderRepeatDays) {
throw new Error("No repeat days provided");
}
await deleteRecurringSecretReminder({
secretId: secret._id.toString(),
repeatDays: secret.secretReminderRepeatDays
});
};

View File

@ -1,12 +1,8 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { ISecret, Secret } from "../models"; import { ISecret, Secret } from "../models";
import { EELogService, EESecretService } from "../ee/services"; import { EESecretService } from "../ee/services";
import { IAction, SecretVersion } from "../ee/models"; import { SecretVersion } from "../ee/models";
import { import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
SECRET_PERSONAL, SECRET_PERSONAL,
@ -320,7 +316,6 @@ export const v2PushSecrets = async ({
ipAddress: string; ipAddress: string;
}): Promise<void> => { }): Promise<void> => {
// TODO: clean up function and fix up types // TODO: clean up function and fix up types
const actions: IAction[] = [];
// construct useful data structures // construct useful data structures
const oldSecrets = await getSecrets({ const oldSecrets = await getSecrets({
@ -356,15 +351,6 @@ export const v2PushSecrets = async ({
await EESecretService.markDeletedSecretVersions({ await EESecretService.markDeletedSecretVersions({
secretIds: toDelete, secretIds: toDelete,
}); });
const deleteAction = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(userId),
secretIds: toDelete,
});
deleteAction && actions.push(deleteAction);
} }
const toUpdate = oldSecrets.filter((s) => { const toUpdate = oldSecrets.filter((s) => {
@ -451,15 +437,6 @@ export const v2PushSecrets = async ({
}; };
}), }),
}); });
const updateAction = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
secretIds: toUpdate.map((u) => u._id),
});
updateAction && actions.push(updateAction);
} }
// handle adding new secrets // handle adding new secrets
@ -494,14 +471,6 @@ export const v2PushSecrets = async ({
}); });
}), }),
}); });
const addAction = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
secretIds: newSecrets.map((n) => n._id),
});
addAction && actions.push(addAction);
} }
// (EE) take a secret snapshot // (EE) take a secret snapshot
@ -509,17 +478,6 @@ export const v2PushSecrets = async ({
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
}); });
// (EE) create (audit) log
if (actions.length > 0) {
await EELogService.createLog({
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
actions,
channel,
ipAddress,
});
}
}; };
/** /**
@ -589,22 +547,6 @@ export const pullSecrets = async ({
environment, environment,
}); });
const readAction = await EELogService.createAction({
name: ACTION_READ_SECRETS,
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
secretIds: secrets.map((n: any) => n._id),
});
readAction &&
(await EELogService.createLog({
userId: new Types.ObjectId(userId),
workspaceId: new Types.ObjectId(workspaceId),
actions: [readAction],
channel,
ipAddress,
}));
return secrets; return secrets;
}; };

View File

@ -13,13 +13,11 @@ import {
Folder, Folder,
ISecret, ISecret,
IServiceTokenData, IServiceTokenData,
IServiceTokenDataV3,
Secret, Secret,
SecretBlindIndexData, SecretBlindIndexData,
ServiceTokenData, ServiceTokenData,
TFolderRootSchema TFolderRootSchema
} from "../models"; } from "../models";
import { Permission } from "../models/serviceTokenDataV3";
import { EventType, SecretVersion } from "../ee/models"; import { EventType, SecretVersion } from "../ee/models";
import { import {
BadRequestError, BadRequestError,
@ -29,10 +27,6 @@ import {
UnauthorizedRequestError UnauthorizedRequestError
} from "../utils/errors"; } from "../utils/errors";
import { import {
ACTION_ADD_SECRETS,
ACTION_DELETE_SECRETS,
ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS,
ALGORITHM_AES_256_GCM, ALGORITHM_AES_256_GCM,
ENCODING_SCHEME_BASE64, ENCODING_SCHEME_BASE64,
ENCODING_SCHEME_UTF8, ENCODING_SCHEME_UTF8,
@ -48,49 +42,13 @@ import {
} from "../utils/crypto"; } from "../utils/crypto";
import { TelemetryService } from "../services"; import { TelemetryService } from "../services";
import { client, getEncryptionKey, getRootEncryptionKey } from "../config"; import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
import { EEAuditLogService, EELogService, EESecretService } from "../ee/services"; import { EEAuditLogService, EESecretService } from "../ee/services";
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth"; import { getAuthDataPayloadUserObj } from "../utils/authn/helpers";
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService"; import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
import picomatch from "picomatch"; import picomatch from "picomatch";
import path from "path"; import path from "path";
import { getAnImportedSecret } from "../services/SecretImportService"; import { getAnImportedSecret } from "../services/SecretImportService";
/**
* Validate scope for service token v3
* @param authPayload
* @param environment
* @param secretPath
* @returns
*/
export const isValidScopeV3 = ({
authPayload,
environment,
secretPath,
requiredPermissions
}: {
authPayload: IServiceTokenDataV3;
environment: string;
secretPath: string;
requiredPermissions: Permission[];
}) => {
const { scopes } = authPayload;
const validScope = scopes.find(
(scope) =>
picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) &&
scope.environment === environment
);
if (
validScope &&
!requiredPermissions.every((permission) => validScope.permissions.includes(permission))
) {
return false;
}
return Boolean(validScope);
};
/** /**
* Validate scope for service token v2 * Validate scope for service token v2
* @param authPayload * @param authPayload
@ -478,23 +436,6 @@ export const createSecretHelper = async ({
secretVersions: [secretVersion] secretVersions: [secretVersion]
}); });
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_ADD_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {
@ -553,14 +494,22 @@ export const getSecretsHelper = async ({
workspaceId, workspaceId,
environment, environment,
authData, authData,
folderId,
secretPath = "/" secretPath = "/"
}: GetSecretsParams) => { }: GetSecretsParams) => {
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
// if using service token filter towards the folderId by secretpath // if using service token filter towards the folderId by secretpath
if (!folderId) { const folders = await Folder.findOne({
folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); workspace: workspaceId,
environment
});
let folderId = "root";
if (!folders && folderId !== "root") return [];
// get folder from folder tree
if (folders) {
const folder = getFolderByPath(folders.nodes, secretPath);
if (!folder) return [];
folderId = folder?.id;
} }
// get personal secrets first // get personal secrets first
@ -589,23 +538,6 @@ export const getSecretsHelper = async ({
.lean() .lean()
); );
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: secrets.map((secret) => secret._id)
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {
@ -642,18 +574,20 @@ export const getSecretsHelper = async ({
const approximateForNoneCapturedEvents = secrets.length * 10; const approximateForNoneCapturedEvents = secrets.length * 10;
if (shouldCapture) { if (shouldCapture) {
postHogClient.capture({ if (workspaceId.toString() != "650e71fbae3e6c8572f436d4") {
event: "secrets pulled", postHogClient.capture({
distinctId: await TelemetryService.getDistinctId({ authData }), event: "secrets pulled",
properties: { distinctId: await TelemetryService.getDistinctId({ authData }),
numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length, properties: {
environment, numberOfSecrets: shouldRecordK8Event ? approximateForNoneCapturedEvents : secrets.length,
workspaceId, environment,
folderId, workspaceId,
channel: authData.userAgentType, folderId,
userAgent: authData.userAgent channel: authData.userAgentType,
} userAgent: authData.userAgent
}); }
});
}
} }
} }
@ -717,23 +651,6 @@ export const getSecretHelper = async ({
if (!secret) throw SecretNotFoundError(); if (!secret) throw SecretNotFoundError();
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_READ_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {
@ -802,6 +719,8 @@ export const updateSecretHelper = async ({
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
secretPath, secretPath,
secretReminderRepeatDays,
secretReminderNote,
tags, tags,
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
@ -866,6 +785,10 @@ export const updateSecretHelper = async ({
secretCommentIV, secretCommentIV,
secretCommentTag, secretCommentTag,
secretCommentCiphertext, secretCommentCiphertext,
secretReminderRepeatDays,
secretReminderNote,
skipMultilineEncoding, skipMultilineEncoding,
secretBlindIndex: newSecretNameBlindIndex, secretBlindIndex: newSecretNameBlindIndex,
secretKeyIV, secretKeyIV,
@ -937,23 +860,6 @@ export const updateSecretHelper = async ({
secretVersions: [secretVersion] secretVersions: [secretVersion]
}); });
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_UPDATE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: [secret._id]
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {
@ -1081,23 +987,6 @@ export const deleteSecretHelper = async ({
secretIds: secrets.map((secret) => secret._id) secretIds: secrets.map((secret) => secret._id)
}); });
// (EE) create (audit) log
const action = await EELogService.createAction({
name: ACTION_DELETE_SECRETS,
...getAuthDataPayloadIdObj(authData),
workspaceId,
secretIds: secrets.map((secret) => secret._id)
});
action &&
(await EELogService.createLog({
...getAuthDataPayloadIdObj(authData),
workspaceId,
actions: [action],
channel: authData.userAgentType,
ipAddress: authData.ipAddress
}));
await EEAuditLogService.createAuditLog( await EEAuditLogService.createAuditLog(
authData, authData,
{ {

View File

@ -1,4 +1,4 @@
import mongoose, { Types, mongo } from "mongoose"; import { Types } from "mongoose";
import { import {
APIKeyData, APIKeyData,
BackupPrivateKey, BackupPrivateKey,
@ -10,10 +10,6 @@ import {
User, User,
UserAction UserAction
} from "../models"; } from "../models";
import {
Action,
Log
} from "../ee/models";
import { sendMail } from "./nodemailer"; import { sendMail } from "./nodemailer";
import { import {
InternalServerError, InternalServerError,
@ -222,141 +218,84 @@ const checkDeleteUserConditions = async ({
* @returns {User} user - deleted user * @returns {User} user - deleted user
*/ */
export const deleteUser = async ({ export const deleteUser = async ({
userId, userId
existingSession
}: { }: {
userId: Types.ObjectId; userId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => { }) => {
const user = await User.findByIdAndDelete(userId);
let session; if (!user) throw ResourceNotFoundError();
await checkDeleteUserConditions({
userId: user._id
});
if (existingSession) { await UserAction.deleteMany({
session = existingSession; user: user._id
} else { });
session = await mongoose.startSession();
session.startTransaction(); await BackupPrivateKey.deleteMany({
user: user._id
});
await APIKeyData.deleteMany({
user: user._id
});
await TokenVersion.deleteMany({
user: user._id
});
await Key.deleteMany({
receiver: user._id
});
const membershipOrgs = await MembershipOrg.find({
user: userId
});
// delete organizations where user is only member
for await (const membershipOrg of membershipOrgs) {
const memberCount = await MembershipOrg.countDocuments({
organization: membershipOrg.organization
});
if (memberCount === 1) {
// organization only has 1 member (the current user)
await deleteOrganization({
organizationId: membershipOrg.organization
});
}
} }
try { const memberships = await Membership.find({
const user = await User.findByIdAndDelete(userId, { user: userId
session });
// delete workspaces where user is only member
for await (const membership of memberships) {
const memberCount = await Membership.countDocuments({
workspace: membership.workspace
}); });
if (!user) throw ResourceNotFoundError(); if (memberCount === 1) {
// workspace only has 1 member (the current user) -> delete workspace
await checkDeleteUserConditions({ await deleteWorkspace({
userId: user._id workspaceId: membership.workspace
});
await UserAction.deleteMany({
user: user._id
}, {
session
});
await BackupPrivateKey.deleteMany({
user: user._id
}, {
session
});
await APIKeyData.deleteMany({
user: user._id
}, {
session
});
await Action.deleteMany({
user: user._id
}, {
session
});
await Log.deleteMany({
user: user._id
}, {
session
});
await TokenVersion.deleteMany({
user: user._id
});
await Key.deleteMany({
receiver: user._id
}, {
session
});
const membershipOrgs = await MembershipOrg.find({
user: userId
}, null, {
session
});
// delete organizations where user is only member
for await (const membershipOrg of membershipOrgs) {
const memberCount = await MembershipOrg.countDocuments({
organization: membershipOrg.organization
}); });
if (memberCount === 1) {
// organization only has 1 member (the current user)
await deleteOrganization({
organizationId: membershipOrg.organization,
existingSession: session
});
}
}
const memberships = await Membership.find({
user: userId
}, null, {
session
});
// delete workspaces where user is only member
for await (const membership of memberships) {
const memberCount = await Membership.countDocuments({
workspace: membership.workspace
});
if (memberCount === 1) {
// workspace only has 1 member (the current user) -> delete workspace
await deleteWorkspace({
workspaceId: membership.workspace,
existingSession: session
});
}
}
await MembershipOrg.deleteMany({
user: userId
}, {
session
});
await Membership.deleteMany({
user: userId
}, {
session
});
return user;
} catch (err) {
if (!existingSession) {
await session.abortTransaction();
}
throw InternalServerError({
message: "Failed to delete account"
})
} finally {
if (!existingSession) {
await session.commitTransaction();
session.endSession();
} }
} }
await MembershipOrg.deleteMany({
user: userId
});
await Membership.deleteMany({
user: userId
});
return user;
} }

View File

@ -1,4 +1,4 @@
import mongoose, { Types, mongo } from "mongoose"; import { Types } from "mongoose";
import { import {
Bot, Bot,
BotKey, BotKey,
@ -19,11 +19,9 @@ import {
Workspace Workspace
} from "../models"; } from "../models";
import { import {
Action,
AuditLog, AuditLog,
FolderVersion, FolderVersion,
IPType, IPType,
Log,
SecretApprovalPolicy, SecretApprovalPolicy,
SecretApprovalRequest, SecretApprovalRequest,
SecretSnapshot, SecretSnapshot,
@ -33,8 +31,7 @@ import {
import { createBot } from "../helpers/bot"; import { createBot } from "../helpers/bot";
import { EELicenseService } from "../ee/services"; import { EELicenseService } from "../ee/services";
import { SecretService } from "../services"; import { SecretService } from "../services";
import { import {
InternalServerError,
ResourceNotFoundError ResourceNotFoundError
} from "../utils/errors"; } from "../utils/errors";
@ -102,189 +99,105 @@ export const createWorkspace = async ({
* @param {String} obj.id - id of workspace to delete * @param {String} obj.id - id of workspace to delete
*/ */
export const deleteWorkspace = async ({ export const deleteWorkspace = async ({
workspaceId, workspaceId
existingSession
}: { }: {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
existingSession?: mongo.ClientSession;
}) => { }) => {
const workspace = await Workspace.findByIdAndDelete(workspaceId);
let session;
if (existingSession) {
session = existingSession;
} else {
session = await mongoose.startSession();
session.startTransaction();
}
try { if (!workspace) throw ResourceNotFoundError();
const workspace = await Workspace.findByIdAndDelete(workspaceId, { session });
await Membership.deleteMany({
if (!workspace) throw ResourceNotFoundError(); workspace: workspace._id
});
await Membership.deleteMany({
workspace: workspace._id await Key.deleteMany({
}, { workspace: workspace._id
session });
});
await Bot.deleteMany({
await Key.deleteMany({ workspace: workspace._id
workspace: workspace._id });
}, {
session
});
await Bot.deleteMany({
workspace: workspace._id
}, {
session
});
await BotKey.deleteMany({ await BotKey.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await SecretBlindIndexData.deleteMany({ await SecretBlindIndexData.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await Secret.deleteMany({ await Secret.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
}); await SecretVersion.deleteMany({
workspace: workspace._id
await SecretVersion.deleteMany({ });
workspace: workspace._id
}, {
session
});
await SecretSnapshot.deleteMany({ await SecretSnapshot.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await SecretImport.deleteMany({ await SecretImport.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await Folder.deleteMany({ await Folder.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await FolderVersion.deleteMany({ await FolderVersion.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await Webhook.deleteMany({ await Webhook.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await TrustedIP.deleteMany({ await TrustedIP.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await Tag.deleteMany({ await Tag.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await IntegrationAuth.deleteMany({ await IntegrationAuth.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await Integration.deleteMany({ await Integration.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await ServiceToken.deleteMany({ await ServiceToken.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await ServiceTokenData.deleteMany({ await ServiceTokenData.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await ServiceTokenDataV3.deleteMany({ await ServiceTokenDataV3.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await ServiceTokenDataV3Key.deleteMany({ await ServiceTokenDataV3Key.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await AuditLog.deleteMany({ await AuditLog.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await Log.deleteMany({ await SecretApprovalPolicy.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
});
await Action.deleteMany({ await SecretApprovalRequest.deleteMany({
workspace: workspace._id workspace: workspace._id
}, { });
session
}); return workspace;
await SecretApprovalPolicy.deleteMany({
workspace: workspace._id
}, {
session
});
await SecretApprovalRequest.deleteMany({
workspace: workspace._id
}, {
session
});
return workspace;
} catch (err) {
if (!existingSession) {
await session.abortTransaction();
}
throw InternalServerError({
message: "Failed to delete organization"
});
} finally {
if (!existingSession) {
await session.commitTransaction();
session.endSession();
}
}
}; };

View File

@ -2,9 +2,11 @@ import dotenv from "dotenv";
dotenv.config(); dotenv.config();
import express from "express"; import express from "express";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
require("express-async-errors"); import "express-async-errors";
import helmet from "helmet"; import helmet from "helmet";
import cors from "cors"; import cors from "cors";
import { initLogger, logger } from "./utils/logging";
import httpLogger from "pino-http";
import { DatabaseService } from "./services"; import { DatabaseService } from "./services";
import { EELicenseService, GithubSecretScanningService } from "./ee/services"; import { EELicenseService, GithubSecretScanningService } from "./ee/services";
import { setUpHealthEndpoint } from "./services/health"; import { setUpHealthEndpoint } from "./services/health";
@ -16,7 +18,6 @@ const swaggerFile = require("../spec.json");
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
import { apiLimiter } from "./helpers/rateLimiter"; import { apiLimiter } from "./helpers/rateLimiter";
import { import {
action as eeActionRouter,
cloudProducts as eeCloudProductsRouter, cloudProducts as eeCloudProductsRouter,
organizations as eeOrganizationsRouter, organizations as eeOrganizationsRouter,
sso as eeSSORouter, sso as eeSSORouter,
@ -25,12 +26,16 @@ import {
users as eeUsersRouter, users as eeUsersRouter,
workspace as eeWorkspaceRouter, workspace as eeWorkspaceRouter,
roles as v1RoleRouter, roles as v1RoleRouter,
secretApprovalPolicy as v1SecretApprovalPolicy, secretApprovalPolicy as v1SecretApprovalPolicyRouter,
secretApprovalRequest as v1SecretApprovalRequest, secretApprovalRequest as v1SecretApprovalRequestRouter,
secretRotation as v1SecretRotation,
secretRotationProvider as v1SecretRotationProviderRouter,
secretScanning as v1SecretScanningRouter secretScanning as v1SecretScanningRouter
} from "./ee/routes/v1"; } from "./ee/routes/v1";
import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3";
import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3"; import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3";
import { import {
admin as v1AdminRouter,
auth as v1AuthRouter, auth as v1AuthRouter,
bot as v1BotRouter, bot as v1BotRouter,
integrationAuth as v1IntegrationAuthRouter, integrationAuth as v1IntegrationAuthRouter,
@ -62,42 +67,63 @@ import {
signup as v2SignupRouter, signup as v2SignupRouter,
tags as v2TagsRouter, tags as v2TagsRouter,
users as v2UsersRouter, users as v2UsersRouter,
workspace as v2WorkspaceRouter workspace as v2WorkspaceRouter,
membership as v2MembershipController
} from "./routes/v2"; } from "./routes/v2";
import { import {
auth as v3AuthRouter, auth as v3AuthRouter,
secrets as v3SecretsRouter, secrets as v3SecretsRouter,
signup as v3SignupRouter, signup as v3SignupRouter,
users as v3UsersRouter,
workspaces as v3WorkspacesRouter workspaces as v3WorkspacesRouter
} from "./routes/v3"; } from "./routes/v3";
import { healthCheck } from "./routes/status"; import { healthCheck } from "./routes/status";
import { getLogger } from "./utils/logger"; // import { getLogger } from "./utils/logger";
import { RouteNotFoundError } from "./utils/errors"; import { RouteNotFoundError } from "./utils/errors";
import { requestErrorHandler } from "./middleware/requestErrorHandler"; import { requestErrorHandler } from "./middleware/requestErrorHandler";
import { import {
getMongoURL,
getNodeEnv, getNodeEnv,
getPort, getPort,
getSecretScanningGitAppId, getSecretScanningGitAppId,
getSecretScanningPrivateKey, getSecretScanningPrivateKey,
getSecretScanningWebhookProxy, getSecretScanningWebhookProxy,
getSecretScanningWebhookSecret, getSecretScanningWebhookSecret,
getSiteURL, getSiteURL
} from "./config"; } from "./config";
import { setup } from "./utils/setup"; import { setup } from "./utils/setup";
import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices"; import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecretsToThirdPartyServices";
import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent"; import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent";
const SmeeClient = require("smee-client"); // eslint-disable-line const SmeeClient = require("smee-client"); // eslint-disable-line
import path from "path"; import path from "path";
import { serverConfigInit } from "./config/serverConfig";
import { initRedis } from "./services/RedisService";
let handler: null | any = null; let handler: null | any = null;
const main = async () => { const main = async () => {
await initLogger();
const port = await getPort();
// initializing the database connection + redis
await initRedis()
await DatabaseService.initDatabase(await getMongoURL());
const serverCfg = await serverConfigInit();
await setup(); await setup();
await EELicenseService.initGlobalFeatureSet(); await EELicenseService.initGlobalFeatureSet();
const app = express(); const app = express();
app.enable("trust proxy"); app.enable("trust proxy");
app.use(
httpLogger({
logger,
autoLogging: false
})
);
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
@ -162,7 +188,7 @@ const main = async () => {
const nextApp = new NextServer({ const nextApp = new NextServer({
dev: false, dev: false,
dir: nextJsBuildPath, dir: nextJsBuildPath,
port: await getPort(), port,
conf, conf,
hostname: "local", hostname: "local",
customServer: false customServer: false
@ -176,15 +202,18 @@ const main = async () => {
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter); app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
app.use("/api/v1/users", eeUsersRouter); app.use("/api/v1/users", eeUsersRouter);
app.use("/api/v1/workspace", eeWorkspaceRouter); app.use("/api/v1/workspace", eeWorkspaceRouter);
app.use("/api/v1/action", eeActionRouter);
app.use("/api/v1/organizations", eeOrganizationsRouter); app.use("/api/v1/organizations", eeOrganizationsRouter);
app.use("/api/v1/sso", eeSSORouter); app.use("/api/v1/sso", eeSSORouter);
app.use("/api/v1/cloud-products", eeCloudProductsRouter); app.use("/api/v1/cloud-products", eeCloudProductsRouter);
app.use("/api/v3/service-token", v3ServiceTokenDataRouter); app.use("/api/v3/api-key", v3apiKeyDataRouter); // new
app.use("/api/v3/service-token", v3ServiceTokenDataRouter); // new
app.use("/api/v1/secret-rotation-providers", v1SecretRotationProviderRouter);
app.use("/api/v1/secret-rotations", v1SecretRotation);
// v1 routes // v1 routes
app.use("/api/v1/signup", v1SignupRouter); app.use("/api/v1/signup", v1SignupRouter);
app.use("/api/v1/auth", v1AuthRouter); app.use("/api/v1/auth", v1AuthRouter);
app.use("/api/v1/admin", v1AdminRouter);
app.use("/api/v1/bot", v1BotRouter); app.use("/api/v1/bot", v1BotRouter);
app.use("/api/v1/user", v1UserRouter); app.use("/api/v1/user", v1UserRouter);
app.use("/api/v1/user-action", v1UserActionRouter); app.use("/api/v1/user-action", v1UserActionRouter);
@ -204,28 +233,29 @@ const main = async () => {
app.use("/api/v1/webhooks", v1WebhooksRouter); app.use("/api/v1/webhooks", v1WebhooksRouter);
app.use("/api/v1/secret-imports", v1SecretImpsRouter); app.use("/api/v1/secret-imports", v1SecretImpsRouter);
app.use("/api/v1/roles", v1RoleRouter); app.use("/api/v1/roles", v1RoleRouter);
app.use("/api/v1/secret-approvals", v1SecretApprovalPolicy); app.use("/api/v1/secret-approvals", v1SecretApprovalPolicyRouter);
app.use("/api/v1/sso", v1SSORouter); app.use("/api/v1/sso", v1SSORouter);
app.use("/api/v1/secret-approval-requests", v1SecretApprovalRequest); app.use("/api/v1/secret-approval-requests", v1SecretApprovalRequestRouter);
// v2 routes (improvements) // v2 routes (improvements)
app.use("/api/v2/signup", v2SignupRouter); app.use("/api/v2/signup", v2SignupRouter);
app.use("/api/v2/auth", v2AuthRouter); app.use("/api/v2/auth", v2AuthRouter);
app.use("/api/v2/users", v2UsersRouter); app.use("/api/v2/users", v2UsersRouter);
app.use("/api/v2/organizations", v2OrganizationsRouter); app.use("/api/v2/organizations", v2OrganizationsRouter);
app.use("/api/v2/workspace", v2MembershipController);
app.use("/api/v2/workspace", v2EnvironmentRouter); app.use("/api/v2/workspace", v2EnvironmentRouter);
app.use("/api/v2/workspace", v2TagsRouter); app.use("/api/v2/workspace", v2TagsRouter);
app.use("/api/v2/workspace", v2WorkspaceRouter); app.use("/api/v2/workspace", v2WorkspaceRouter);
app.use("/api/v2/secret", v2SecretRouter); // deprecate app.use("/api/v2/secret", v2SecretRouter); // deprecate
app.use("/api/v2/secrets", v2SecretsRouter); // note: in the process of moving to v3/secrets app.use("/api/v2/secrets", v2SecretsRouter); // note: in the process of moving to v3/secrets
app.use("/api/v2/service-token", v2ServiceTokenDataRouter); app.use("/api/v2/service-token", v2ServiceTokenDataRouter);
// app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new
// v3 routes (experimental) // v3 routes (experimental)
app.use("/api/v3/auth", v3AuthRouter); app.use("/api/v3/auth", v3AuthRouter);
app.use("/api/v3/secrets", v3SecretsRouter); app.use("/api/v3/secrets", v3SecretsRouter);
app.use("/api/v3/workspaces", v3WorkspacesRouter); app.use("/api/v3/workspaces", v3WorkspacesRouter);
app.use("/api/v3/signup", v3SignupRouter); app.use("/api/v3/signup", v3SignupRouter);
app.use("/api/v3/us", v3UsersRouter);
// api docs // api docs
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile)); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
@ -251,8 +281,23 @@ const main = async () => {
app.use(requestErrorHandler); app.use(requestErrorHandler);
const server = app.listen(await getPort(), async () => { const server = app.listen(port, async () => {
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`); if (!serverCfg.initialized) {
logger.info(`Welcome to Infisical
Create your Infisical administrator account at:
http://localhost:${port}/admin/signup
`);
} else {
logger.info(`Welcome back!
To access Infisical Administrator Panel open
http://localhost:${port}/admin
To access Infisical server
http://localhost:${port}
`);
}
}); });
// await createTestUserForDevelopment(); // await createTestUserForDevelopment();

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,8 @@ import {
INTEGRATION_CIRCLECI_API_URL, INTEGRATION_CIRCLECI_API_URL,
INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_CLOUDFLARE_PAGES,
INTEGRATION_CLOUDFLARE_PAGES_API_URL, INTEGRATION_CLOUDFLARE_PAGES_API_URL,
INTEGRATION_CLOUDFLARE_WORKERS,
INTEGRATION_CLOUDFLARE_WORKERS_API_URL,
INTEGRATION_CLOUD_66, INTEGRATION_CLOUD_66,
INTEGRATION_CLOUD_66_API_URL, INTEGRATION_CLOUD_66_API_URL,
INTEGRATION_CODEFRESH, INTEGRATION_CODEFRESH,
@ -32,6 +34,8 @@ import {
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL, INTEGRATION_GITLAB_API_URL,
INTEGRATION_HASHICORP_VAULT, INTEGRATION_HASHICORP_VAULT,
INTEGRATION_HASURA_CLOUD,
INTEGRATION_HASURA_CLOUD_API_URL,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_HEROKU_API_URL, INTEGRATION_HEROKU_API_URL,
INTEGRATION_LARAVELFORGE, INTEGRATION_LARAVELFORGE,
@ -63,6 +67,10 @@ import { Octokit } from "@octokit/rest";
import _ from "lodash"; import _ from "lodash";
import sodium from "libsodium-wrappers"; import sodium from "libsodium-wrappers";
import { standardRequest } from "../config/request"; import { standardRequest } from "../config/request";
import {
ZGetTenantEnv,
ZUpdateTenantEnv
} from "../validation/hasuraCloudIntegration";
const getSecretKeyValuePair = ( const getSecretKeyValuePair = (
secrets: Record<string, { value: string | null; comment?: string } | null> secrets: Record<string, { value: string | null; comment?: string } | null>
@ -95,7 +103,7 @@ const syncSecrets = async ({
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessId: string | null; accessId: string | null;
accessToken: string; accessToken: string;
appendices?: { prefix: string, suffix: string }; appendices?: { prefix: string; suffix: string };
}) => { }) => {
switch (integration.integration) { switch (integration.integration) {
case INTEGRATION_GCP_SECRET_MANAGER: case INTEGRATION_GCP_SECRET_MANAGER:
@ -256,6 +264,14 @@ const syncSecrets = async ({
accessToken accessToken
}); });
break; break;
case INTEGRATION_CLOUDFLARE_WORKERS:
await syncSecretsCloudflareWorkers({
integration,
secrets,
accessId,
accessToken
});
break;
case INTEGRATION_CODEFRESH: case INTEGRATION_CODEFRESH:
await syncSecretsCodefresh({ await syncSecretsCodefresh({
integration, integration,
@ -306,6 +322,14 @@ const syncSecrets = async ({
accessToken accessToken
}); });
break; break;
case INTEGRATION_HASURA_CLOUD:
await syncSecretsHasuraCloud({
integration,
secrets,
accessToken
});
break;
} }
}; };
@ -963,8 +987,9 @@ const syncSecretsVercel = async ({
: {}), : {}),
...(integration?.path ...(integration?.path
? { ? {
gitBranch: integration?.path gitBranch: integration?.path
} : {}) }
: {})
}; };
const vercelSecrets: VercelSecret[] = ( const vercelSecrets: VercelSecret[] = (
@ -992,7 +1017,7 @@ const syncSecretsVercel = async ({
return true; return true;
}); });
const res: { [key: string]: VercelSecret } = {}; const res: { [key: string]: VercelSecret } = {};
for await (const vercelSecret of vercelSecrets) { for await (const vercelSecret of vercelSecrets) {
@ -1352,7 +1377,7 @@ const syncSecretsGitHub = async ({
integration: IIntegration; integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
appendices?: { prefix: string, suffix: string }; appendices?: { prefix: string; suffix: string };
}) => { }) => {
interface GitHubRepoKey { interface GitHubRepoKey {
key_id: string; key_id: string;
@ -1395,14 +1420,23 @@ const syncSecretsGitHub = async ({
{} {}
); );
encryptedSecrets = Object.keys(encryptedSecrets).reduce((result: { encryptedSecrets = Object.keys(encryptedSecrets).reduce(
[key: string]: GitHubSecret; (
}, key) => { result: {
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) { [key: string]: GitHubSecret;
result[key] = encryptedSecrets[key]; },
} key
return result; ) => {
}, {}); if (
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
) {
result[key] = encryptedSecrets[key];
}
return result;
},
{}
);
Object.keys(encryptedSecrets).map(async (key) => { Object.keys(encryptedSecrets).map(async (key) => {
if (!(key in secrets)) { if (!(key in secrets)) {
@ -2080,7 +2114,7 @@ const syncSecretsSupabase = async ({
}; };
/** /**
* Sync/push [secrets] to Checkly app * Sync/push [secrets] to Checkly app/group
* @param {Object} obj * @param {Object} obj
* @param {IIntegration} obj.integration - integration details * @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
@ -2095,87 +2129,156 @@ const syncSecretsCheckly = async ({
integration: IIntegration; integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
appendices?: { prefix: string, suffix: string }; appendices?: { prefix: string; suffix: string };
}) => { }) => {
let getSecretsRes = (
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
"X-Checkly-Account": integration.appId
}
})
).data.reduce(
(obj: any, secret: any) => ({
...obj,
[secret.key]: secret.value
}),
{}
);
getSecretsRes = Object.keys(getSecretsRes).reduce((result: { if (integration.targetServiceId) {
[key: string]: string; // sync secrets to checkly group envars
}, key) => {
if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) {
result[key] = getSecretsRes[key];
}
return result;
}, {});
// add secrets let getGroupSecretsRes = (
for await (const key of Object.keys(secrets)) { await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups/${integration.targetServiceId}`, {
if (!(key in getSecretsRes)) {
// case: secret does not exist in checkly
// -> add secret
await standardRequest.post(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables`,
{
key,
value: secrets[key].value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Content-Type": "application/json",
"X-Checkly-Account": integration.appId
}
}
);
} else {
// case: secret exists in checkly
// -> update/set secret
if (secrets[key] !== getSecretsRes[key]) {
await standardRequest.put(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`,
{
value: secrets[key].value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/json",
"X-Checkly-Account": integration.appId
}
}
);
}
}
}
for await (const key of Object.keys(getSecretsRes)) {
if (!(key in secrets)) {
// delete secret
await standardRequest.delete(`${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: "application/json", Accept: "application/json",
"X-Checkly-Account": integration.appId "X-Checkly-Account": integration.appId
} }
}); })
).data.environmentVariables.reduce(
(obj: any, secret: any) => ({
...obj,
[secret.key]: secret.value
}),
{}
);
getGroupSecretsRes = Object.keys(getGroupSecretsRes).reduce(
(
result: {
[key: string]: string;
},
key
) => {
if (
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
) {
result[key] = getGroupSecretsRes[key];
}
return result;
},
{}
);
const groupEnvironmentVariables = Object.keys(secrets).map(key => ({
key,
value: secrets[key].value
}));
await standardRequest.put(
`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups/${integration.targetServiceId}`,
{
environmentVariables: groupEnvironmentVariables
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"X-Checkly-Account": integration.appId
}
}
);
} else {
// sync secrets to checkly global envars
let getSecretsRes = (
await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json",
"X-Checkly-Account": integration.appId
}
})
).data.reduce(
(obj: any, secret: any) => ({
...obj,
[secret.key]: secret.value
}),
{}
);
getSecretsRes = Object.keys(getSecretsRes).reduce(
(
result: {
[key: string]: string;
},
key
) => {
if (
(appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) &&
(appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)
) {
result[key] = getSecretsRes[key];
}
return result;
},
{}
);
// add secrets
for await (const key of Object.keys(secrets)) {
if (!(key in getSecretsRes)) {
// case: secret does not exist in checkly
// -> add secret
await standardRequest.post(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables`,
{
key,
value: secrets[key].value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"Content-Type": "application/json",
"X-Checkly-Account": integration.appId
}
}
);
} else {
// case: secret exists in checkly
// -> update/set secret
if (secrets[key] !== getSecretsRes[key]) {
await standardRequest.put(
`${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`,
{
value: secrets[key].value
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/json",
"X-Checkly-Account": integration.appId
}
}
);
}
}
} }
for await (const key of Object.keys(getSecretsRes)) {
if (!(key in secrets)) {
// delete secret
await standardRequest.delete(`${INTEGRATION_CHECKLY_API_URL}/v1/variables/${key}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
"X-Checkly-Account": integration.appId
}
});
}
}
} }
}; };
@ -2195,18 +2298,20 @@ const syncSecretsQovery = async ({
secrets: Record<string, { value: string; comment?: string }>; secrets: Record<string, { value: string; comment?: string }>;
accessToken: string; accessToken: string;
}) => { }) => {
const getSecretsRes = ( const getSecretsRes = (
await standardRequest.get(`${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`, { await standardRequest.get(
headers: { `${INTEGRATION_QOVERY_API_URL}/${integration.scope}/${integration.appId}/environmentVariable`,
Authorization: `Token ${accessToken}`, {
"Accept-Encoding": "application/json" headers: {
Authorization: `Token ${accessToken}`,
"Accept-Encoding": "application/json"
}
} }
}) )
).data.results.reduce( ).data.results.reduce(
(obj: any, secret: any) => ({ (obj: any, secret: any) => ({
...obj, ...obj,
[secret.key]: {"id": secret.id, "value": secret.value} [secret.key]: { id: secret.id, value: secret.value }
}), }),
{} {}
); );
@ -2651,6 +2756,98 @@ const syncSecretsCloudflarePages = async ({
); );
}; };
/**
* Sync/push [secrets] to Cloudflare Workers project with name [integration.app]
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - API token for Cloudflare workers
*/
const syncSecretsCloudflareWorkers = async ({
integration,
secrets,
accessId,
accessToken
}: {
integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>;
accessId: string | null;
accessToken: string;
}) => {
// get secrets from cloudflare workers
const getSecretsRes = (
await standardRequest.get(
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
)
).data.result;
const secretsObj: any = getSecretKeyValuePair(secrets);
for (const [key, val] of Object.entries(secretsObj)) {
secretsObj[key] = { type: "secret_text", value: val };
}
// get deleted secrets list
const deletedSecretKeys: string[] = [];
if (getSecretsRes) {
getSecretsRes.forEach((secretRes: any) => {
if (!(Object.keys(secrets).includes(secretRes.name))) {
deletedSecretKeys.push(secretRes.name);
}
})
}
deletedSecretKeys.forEach(async (secretKey) => {
await standardRequest.delete(
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets/${secretKey}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
});
interface ConvertedSecret {
name: string;
text: string;
type: string;
}
interface SecretsObj {
[key: string]: {
type: string;
value: string;
};
}
const data: ConvertedSecret[] = Object.entries(secretsObj as SecretsObj).map(([name, secret]) => ({
name,
text: secret.value,
type: "secret_text"
}));
data.forEach(async (secret) => {
await standardRequest.put(
`${INTEGRATION_CLOUDFLARE_WORKERS_API_URL}/client/v4/accounts/${accessId}/workers/scripts/${integration.app}/secrets`,
secret,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
);
})
};
/** /**
* Sync/push [secrets] to BitBucket repo with name [integration.app] * Sync/push [secrets] to BitBucket repo with name [integration.app]
* @param {Object} obj * @param {Object} obj
@ -3076,4 +3273,111 @@ const syncSecretsNorthflank = async ({
); );
}; };
/** Sync/push [secrets] to Hasura Cloud
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessToken - access token for Hasura Cloud integration
*/
const syncSecretsHasuraCloud = async ({
integration,
secrets,
accessToken
}: {
integration: IIntegration;
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
}) => {
const res = await standardRequest.post(
INTEGRATION_HASURA_CLOUD_API_URL,
{
query:
"query MyQuery($tenantId: uuid!) { getTenantEnv(tenantId: $tenantId) { hash envVars } }",
variables: {
tenantId: integration.appId
}
},
{
headers: {
Authorization: `pat ${accessToken}`,
"Content-Type": "application/json"
}
}
);
const {
data: {
getTenantEnv: { hash, envVars }
}
} = ZGetTenantEnv.parse(res.data);
let currentHash = hash;
const secretsToUpdate = Object.keys(secrets).map((key) => {
return ({
key,
value: secrets[key].value
});
});
if (secretsToUpdate.length) {
// update secrets
const addRequest = await standardRequest.post(
INTEGRATION_HASURA_CLOUD_API_URL,
{
query:
"mutation MyQuery($currentHash: String!, $envs: [UpdateEnvObject!]!, $tenantId: uuid!) { updateTenantEnv(currentHash: $currentHash, envs: $envs, tenantId: $tenantId) { hash envVars} }",
variables: {
currentHash,
envs: secretsToUpdate,
tenantId: integration.appId
}
},
{
headers: {
Authorization: `pat ${accessToken}`,
"Content-Type": "application/json"
}
}
);
const addRequestResponse = ZUpdateTenantEnv.safeParse(addRequest.data);
if (addRequestResponse.success) {
currentHash = addRequestResponse.data.data.updateTenantEnv.hash;
}
}
const secretsToDelete = envVars.environment
? Object.keys(envVars.environment).filter((key) => !(key in secrets))
: [];
if (secretsToDelete.length) {
await standardRequest.post(
INTEGRATION_HASURA_CLOUD_API_URL,
{
query: `
mutation deleteTenantEnv($id: uuid!, $currentHash: String!, $env: [String!]!) {
deleteTenantEnv(tenantId: $id, currentHash: $currentHash, deleteEnvs: $env) {
hash
envVars
}
}
`,
variables: {
id: integration.appId,
currentHash,
env: secretsToDelete
}
},
{
headers: {
Authorization: `pat ${accessToken}`,
"Content-Type": "application/json"
}
}
);
}
};
export { syncSecrets }; export { syncSecrets };

View File

@ -1,39 +1,27 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { import { IServiceTokenData, IServiceTokenDataV3, IUser } from "../../models";
IServiceTokenData, import { ServiceActor, ServiceActorV3, UserActor, UserAgentType } from "../../ee/models";
IServiceTokenDataV3,
IUser,
} from "../../models";
import {
ServiceActor,
ServiceActorV3,
UserActor,
UserAgentType
} from "../../ee/models";
interface BaseAuthData { interface BaseAuthData {
ipAddress: string; ipAddress: string;
userAgent: string; userAgent: string;
userAgentType: UserAgentType; userAgentType: UserAgentType;
tokenVersionId?: Types.ObjectId; tokenVersionId?: Types.ObjectId;
} }
export interface UserAuthData extends BaseAuthData { export interface UserAuthData extends BaseAuthData {
actor: UserActor; actor: UserActor;
authPayload: IUser; authPayload: IUser;
} }
export interface ServiceTokenV3AuthData extends BaseAuthData { export interface ServiceTokenV3AuthData extends BaseAuthData {
actor: ServiceActorV3; actor: ServiceActorV3;
authPayload: IServiceTokenDataV3; authPayload: IServiceTokenDataV3;
} }
export interface ServiceTokenAuthData extends BaseAuthData { export interface ServiceTokenAuthData extends BaseAuthData {
actor: ServiceActor; actor: ServiceActor;
authPayload: IServiceTokenData; authPayload: IServiceTokenData;
} }
export type AuthData = export type AuthData = UserAuthData | ServiceTokenV3AuthData | ServiceTokenAuthData;
| UserAuthData
| ServiceTokenV3AuthData
| ServiceTokenAuthData;

View File

@ -1,7 +0,0 @@
interface AddServiceAccountPermissionDto {
name: string;
workspaceId?: string;
environment?: string;
}
export default AddServiceAccountPermissionDto;

View File

@ -1,8 +0,0 @@
interface CreateServiceAccountDto {
organizationId: string;
name: string;
publicKey: string;
expiresIn: number;
}
export default CreateServiceAccountDto;

View File

@ -1,7 +0,0 @@
import CreateServiceAccountDto from "./CreateServiceAccountDto";
import AddServiceAccountPermissionDto from "./AddServiceAccountPermissionDto";
export {
CreateServiceAccountDto,
AddServiceAccountPermissionDto,
}

View File

@ -26,7 +26,6 @@ export interface CreateSecretParams {
export interface GetSecretsParams { export interface GetSecretsParams {
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
environment: string; environment: string;
folderId?: string;
secretPath: string; secretPath: string;
authData: AuthData; authData: AuthData;
} }
@ -59,6 +58,10 @@ export interface UpdateSecretParams {
secretCommentCiphertext?: string; secretCommentCiphertext?: string;
secretCommentIV?: string; secretCommentIV?: string;
secretCommentTag?: string; secretCommentTag?: string;
secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
tags?: string[]; tags?: string[];
} }

View File

@ -1,43 +1,27 @@
import requireAuth from "./requireAuth"; import requireAuth from "./requireAuth";
import requireMfaAuth from "./requireMfaAuth"; import requireMfaAuth from "./requireMfaAuth";
import requireBotAuth from "./requireBotAuth";
import requireSignupAuth from "./requireSignupAuth"; import requireSignupAuth from "./requireSignupAuth";
import requireWorkspaceAuth from "./requireWorkspaceAuth"; import requireWorkspaceAuth from "./requireWorkspaceAuth";
import requireMembershipAuth from "./requireMembershipAuth";
import requireMembershipOrgAuth from "./requireMembershipOrgAuth";
import requireOrganizationAuth from "./requireOrganizationAuth";
import requireIntegrationAuth from "./requireIntegrationAuth";
import requireIntegrationAuthorizationAuth from "./requireIntegrationAuthorizationAuth";
import requireServiceTokenAuth from "./requireServiceTokenAuth"; import requireServiceTokenAuth from "./requireServiceTokenAuth";
import requireServiceTokenDataAuth from "./requireServiceTokenDataAuth";
import requireServiceAccountAuth from "./requireServiceAccountAuth";
import requireServiceAccountWorkspacePermissionAuth from "./requireServiceAccountWorkspacePermissionAuth";
import requireSecretAuth from "./requireSecretAuth"; import requireSecretAuth from "./requireSecretAuth";
import requireSecretsAuth from "./requireSecretsAuth"; import requireSecretsAuth from "./requireSecretsAuth";
import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled"; import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled";
import requireE2EEOff from "./requireE2EEOff"; import requireE2EEOff from "./requireE2EEOff";
import requireIPAllowlistCheck from "./requireIPAllowlistCheck"; import { requireSuperAdminAccess } from "./requireSuperAdminAccess";
import validateRequest from "./validateRequest"; import validateRequest from "./validateRequest";
import { disableSignUpByServerCfg } from "./serverAdmin";
export { export {
requireAuth, requireAuth,
requireMfaAuth, requireMfaAuth,
requireBotAuth, requireSignupAuth,
requireSignupAuth, requireWorkspaceAuth,
requireWorkspaceAuth, requireServiceTokenAuth,
requireMembershipAuth, requireSecretAuth,
requireMembershipOrgAuth, requireSecretsAuth,
requireOrganizationAuth, requireBlindIndicesEnabled,
requireIntegrationAuth, requireE2EEOff,
requireIntegrationAuthorizationAuth, validateRequest,
requireServiceTokenAuth, requireSuperAdminAccess,
requireServiceTokenDataAuth, disableSignUpByServerCfg
requireServiceAccountAuth,
requireServiceAccountWorkspacePermissionAuth,
requireSecretAuth,
requireSecretsAuth,
requireBlindIndicesEnabled,
requireE2EEOff,
requireIPAllowlistCheck,
validateRequest,
}; };

View File

@ -2,49 +2,45 @@ import * as Sentry from "@sentry/node";
import { ErrorRequestHandler } from "express"; import { ErrorRequestHandler } from "express";
import { TokenExpiredError } from "jsonwebtoken"; import { TokenExpiredError } from "jsonwebtoken";
import { InternalServerError, UnauthorizedRequestError } from "../utils/errors"; import { InternalServerError, UnauthorizedRequestError } from "../utils/errors";
import { getLogger } from "../utils/logger"; import { logger } from "../utils/logging";
import RequestError from "../utils/requestError"; import RequestError, { mapToPinoLogLevel } from "../utils/requestError";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
export const requestErrorHandler: ErrorRequestHandler = async ( export const requestErrorHandler: ErrorRequestHandler = async (
error: RequestError | Error, err: RequestError | Error,
req, req,
res, res,
next next
) => { ) => {
if (res.headersSent) return next(); if (res.headersSent) return next();
const logAndCaptureException = async (error: RequestError) => { let error: RequestError;
(await getLogger("backend-main")).log(
(<RequestError>error).levelName.toLowerCase(),
`${error.stack}\n${error.message}`
);
//* Set Sentry user identification if req.user is populated switch (true) {
if (req.user !== undefined && req.user !== null) { case err instanceof TokenExpiredError:
Sentry.setUser({ email: (req.user as any).email }); error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" });
} break;
case err instanceof ForbiddenError:
Sentry.captureException(error); error = UnauthorizedRequestError({ context: { exception: err.message }, stack: err.stack })
}; break;
case err instanceof RequestError:
if (error instanceof RequestError) { error = err as RequestError;
if (error instanceof TokenExpiredError) { break;
error = UnauthorizedRequestError({ stack: error.stack, message: "Token expired" }); default:
} error = InternalServerError({ context: { exception: err.message }, stack: err.stack });
await logAndCaptureException((<RequestError>error)); break;
} else {
if (error instanceof ForbiddenError) {
error = UnauthorizedRequestError({ context: { exception: error.message }, stack: error.stack })
} else {
error = InternalServerError({ context: { exception: error.message }, stack: error.stack });
}
await logAndCaptureException((<RequestError>error));
} }
logger[mapToPinoLogLevel(error.level)]({ msg: error });
if (req.user) {
Sentry.setUser({ email: (req.user as any).email });
}
Sentry.captureException(error);
delete (<any>error).stacktrace // remove stack trace from being sent to client delete (<any>error).stacktrace // remove stack trace from being sent to client
res.status((<RequestError>error).statusCode).json(error); res.status((<RequestError>error).statusCode).json(error); // revise json part here
next(); next();
}; };

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