Compare commits

...

240 Commits

Author SHA1 Message Date
5fdec97319 requested changes 2025-01-15 17:50:30 +01:00
9edfdb7234 docs(audit-logs): audit log streams structure 2025-01-15 02:33:21 +01:00
6ee446e574 Merge pull request #2979 from akhilmhdh/refactor/permission-service-fn
Refactored permission service for project to have project operation type validation
2025-01-14 23:50:10 +05:30
c806059b11 Merge pull request #2985 from Infisical/misc/add-oidc-saml-handling-for-login-check
misc: add oidc saml handling for login SA check
2025-01-14 13:11:29 -05:00
=
3a5bb31bde feat: updated all permission argument to actionProjectType 2025-01-14 23:41:06 +05:30
=
6f38d6c76f feat: updated to enum ActionProjectType 2025-01-14 23:26:18 +05:30
=
d721a46ec9 fix: resolved secret and approval count triggered for other project types 2025-01-14 23:26:18 +05:30
=
989065ba34 refactor: updated permission service for projects to check for operation type as well 2025-01-14 23:26:18 +05:30
5ad419c079 misc: updated comment 2025-01-15 00:49:53 +08:00
80f72e8040 misc: added context 2025-01-15 00:48:19 +08:00
0cef728617 Merge pull request #2986 from Infisical/misc/addressed-cf-pages-error-304
fix: addressed cloudflare pages error 304
2025-01-14 10:42:42 -05:00
0a735422ed misc: standardized log 2025-01-14 23:39:47 +08:00
8370a0d9c0 misc: added log 2025-01-14 23:29:04 +08:00
71af662998 fix: addressed cloudflare pages error 304 2025-01-14 23:18:38 +08:00
27e391f7e0 misc: add oidc saml handling for login check 2025-01-14 21:02:41 +08:00
2187c7588c update cache control to be no store 2025-01-13 23:58:36 -05:00
8ab7e8360d add cache busting param 2025-01-13 22:53:04 -05:00
957cab3117 Merge pull request #2982 from Infisical/fix-copy-user-id
Fix: Copy Username on User Details Page
2025-01-13 20:06:11 -05:00
4bf16f68fc fix: correctly copy user email to clipboard 2025-01-13 17:03:08 -08:00
2a86e6f4d1 add cache contro to cloudflare pages integration 2025-01-13 17:17:39 -05:00
194fbb79f2 Merge pull request #2975 from Infisical/daniel/custom-cors
feat(api): custom cors settings
2025-01-13 14:36:19 -05:00
faaba8deb7 add metabase link to on call guide 2025-01-13 11:16:49 -05:00
ae21b157a9 Merge pull request #2980 from Infisical/misc/added-proper-error-propagation-for-aws-kms
misc: added error propagation for aws kms
2025-01-13 23:46:57 +08:00
6167c70a74 Merge pull request #2973 from Infisical/daniel/push-secret-docs
docs: small k8s docs improvements
2025-01-13 10:39:45 -05:00
0ecf75cbdb misc: scoped down to AWS-related errors 2025-01-13 23:18:24 +08:00
3f8aa0fa4b misc: added error propagation for aws kms 2025-01-13 23:00:40 +08:00
6487c83bda docs: requested changes 2025-01-13 14:35:36 +01:00
c08fbbdab2 Update app.ts 2025-01-13 13:59:25 +01:00
a4aa65bb81 add on call to hand book 2025-01-13 01:27:50 -05:00
258d19cbe4 Merge pull request #2974 from Infisical/daniel/shared-secret-audit-logs
feat(audit-logs): shared secrets audit logs
2025-01-10 22:29:05 +01:00
de91356127 Update envars.mdx 2025-01-10 22:24:57 +01:00
ccb07942de feat(api): custom cors settings 2025-01-10 22:23:19 +01:00
3d278b0925 feat(audit-logs): shared secrets audit logs 2025-01-10 21:37:10 +01:00
956fb2efb4 docs: better k8s pre-req 2025-01-10 20:16:30 +01:00
13894261ce docs: small k8s docs improvements 2025-01-10 20:10:00 +01:00
d7ffa70906 Merge pull request #2972 from Infisical/misc/suppot-array-value-for-oidc-aud-field
misc: add support for array values in OIDC aud field
2025-01-10 13:32:09 -05:00
b8fa7c5bb6 Merge pull request #2969 from Infisical/misc/fix-shared-secret-within-org-and-login-redirect
misc: resolve shared secret within org and added login redirect
2025-01-11 02:16:48 +08:00
2baacfcd8f misc: add support for array values 2025-01-11 02:07:04 +08:00
31c11f7d2a Merge pull request #2964 from Infisical/vmatsiiako-typo-patch-1
Update infisical-dynamic-secret-crd.mdx
2025-01-10 12:43:40 -05:00
c5f06dece4 Merge pull request #2957 from Infisical/misc/made-is-active-optional-for-integration-patch
misc: made is active optional for integration patch
2025-01-11 00:26:21 +08:00
662e79ac98 Merge pull request #2971 from Infisical/daniel/audit-log-timestamp-bug
fix(audit-logs): time conversion bug
2025-01-10 17:23:58 +01:00
17249d603b fix: add delete secrets event type to frontend 2025-01-10 17:15:26 +01:00
9bdff9c504 fix: explicit postgres time conversion 2025-01-10 17:15:14 +01:00
4552ce6ca4 Merge pull request #2970 from Infisical/misc/add-secret-key-indicator-for-failed-integrations-when-possible
misc: add secret key indicator for failed AWS integration syncs
2025-01-10 11:10:25 -05:00
ba4b8801eb Merge pull request #2965 from akhilmhdh/fix/broken-secret-creation
Resolve self signed error for mssql
2025-01-10 11:01:54 -05:00
36a5f728a1 misc: add secret key indicator for failed AWS integration syncs 2025-01-11 00:00:17 +08:00
502429d914 misc: resolve shared secret within org and added login redirect 2025-01-10 23:55:21 +08:00
27abfa4fff Merge pull request #2966 from Infisical/misc/add-pagination-handling-for-gitlab-groups-fetch
misc: add pagination handling for gitlab groups fetch
2025-01-10 19:15:38 +08:00
4bc9bca287 removed undefined type 2025-01-10 19:08:49 +08:00
612c29225d misc: add pagination handling for gitlab groups fetch 2025-01-10 19:06:10 +08:00
=
4d43accc8a fix: did same resolution for dynamic secret ops as well 2025-01-10 15:12:04 +05:30
3c89a69410 Update infisical-dynamic-secret-crd.mdx 2025-01-10 01:39:12 -08:00
=
e741b63e63 fix: resolved mssql self signed error 2025-01-10 14:59:49 +05:30
9cde1995c7 Merge pull request #2962 from Infisical/fix/address-indefinite-hang-for-run-watch-failure
fix: address infinite hang when infisical run watch fails
2025-01-10 11:54:51 +08:00
3ed3856c85 Merge pull request #2963 from akhilmhdh/fix/broken-secret-creation
feat: added validation for secret name to disallow spaces and overview page
2025-01-09 15:38:25 -05:00
=
8054d93851 feat: added validation for secret name to disallow spaces and overview page fix for folder creation 2025-01-10 02:00:39 +05:30
02dc23425c fix: address infinite hang when infisical run watch fails 2025-01-10 02:29:39 +08:00
01534c3525 Merge pull request #2847 from Infisical/daniel/k8s-dynamic-secrets
feat(k8-operator): dynamic secrets
2025-01-09 12:45:30 -05:00
325ce73b9f fix typo 2025-01-09 12:36:52 -05:00
1639bda3f6 remove copy-paste and make redeploy docs specific 2025-01-09 12:23:46 -05:00
b5b91c929f fix kubernetes docs 2025-01-09 12:11:48 -05:00
9bc549ca8c Merge pull request #2960 from Infisical/feat/add-initial-sync-behavior-for-gitlab
feat: add initial sync behavior for gitlab
2025-01-10 01:01:17 +08:00
cedc88d83a misc: removed comment 2025-01-10 00:42:16 +08:00
f866a810c1 Merge pull request #2950 from Infisical/feat/secret-access-list
feat: secret access list
2025-01-10 00:40:56 +08:00
0c034d69ac misc: removed raw from api 2025-01-10 00:32:31 +08:00
e29f7f656c docs(k8s): better documentation layout 2025-01-09 16:07:07 +01:00
858e569d4d feat: add initial sync behavior for gitlab 2025-01-09 20:43:14 +08:00
5d8f32b774 Merge pull request #2955 from thomas-infisical/patch-1
Doc: Update Nov & Dec Changelogs
2025-01-08 21:42:49 -08:00
bb71f5eb7e Merge pull request #2956 from Infisical/daniel/update-k8s-manifest
fix: k8's manifest out of sync
2025-01-08 17:25:21 -05:00
30b431a255 Merge pull request #2958 from Infisical/bump-ecs-deploy-task-definition
ci: Bump aws-actions/amazon-ecs-deploy-task-definition to v2
2025-01-08 16:22:35 -05:00
fd32118685 Bump aws-actions/amazon-ecs-deploy-task-definition to v2
Bump aws-actions/amazon-ecs-deploy-task-definition to resolve:

```
Error: Failed to register task definition in ECS: Unexpected key 'enableFaultInjection' found in params
Error: Unexpected key 'enableFaultInjection' found in params
```

errors.
2025-01-08 13:20:41 -08:00
8528f3fd2a Merge pull request #2897 from Infisical/feat/resource-metadata
feat: resource metadata
2025-01-08 14:47:01 -05:00
60749cfc43 misc: made is active optional for integration patch 2025-01-09 02:19:24 +08:00
eb358bcafd Update install-secrets-operator.yaml 2025-01-08 16:26:50 +01:00
ffbd29c575 doc: update nov & dec changelog
First try at updating the Changelog from these past months.

Let me know if you think of anything I might have forgotten.
2025-01-08 15:50:53 +01:00
a74f0170da Merge pull request #2954 from Infisical/fix/addressed-reported-migration-related-ui-issues
fix: resolved conditions permission not getting added from new policy…
2025-01-08 22:26:45 +08:00
=
a0fad34a6d fix: resolved conditions permission not getting added from new policy button 2025-01-08 19:48:13 +05:30
f0dc5ec876 misc: add secret access insights to license fns 2025-01-08 20:56:33 +08:00
c2453f0c84 misc: finalized ui 2025-01-08 20:50:20 +08:00
2819c8519e misc: added license checks 2025-01-08 20:31:04 +08:00
616b013b12 Merge remote-tracking branch 'origin/main' into feat/secret-access-list 2025-01-08 20:08:16 +08:00
0b9d890a51 Merge pull request #2953 from Infisical/fix/addressed-reported-migration-related-ui-issues
fix: address sso redirect and project role creation
2025-01-08 19:20:19 +08:00
5ba507bc1c misc: made installation ID optional for github 2025-01-08 19:17:38 +08:00
=
0ecc196e5d feat: added missing coerce in azure key vault 2025-01-08 16:30:46 +05:30
=
ddac9f7cc4 feat: made all redirect route to coerce it 2025-01-08 16:27:56 +05:30
f5adc4d9f3 misc: removed unnecessary type assertion 2025-01-08 18:44:58 +08:00
34354994d8 fix: address sso redirect and project role creation 2025-01-08 18:29:25 +08:00
d7c3192099 feat: frontend integration 2025-01-08 18:24:40 +08:00
1576358805 Merge pull request #2947 from Infisical/auth0-saml
Add Support for Auth0 SAML
2025-01-07 15:04:44 -05:00
e6103d2d3f docs: update initial saml setup steps 2025-01-07 11:45:07 -08:00
8bf8bc77c9 Merge pull request #2951 from akhilmhdh/fix/broken-image
feat: resolved app not loading on org no access
2025-01-07 14:29:47 -05:00
=
3219723149 feat: resolved app not loading on org no access 2025-01-08 00:55:22 +05:30
74b95d92ab feat: backend setup 2025-01-08 02:48:47 +08:00
6d3793beff Merge pull request #2949 from akhilmhdh/fix/broken-image
Fixed broke image
2025-01-08 00:18:38 +05:30
0df41f3391 Merge pull request #2948 from Infisical/fix-user-groups-plan
Improvement: Clarify Enterprise Plan for User Group Feature Upgrade Modal
2025-01-07 10:44:32 -08:00
=
1acac9d479 feat: resolved broken gitlab image and hidden the standalone endpoints from api documentation 2025-01-08 00:14:31 +05:30
0cefd6f837 Run linter 2025-01-08 01:41:22 +07:00
5e9dc0b98d clarify enterprise plan for user group feature upgrade modal 2025-01-07 10:38:09 -08:00
f632847dc6 Merge branch 'main', remote-tracking branch 'origin' into auth0-saml 2025-01-08 01:35:11 +07:00
faa6d1cf40 Add support for Auth0 SAML 2025-01-08 01:34:03 +07:00
7fb18870e3 Merge pull request #2946 from akhilmhdh/feat/updated-saml-error-message
Updated saml error message
2025-01-07 10:57:03 -05:00
ae841715e5 update saml error message 2025-01-07 10:56:44 -05:00
=
baac87c16a feat: updated saml error message on missing email or first name attribute 2025-01-07 21:17:25 +05:30
291d29ec41 Merge remote-tracking branch 'origin/main' into feat/resource-metadata 2025-01-07 19:15:33 +08:00
b726187ba3 Merge pull request #2917 from mr-ssd/patch-1
docs: add note for dynamic secrets as paid feature
2025-01-07 14:12:50 +05:30
d98ff32b07 Merge pull request #2919 from mr-ssd/patch-2
docs: add note Approval Workflows as paid feature
2025-01-07 14:12:12 +05:30
1fa510b32f Merge pull request #2944 from Infisical/feat/target-specific-azure-key-vault-tenant
feat: target specific azure key vault tenant
2025-01-07 14:05:14 +08:00
c57f0d8120 Merge pull request #2945 from Infisical/misc/made-secret-path-input-show-correct-folder
misc: made secret path input show correct folders
2025-01-06 23:28:36 -05:00
00490f2cff misc: made secret path input show correct folders based on env in integrations 2025-01-07 12:08:09 +08:00
ee58f538c0 feat: target specific azure key vault tenant 2025-01-07 11:53:17 +08:00
0fa20f7839 Merge pull request #2943 from Infisical/aws-sm-integration-force-delete
Fix: Set Force Flag on Delete Secret Command for AWS Secrets Manager
2025-01-06 20:51:48 -05:00
40ef75d3bd fix: use force flag when deleting secrets from aws secret manager integration 2025-01-06 16:11:32 -08:00
26af13453c Merge pull request #2942 from akhilmhdh/fix/text-typo
fix: resolved typo in layout
2025-01-06 17:31:06 -05:00
=
ad1f71883d fix: resolved typo in layout 2025-01-07 02:58:56 +05:30
aa39451bc2 fix: generated files 2025-01-06 22:03:56 +01:00
f5548b3e8c Merge branch 'heads/main' into daniel/k8s-dynamic-secrets 2025-01-06 22:00:21 +01:00
2659ea7170 Merge pull request #2940 from Infisical/misc/updated-openssh-installation-for-fips
misc: updated openssh installation for fips
2025-01-06 14:08:07 -05:00
d2e3f504fd misc: updated openssh installation for fips 2025-01-07 03:04:57 +08:00
ca4151a34d Merge pull request #2935 from akhilmhdh/refactor/rbr
Adios nextjs
2025-01-06 14:01:22 -05:00
=
d4bc104fd1 feat: adios nextjs 2025-01-06 18:38:57 +00:00
=
7e3a3fcdb0 feat: updated the installation id to coerce string 2025-01-06 23:37:16 +05:30
=
7f67912d2f feat: resolved failing be lint check 2025-01-06 22:18:49 +05:30
=
a7f4020c08 feat: bumped nodejs from 16 to 20 2025-01-06 22:13:56 +05:30
=
d2d89034ba feat: moved more of isLoading to isPending 2025-01-06 22:13:16 +05:30
=
2fc6c564c0 feat: removed all existBeforeEnter error 2025-01-06 19:56:53 +05:30
=
240b86c1d5 fix: resolved project list issue and app connection github not listed 2025-01-06 19:48:50 +05:30
=
30d66cef89 feat: resolved slack failing and approval url correction 2025-01-06 19:18:10 +05:30
=
b7d11444a9 feat: resolved bug on org change select and project on change 2025-01-06 19:01:10 +05:30
=
0a6aef0afa feat: lint fixes 2025-01-06 14:56:51 +05:30
=
0ac7ec460b feat: resolving some bugs 2025-01-06 14:56:50 +05:30
=
808a901aee feat: updated env in docker files 2025-01-06 14:56:50 +05:30
=
d5412f916f feat: updated frontend use run time config inject value 2025-01-06 14:56:50 +05:30
=
d0648ca596 feat: added runtime env config endpoint 2025-01-06 14:56:50 +05:30
=
3291f1f908 feat: resolved typo in integration redirects 2025-01-06 14:56:50 +05:30
=
965d30bd03 feat: updated docker 2025-01-06 14:56:50 +05:30
=
68fe7c589a feat: updated backend to support bare react as standalone 2025-01-06 14:56:50 +05:30
=
54377a44d3 feat: renamed old frontend and fixed some minor bugs 2025-01-06 14:56:49 +05:30
=
8c902d7699 feat: added error page and not found page handler 2025-01-06 14:55:14 +05:30
=
c25c84aeb3 feat: added csp and minor changes 2025-01-06 14:55:14 +05:30
=
4359eb7313 feat: testing all todo comments and cli 2025-01-06 14:55:13 +05:30
=
322536d738 feat: completed migration of all integrations pages 2025-01-06 14:55:13 +05:30
=
6c5db3a187 feat: completed secret manager integration list and detail page 2025-01-06 14:55:13 +05:30
=
a337e6badd feat: bug fixes in overview page and dashboard page 2025-01-06 14:55:13 +05:30
=
524a97e9a6 fix: resolved infinite rendering issue caused by zustand 2025-01-06 14:55:13 +05:30
=
c56f598115 feat: switched to official lottie react and adjusted all the existings ones 2025-01-06 14:55:13 +05:30
=
19d32a1a3b feat: completed migration of ssh product 2025-01-06 14:55:12 +05:30
=
7e5417a0eb feat: completed migration of app connection 2025-01-06 14:55:12 +05:30
=
afd6de27fe feat: re-arranged route paths 2025-01-06 14:55:12 +05:30
=
7781a6b7e7 feat: added static images and fixing minor layout issues 2025-01-06 14:55:12 +05:30
=
b3b4e41d92 feat: resolved ico header, failing translation and lottie icon issues 2025-01-06 14:55:12 +05:30
=
5225f5136a feat: resolving issues on loader 2025-01-06 14:55:12 +05:30
=
398adfaf76 feat: resolved all ts issues 2025-01-06 14:55:12 +05:30
=
d77c26fa38 feat: resolved almost all ts issues 2025-01-06 14:55:11 +05:30
=
ef7b81734a feat: added kms routes 2025-01-06 14:55:11 +05:30
=
09b489a348 feat: completed cert routes 2025-01-06 14:55:11 +05:30
=
6b5c50def0 feat: added secret-manager routes 2025-01-06 14:55:11 +05:30
=
1f2d52176c feat: added org route 2025-01-06 14:55:11 +05:30
=
7002e297c8 feat: removed routes and added kms, cert to pages setup 2025-01-06 14:55:11 +05:30
=
71864a131f feat: changed to virtual route for secret-manager 2025-01-06 14:55:11 +05:30
=
9964d2ecaa feat: completed switch to virtual for org pages 2025-01-06 14:55:10 +05:30
=
3ebbaefc2a feat: changed to virtual route for auth and personal settings 2025-01-06 14:55:10 +05:30
=
dd5c494bdb feat: secret manager routes migration completed 2025-01-06 14:55:10 +05:30
=
bace8af5a1 feat: removed $organizationId from the routes 2025-01-06 14:55:10 +05:30
=
f56196b820 feat: completed project layout base 2025-01-06 14:55:10 +05:30
=
7042d73410 feat: upgrade react-query to v5 and changed hooks to staletime inf for prev context based ones 2025-01-06 14:55:09 +05:30
=
cb22ee315e feat: resolved a lot of ts issues, planning to migrate to v5 tanstack query 2025-01-06 14:55:09 +05:30
=
701eb7cfc6 feat: resolved breaking dev 2025-01-06 14:55:09 +05:30
=
bf8df14b01 feat: added hoc, resolved dropdown type issue 2025-01-06 14:55:09 +05:30
=
1ba8b6394b feat: added virtual route to mount the layout without wrapping again 2025-01-06 14:55:09 +05:30
=
c442c8483a feat: completed signup and personal settings ui 2025-01-06 14:55:09 +05:30
=
0435305a68 feat: resolved eslint issues and seperated org layout to make it simple 2025-01-06 14:55:09 +05:30
=
febf11f502 feat: added organization layout base with subscription and user loading 2025-01-06 14:55:08 +05:30
=
64fd15c32d feat: modified backend token endpoint to return organizationid 2025-01-06 14:55:08 +05:30
=
a2c9494d52 feat: added signup routes and moved server config to router context 2025-01-06 14:55:08 +05:30
=
18460e0678 feat: base for signup pages completed 2025-01-06 14:55:08 +05:30
=
3d03fece74 feat: first login page base completed 2025-01-06 14:55:08 +05:30
=
234e7eb9be feat: added ui components 2025-01-06 14:55:08 +05:30
=
04af313bf0 feat: added all old dependencies 2025-01-06 14:55:08 +05:30
=
9b038ccc45 feat: added tanstack router 2025-01-06 14:55:07 +05:30
=
9beb384546 feat: added tailwindcss with prettier tailwind support 2025-01-06 14:55:07 +05:30
=
12ec9b4b4e feat: added type check, lint and prettier 2025-01-06 14:55:07 +05:30
96b8e7fda8 Merge pull request #2930 from Infisical/vmatsiiako-wiki-patch-1 2025-01-01 18:12:48 -05:00
93b9108aa3 Update onboarding.mdx 2025-01-01 15:04:50 -08:00
99017ea1ae Merge pull request #2923 from Infisical/akhilmhdh-patch-azure
fix: resolved azure app config api not pulling all keys
2024-12-30 23:19:54 +08:00
f32588112e fix: resolved azure app config api not pulling all keys 2024-12-29 19:51:59 +05:30
f9b0b6700a Merge pull request #2922 from Infisical/feat/authenticate-key-vault-against-specific-tenant
feat: auth key vault against specific tenant
2024-12-29 08:18:52 -05:00
b45d9398f0 Merge pull request #2920 from Infisical/vmatsiiako-intercom-patch-1 2024-12-27 21:47:36 -05:00
1d1140237f Update azure-app-configuration.mdx 2024-12-27 00:59:49 -05:00
937560fd8d Update azure-app-configuration.mdx 2024-12-27 00:58:48 -05:00
5f4b7b9ea7 Merge pull request #2921 from Infisical/patch-azure-label
Azure App Config Label patch
2024-12-27 00:40:42 -05:00
05139820a5 add docs for label and refereces 2024-12-26 16:36:27 -05:00
7f6bc3ecfe misc: added additional note for azure app config 2024-12-26 19:07:45 +08:00
d8cc000ad1 feat: authenticate key vault against specific tenant 2024-12-26 19:07:16 +08:00
8fc03c06d9 handle empty label 2024-12-26 01:17:47 -05:00
50ceedf39f Remove intercom from docs 2024-12-25 16:40:45 -08:00
550096e72b Merge pull request #2787 from Mhammad-riyaz/riyaz/query
Fix Error When Clicking on CA Table Row After Viewing Certificate in Certificate Authorities Tab
2024-12-25 02:05:27 -08:00
1190ca2d77 docs: add note Approval Workflows as paid feature 2024-12-25 15:27:38 +07:00
2fb60201bc add not for dynamic secrets as paid feature 2024-12-25 14:17:27 +07:00
e763a6f683 misc: added default for metadataSyncMode 2024-12-23 21:29:51 +08:00
cb1b006118 misc: add secret metadata to batch update 2024-12-23 14:46:23 +08:00
356e7c5958 feat: added approval handling for metadata in replicate 2024-12-23 14:36:41 +08:00
634b500244 Merge pull request #2907 from Infisical/temp-hide-app-connection-docs 2024-12-20 18:53:20 -05:00
1a68765f15 feat: secret approval and replication 2024-12-21 02:51:47 +08:00
2f6dab3f63 Merge pull request #2901 from Infisical/ssh-cli-docs
Documentation for SSH Command in CLI
2024-12-20 08:49:38 -08:00
ae07d38c19 Merge remote-tracking branch 'origin/main' into feat/resource-metadata 2024-12-20 14:23:14 +08:00
e9564f5231 Fix ssh cli docs based on review 2024-12-19 22:12:32 -08:00
05cdca9202 Add docs for SSH CLI 2024-12-19 19:47:24 -08:00
025b4b8761 feat: aws secret manager metadata sync 2024-12-19 23:54:47 +08:00
ef688efc8d feat: integrated secret metadata to ui 2024-12-19 22:02:20 +08:00
8c98565715 feat: completed basic CRUD 2024-12-19 17:47:39 +08:00
e9358cd1d8 misc: backend setup + create secret metadata 2024-12-19 15:05:16 +08:00
4daaf80caa fix: better naming 2024-12-18 02:56:13 +01:00
cf7768d8e5 Merge branch 'daniel/k8-push-secret' into daniel/k8s-dynamic-secrets 2024-12-18 01:41:17 +01:00
e76d2f58ea fix: move fixes from different branch 2024-12-18 01:39:32 +01:00
36a13d182f requested changes 2024-12-12 06:13:55 +04:00
8b26670d73 fix(k8s): dynamic secret structual change 2024-12-11 22:25:02 +04:00
35d3581e23 fix(k8s): fixed dynamic secret bugs 2024-12-11 22:22:52 +04:00
0edf0dac98 fix(k8-operator): PushSecret CRd causing endless snapshot updates 2024-12-10 04:31:55 +04:00
a757ea22a1 fix(k8-operator): improvements for dynamic secrets 2024-12-09 00:22:22 +04:00
74df374998 Update infisicaldynamicsecret-crd.yaml 2024-12-08 23:24:38 +04:00
925a594a1b feat(k8-operator): dynamic secrets status conditions logging 2024-12-08 23:24:30 +04:00
36af975594 docs(k8-operator): k8's dynamic secret docs 2024-12-08 22:42:29 +04:00
ee54d460a0 fix(k8-operator): update charts 2024-12-08 21:30:28 +04:00
3c32d8dd90 fix(k8-operator): helm 2024-12-08 21:30:28 +04:00
9b50d451ec fix(k8-operator): common types support 2024-12-08 21:30:28 +04:00
7ede4e2cf5 fix(k8-operator): moved template 2024-12-08 21:30:28 +04:00
4552f0efa4 feat(k8-operator): dynamic secrets 2024-12-08 21:30:28 +04:00
0d35273857 feat(k8-operator): push secrets 2024-12-08 21:30:09 +04:00
5ad8dab250 fix(k8-operator): resource-based finalizer names 2024-12-08 21:29:45 +04:00
92a80b3314 fix(k8-operator): helm and cleanup 2024-12-08 21:21:14 +04:00
01dcbb0122 updated env slugs 2024-12-07 05:22:21 +04:00
adb0819102 Added RBAC 2024-12-07 05:22:21 +04:00
41ba111a69 Update PROJECT 2024-12-07 05:22:21 +04:00
1b48ce21be Update conditions.go 2024-12-07 05:22:21 +04:00
2f922d6343 fix: requested changes 2024-12-07 05:22:21 +04:00
e67b0540dd cleanup and resource seperation 2024-12-07 05:22:21 +04:00
a78455fde6 remove print 2024-12-07 05:22:21 +04:00
967dac9be6 docs(k8-operator): push secrets 2024-12-07 05:22:21 +04:00
922b245780 feat(k8-operator): push secrets 2024-12-07 05:22:21 +04:00
242595fceb changed getCaCerts key from ca-cert -> ca-certs 2024-11-24 21:05:39 +05:30
1603 changed files with 42222 additions and 44736 deletions

View File

@ -18,18 +18,18 @@ jobs:
steps:
- name: ☁️ Checkout source
uses: actions/checkout@v3
- name: 🔧 Setup Node 16
- name: 🔧 Setup Node 20
uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: 📦 Install dependencies
run: npm install
working-directory: frontend
- name: 🏗️ Run Type check
run: npm run type:check
run: npm run type:check
working-directory: frontend
- name: 🏗️ Run Link check
run: npm run lint:fix
run: npm run lint:fix
working-directory: frontend

View File

@ -97,7 +97,7 @@ jobs:
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-gamma-stage
@ -153,7 +153,7 @@ jobs:
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-platform
@ -204,7 +204,7 @@ jobs:
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-platform

View File

@ -7,3 +7,4 @@ docs/self-hosting/configuration/envars.mdx:generic-api-key:106
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451
docs/mint.json:generic-api-key:651
backend/src/ee/services/hsm/hsm-service.ts:generic-api-key:134
docs/documentation/platform/audit-log-streams/audit-log-streams.mdx:generic-api-key:104

View File

@ -8,7 +8,7 @@ FROM node:20-slim AS base
FROM base AS frontend-dependencies
WORKDIR /app
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
COPY frontend/package.json frontend/package-lock.json ./
# Install dependencies
RUN npm ci --only-production --ignore-scripts
@ -23,17 +23,16 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
COPY /frontend .
ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
ENV VITE_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
ENV VITE_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
ENV VITE_INTERCOM_ID $INTERCOM_ID
ARG INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
# Build
RUN npm run build
@ -44,20 +43,10 @@ WORKDIR /app
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images
VOLUME /app/.next/cache/images
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
COPY --from=frontend-builder /app/public ./public
RUN chown non-root-user:nodejs ./public/data
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/dist ./
USER non-root-user
ENV NEXT_TELEMETRY_DISABLED 1
##
## BACKEND
##
@ -137,7 +126,7 @@ RUN apt-get update && apt-get install -y \
freetds-dev \
freetds-bin \
tdsodbc \
openssh \
openssh-client \
&& rm -rf /var/lib/apt/lists/*
# Configure ODBC in production
@ -160,14 +149,11 @@ RUN chmod u+rx /usr/sbin/update-ca-certificates
## set pre baked keys
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ENV POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID=intercom-id
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
ENV INTERCOM_ID=$INTERCOM_ID
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
ENV CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
WORKDIR /
@ -192,4 +178,4 @@ EXPOSE 443
USER non-root-user
CMD ["./standalone-entrypoint.sh"]
CMD ["./standalone-entrypoint.sh"]

View File

@ -12,7 +12,7 @@ RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
COPY frontend/package.json frontend/package-lock.json ./
# Install dependencies
RUN npm ci --only-production --ignore-scripts
@ -27,17 +27,16 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
COPY /frontend .
ENV NODE_ENV production
ENV NEXT_PUBLIC_ENV production
ARG POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
ENV VITE_POSTHOG_HOST $POSTHOG_HOST
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
ENV VITE_POSTHOG_API_KEY $POSTHOG_API_KEY
ARG INTERCOM_ID
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
ENV VITE_INTERCOM_ID $INTERCOM_ID
ARG INFISICAL_PLATFORM_VERSION
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
# Build
RUN npm run build
@ -49,20 +48,10 @@ WORKDIR /app
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 non-root-user
RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images
VOLUME /app/.next/cache/images
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
COPY --from=frontend-builder /app/public ./public
RUN chown non-root-user:nodejs ./public/data
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/dist ./
USER non-root-user
ENV NEXT_TELEMETRY_DISABLED 1
##
## BACKEND
##
@ -159,14 +148,11 @@ RUN chmod u+rx /usr/sbin/update-ca-certificates
## set pre baked keys
ARG POSTHOG_API_KEY
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
ENV POSTHOG_API_KEY=$POSTHOG_API_KEY
ARG INTERCOM_ID=intercom-id
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
ENV INTERCOM_ID=$INTERCOM_ID
ARG CAPTCHA_SITE_KEY
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
ENV CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
COPY --from=backend-runner /app /backend
@ -189,4 +175,4 @@ EXPOSE 443
USER non-root-user
CMD ["./standalone-entrypoint.sh"]
CMD ["./standalone-entrypoint.sh"]

View File

@ -26,6 +26,7 @@
"@fastify/rate-limit": "^9.0.0",
"@fastify/request-context": "^5.1.0",
"@fastify/session": "^10.7.0",
"@fastify/static": "^7.0.4",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0",
@ -5406,6 +5407,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz",
"integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==",
"license": "MIT",
"engines": {
"node": ">=14"
}
@ -5545,6 +5547,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz",
"integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==",
"license": "MIT",
"dependencies": {
"@lukeed/ms": "^2.0.1",
"escape-html": "~1.0.3",
@ -5563,16 +5566,85 @@
}
},
"node_modules/@fastify/static": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz",
"integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==",
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz",
"integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==",
"license": "MIT",
"dependencies": {
"@fastify/accept-negotiator": "^1.0.0",
"@fastify/send": "^2.0.0",
"content-disposition": "^0.5.3",
"fastify-plugin": "^4.0.0",
"glob": "^8.0.1",
"p-limit": "^3.1.0"
"fastq": "^1.17.0",
"glob": "^10.3.4"
}
},
"node_modules/@fastify/static/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@fastify/static/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@fastify/static/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/@fastify/static/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@fastify/static/node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/@fastify/swagger": {
@ -5599,6 +5671,20 @@
"yaml": "^2.2.2"
}
},
"node_modules/@fastify/swagger-ui/node_modules/@fastify/static": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz",
"integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==",
"license": "MIT",
"dependencies": {
"@fastify/accept-negotiator": "^1.0.0",
"@fastify/send": "^2.0.0",
"content-disposition": "^0.5.3",
"fastify-plugin": "^4.0.0",
"glob": "^8.0.1",
"p-limit": "^3.1.0"
}
},
"node_modules/@google-cloud/kms": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
@ -6062,9 +6148,10 @@
"integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ=="
},
"node_modules/@lukeed/ms": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz",
"integrity": "sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==",
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
"integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
@ -13879,9 +13966,9 @@
}
},
"node_modules/express": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
@ -13903,7 +13990,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.10",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
@ -13918,6 +14005,10 @@
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-session": {
@ -17388,15 +17479,16 @@
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@ -18383,9 +18475,9 @@
"license": "ISC"
},
"node_modules/path-to-regexp": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/path-type": {

View File

@ -134,6 +134,7 @@
"@fastify/rate-limit": "^9.0.0",
"@fastify/request-context": "^5.1.0",
"@fastify/session": "^10.7.0",
"@fastify/static": "^7.0.4",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^2.1.0",
"@google-cloud/kms": "^4.5.0",

View File

@ -218,6 +218,9 @@ import {
TRateLimit,
TRateLimitInsert,
TRateLimitUpdate,
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate,
TSamlConfigs,
TSamlConfigsInsert,
TSamlConfigsUpdate,
@ -887,6 +890,11 @@ declare module "knex/types/tables" {
TProjectSplitBackfillIdsInsert,
TProjectSplitBackfillIdsUpdate
>;
[TableName.ResourceMetadata]: KnexOriginal.CompositeTableType<
TResourceMetadata,
TResourceMetadataInsert,
TResourceMetadataUpdate
>;
[TableName.AppConnection]: KnexOriginal.CompositeTableType<
TAppConnections,
TAppConnectionsInsert,

View File

@ -0,0 +1,40 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.ResourceMetadata))) {
await knex.schema.createTable(TableName.ResourceMetadata, (tb) => {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.string("key").notNullable();
tb.string("value", 1020).notNullable();
tb.uuid("orgId").notNullable();
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
tb.uuid("userId");
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
tb.uuid("identityId");
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
tb.uuid("secretId");
tb.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
tb.timestamps(true, true, true);
});
}
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (!hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.jsonb("secretMetadata");
});
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.ResourceMetadata);
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
if (hasSecretMetadataField) {
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
t.dropColumn("secretMetadata");
});
}
}

View File

@ -71,6 +71,7 @@ export * from "./project-user-additional-privilege";
export * from "./project-user-membership-roles";
export * from "./projects";
export * from "./rate-limit";
export * from "./resource-metadata";
export * from "./saml-configs";
export * from "./scim-tokens";
export * from "./secret-approval-policies";

View File

@ -80,6 +80,7 @@ export enum TableName {
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
// used by both identity and users
IdentityMetadata = "identity_metadata",
ResourceMetadata = "resource_metadata",
ScimToken = "scim_tokens",
AccessApprovalPolicy = "access_approval_policies",
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
@ -214,3 +215,12 @@ export enum ProjectType {
KMS = "kms",
SSH = "ssh"
}
export enum ActionProjectType {
SecretManager = ProjectType.SecretManager,
CertificateManager = ProjectType.CertificateManager,
KMS = ProjectType.KMS,
SSH = ProjectType.SSH,
// project operations that happen on all types
Any = "any"
}

View File

@ -0,0 +1,24 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const ResourceMetadataSchema = z.object({
id: z.string().uuid(),
key: z.string(),
value: z.string(),
orgId: z.string().uuid(),
userId: z.string().uuid().nullable().optional(),
identityId: z.string().uuid().nullable().optional(),
secretId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TResourceMetadata = z.infer<typeof ResourceMetadataSchema>;
export type TResourceMetadataInsert = Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>;
export type TResourceMetadataUpdate = Partial<Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>>;

View File

@ -24,7 +24,8 @@ export const SecretApprovalRequestsSecretsV2Schema = z.object({
requestId: z.string().uuid(),
op: z.string(),
secretId: z.string().uuid().nullable().optional(),
secretVersion: z.string().uuid().nullable().optional()
secretVersion: z.string().uuid().nullable().optional(),
secretMetadata: z.unknown().nullable().optional()
});
export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>;

View File

@ -22,6 +22,7 @@ import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-rou
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
import { registerSecretRotationRouter } from "./secret-rotation-router";
import { registerSecretRouter } from "./secret-router";
import { registerSecretScanningRouter } from "./secret-scanning-router";
import { registerSecretVersionRouter } from "./secret-version-router";
import { registerSnapshotRouter } from "./snapshot-router";
@ -92,6 +93,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerLdapRouter, { prefix: "/ldap" });
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
await server.register(registerSecretRouter, { prefix: "/secrets" });
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
await server.register(registerGroupRouter, { prefix: "/groups" });
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });

View File

@ -84,7 +84,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
samlConfig.audience = `spn:${ssoConfig.issuer}`;
}
}
if (ssoConfig.authProvider === SamlProviders.GOOGLE_SAML) {
if (
ssoConfig.authProvider === SamlProviders.GOOGLE_SAML ||
ssoConfig.authProvider === SamlProviders.AUTH0_SAML
) {
samlConfig.wantAssertionsSigned = false;
}
@ -123,7 +126,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
`email: ${email} firstName: ${profile.firstName as string}`
);
throw new Error("Invalid saml request. Missing email or first name");
throw new BadRequestError({
message:
"Missing email or first name. Please double check your SAML attribute mapping for the selected provider."
});
}
const userMetadata = Object.keys(profile.attributes || {})

View File

@ -12,6 +12,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge(
UsersSchema.pick({
@ -274,6 +275,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
.extend({
op: z.string(),
tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish(),
secret: z
.object({
id: z.string(),
@ -291,7 +293,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
secretKey: z.string(),
secretValue: z.string().optional(),
secretComment: z.string().optional(),
tags: tagSchema
tags: tagSchema,
secretMetadata: ResourceMetadataSchema.nullish()
})
.optional()
})

View File

@ -0,0 +1,71 @@
import z from "zod";
import { ProjectPermissionActions } from "@app/ee/services/permission/project-permission";
import { RAW_SECRETS } from "@app/lib/api-docs";
import { removeTrailingSlash } from "@app/lib/fn";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
const AccessListEntrySchema = z
.object({
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
id: z.string(),
membershipId: z.string(),
name: z.string()
})
.array();
export const registerSecretRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/:secretName/access-list",
config: {
rateLimit: readLimit
},
schema: {
description: "Get list of users, machine identities, and groups with access to a secret",
security: [
{
bearerAuth: []
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.secretName)
}),
querystring: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.workspaceId),
environment: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.environment),
secretPath: z
.string()
.trim()
.default("/")
.transform(removeTrailingSlash)
.describe(RAW_SECRETS.GET_ACCESS_LIST.secretPath)
}),
response: {
200: z.object({
groups: AccessListEntrySchema,
identities: AccessListEntrySchema,
users: AccessListEntrySchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { secretName } = req.params;
const { secretPath, environment, workspaceId: projectId } = req.query;
return server.services.secret.getSecretAccessList({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
secretPath,
environment,
projectId,
secretName
});
}
});
};

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@ -87,14 +87,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -193,7 +193,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone in the project should be able to get the policies.
await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
return accessApprovalPolicies;
@ -237,14 +244,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!accessApprovalPolicy) {
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
accessApprovalPolicy.projectId,
projectId: accessApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@ -321,14 +328,14 @@ export const accessApprovalPolicyServiceFactory = ({
const policy = await accessApprovalPolicyDAL.findById(policyId);
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
policy.projectId,
projectId: policy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
@ -372,13 +379,14 @@ export const accessApprovalPolicyServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
@ -411,13 +419,14 @@ export const accessApprovalPolicyServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
policy.projectId,
projectId: policy.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);

View File

@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@ -100,13 +100,14 @@ export const accessApprovalRequestServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone can create an access approval request.
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
@ -213,7 +214,7 @@ export const accessApprovalRequestServiceFactory = ({
);
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
const approvalUrl = `${cfg.SITE_URL}/project/${project.id}/approval`;
const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
await triggerSlackNotification({
projectId: project.id,
@ -273,13 +274,14 @@ export const accessApprovalRequestServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}
@ -318,13 +320,14 @@ export const accessApprovalRequestServiceFactory = ({
});
}
const { membership, hasRole } = await permissionService.getProjectPermission(
const { membership, hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
accessApprovalRequest.projectId,
projectId: accessApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@ -422,13 +425,14 @@ export const accessApprovalRequestServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
}

View File

@ -100,10 +100,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
// Filter by date range
if (startDate) {
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate);
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" >= ?::timestamptz`, [startDate]);
}
if (endDate) {
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" <= ?::timestamptz`, [endDate]);
}
// we timeout long running queries to prevent DB resource issues (2 minutes)

View File

@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
@ -26,13 +27,14 @@ export const auditLogServiceFactory = ({
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
// Filter logs for specific project
if (filter.projectId) {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
filter.projectId,
projectId: filter.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
} else {
// Organization-wide logs

View File

@ -31,7 +31,7 @@ export type TListProjectAuditLogDTO = {
export type TCreateAuditLogDTO = {
event: Event;
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor;
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor | UnknownUserActor;
orgId?: string;
projectId?: string;
} & BaseAuthData;
@ -229,7 +229,10 @@ export enum EventType {
GET_APP_CONNECTION = "get-app-connection",
CREATE_APP_CONNECTION = "create-app-connection",
UPDATE_APP_CONNECTION = "update-app-connection",
DELETE_APP_CONNECTION = "delete-app-connection"
DELETE_APP_CONNECTION = "delete-app-connection",
CREATE_SHARED_SECRET = "create-shared-secret",
DELETE_SHARED_SECRET = "delete-shared-secret",
READ_SHARED_SECRET = "read-shared-secret"
}
interface UserActorMetadata {
@ -252,6 +255,8 @@ interface ScimClientActorMetadata {}
interface PlatformActorMetadata {}
interface UnknownUserActorMetadata {}
export interface UserActor {
type: ActorType.USER;
metadata: UserActorMetadata;
@ -267,6 +272,11 @@ export interface PlatformActor {
metadata: PlatformActorMetadata;
}
export interface UnknownUserActor {
type: ActorType.UNKNOWN_USER;
metadata: UnknownUserActorMetadata;
}
export interface IdentityActor {
type: ActorType.IDENTITY;
metadata: IdentityActorMetadata;
@ -1907,6 +1917,35 @@ interface DeleteAppConnectionEvent {
};
}
interface CreateSharedSecretEvent {
type: EventType.CREATE_SHARED_SECRET;
metadata: {
id: string;
accessType: string;
name?: string;
expiresAfterViews?: number;
usingPassword: boolean;
expiresAt: string;
};
}
interface DeleteSharedSecretEvent {
type: EventType.DELETE_SHARED_SECRET;
metadata: {
id: string;
name?: string;
};
}
interface ReadSharedSecretEvent {
type: EventType.READ_SHARED_SECRET;
metadata: {
id: string;
name?: string;
accessType: string;
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
@ -2083,4 +2122,7 @@ export type Event =
| GetAppConnectionEvent
| CreateAppConnectionEvent
| UpdateAppConnectionEvent
| DeleteAppConnectionEvent;
| DeleteAppConnectionEvent
| CreateSharedSecretEvent
| DeleteSharedSecretEvent
| ReadSharedSecretEvent;

View File

@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -66,13 +67,14 @@ export const certificateAuthorityCrlServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
@ -67,14 +67,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -147,14 +147,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -227,14 +227,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -297,13 +297,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -339,13 +340,14 @@ export const dynamicSecretLeaseServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.Lease,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })

View File

@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
@ -73,14 +73,14 @@ export const dynamicSecretServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.CreateRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -145,14 +145,14 @@ export const dynamicSecretServiceFactory = ({
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.EditRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -229,14 +229,14 @@ export const dynamicSecretServiceFactory = ({
const projectId = project.id;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -290,13 +290,14 @@ export const dynamicSecretServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -340,13 +341,14 @@ export const dynamicSecretServiceFactory = ({
isInternal
}: TListDynamicSecretsMultiEnvDTO) => {
if (!isInternal) {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// verify user has access to each env in request
environmentSlugs.forEach((environmentSlug) =>
@ -383,13 +385,14 @@ export const dynamicSecretServiceFactory = ({
search,
projectId
}: TGetDynamicSecretsCountDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -431,13 +434,14 @@ export const dynamicSecretServiceFactory = ({
projectId = project.id;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
@ -462,13 +466,14 @@ export const dynamicSecretServiceFactory = ({
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO,
actor: OrgServiceActor
) => {
const { permission } = await permissionService.getProjectPermission(
actor.type,
actor.id,
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId,
actor.authMethod,
actor.orgId
);
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
permission.can(
@ -507,13 +512,14 @@ export const dynamicSecretServiceFactory = ({
...params
}: TListDynamicSecretsMultiEnvDTO) => {
if (!isInternal) {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// verify user has access to each env in request
environmentSlugs.forEach((environmentSlug) =>

View File

@ -34,6 +34,8 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
const isMsSQLClient = providerInputs.client === SqlProviders.MsSQL;
const db = knex({
client: providerInputs.client,
connection: {
@ -43,7 +45,16 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
user: providerInputs.username,
password: providerInputs.password,
ssl,
pool: { min: 0, max: 1 }
pool: { min: 0, max: 1 },
// @ts-expect-error this is because of knexjs type signature issue. This is directly passed to driver
// https://github.com/knex/knex/blob/b6507a7129d2b9fafebf5f831494431e64c6a8a0/lib/dialects/mssql/index.js#L66
// https://github.com/tediousjs/tedious/blob/ebb023ed90969a7ec0e4b036533ad52739d921f7/test/config.ci.ts#L19
options: isMsSQLClient
? {
trustServerCertificate: !providerInputs.ca,
cryptoCredentialsDetails: providerInputs.ca ? { ca: providerInputs.ca } : {}
}
: undefined
},
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
});

View File

@ -1,7 +1,9 @@
import { KMSServiceException } from "@aws-sdk/client-kms";
import { STSServiceException } from "@aws-sdk/client-sts";
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
@ -71,7 +73,16 @@ export const externalKmsServiceFactory = ({
switch (provider.type) {
case KmsProviders.Aws:
{
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs }).catch((error) => {
if (error instanceof STSServiceException || error instanceof KMSServiceException) {
throw new InternalServerError({
message: error.message ? `AWS error: ${error.message}` : ""
});
}
throw error;
});
// if missing kms key this generate a new kms key id and returns new provider input
const newProviderInput = await externalKms.generateInputKmsKey();
sanitizedProviderInput = JSON.stringify(newProviderInput);

View File

@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import { packRules } from "@casl/ability/extra";
import ms from "ms";
import { TableName } from "@app/db/schemas";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
@ -55,24 +55,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -135,24 +137,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -215,24 +219,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
@ -260,13 +266,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
@ -294,13 +301,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
@ -329,13 +337,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })

View File

@ -2,6 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { ActionProjectType } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
@ -62,25 +63,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -143,26 +146,28 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -242,25 +247,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityProjectMembership.identityId,
identityProjectMembership.projectId,
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
if (!hasRequiredPriviledges)
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
@ -299,13 +306,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Identity, { identityId })
@ -341,13 +349,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -24,6 +24,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
rbac: false,
customRateLimits: false,
customAlerts: false,
secretAccessInsights: false,
auditLogs: false,
auditLogsRetentionDays: 0,
auditLogStreams: false,

View File

@ -246,8 +246,7 @@ export const licenseServiceFactory = ({
};
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
const plan = await getPlan(orgId, projectId);
return plan;
};

View File

@ -48,6 +48,7 @@ export type TFeatureSet = {
samlSSO: false;
hsm: false;
oidcSSO: false;
secretAccessInsights: false;
scim: false;
ldap: false;
groups: false;

View File

@ -125,6 +125,404 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectGroupPermissions = async (projectId: string) => {
try {
const docs = await db
.replicaNode()(TableName.GroupProjectMembership)
.join(TableName.Groups, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`)
.join(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin<TProjectRoles>(
{ groupCustomRoles: TableName.ProjectRoles },
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`groupCustomRoles.id`
)
.where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId)
.select(
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
db.ref("id").withSchema(TableName.Groups).as("groupId"),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("slug").withSchema("groupCustomRoles").as("groupProjectMembershipRoleCustomRoleSlug"),
db.ref("permissions").withSchema("groupCustomRoles").as("groupProjectMembershipRolePermission"),
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRoleId"),
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRole"),
db
.ref("customRoleId")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleCustomRoleId"),
db
.ref("isTemporary")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleIsTemporary"),
db
.ref("temporaryMode")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryMode"),
db
.ref("temporaryRange")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("groupProjectMembershipRoleTemporaryAccessEndTime")
);
const groupPermissions = sqlNestRelationships({
data: docs,
key: "groupId",
parentMapper: ({ groupId, groupName, membershipId }) => ({
groupId,
username: groupName,
id: membershipId
}),
childrenMapper: [
{
key: "groupProjectMembershipRoleId",
label: "groupRoles" as const,
mapper: ({
groupProjectMembershipRoleId,
groupProjectMembershipRole,
groupProjectMembershipRolePermission,
groupProjectMembershipRoleCustomRoleSlug,
groupProjectMembershipRoleIsTemporary,
groupProjectMembershipRoleTemporaryMode,
groupProjectMembershipRoleTemporaryAccessEndTime,
groupProjectMembershipRoleTemporaryAccessStartTime,
groupProjectMembershipRoleTemporaryRange
}) => ({
id: groupProjectMembershipRoleId,
role: groupProjectMembershipRole,
customRoleSlug: groupProjectMembershipRoleCustomRoleSlug,
permissions: groupProjectMembershipRolePermission,
temporaryRange: groupProjectMembershipRoleTemporaryRange,
temporaryMode: groupProjectMembershipRoleTemporaryMode,
temporaryAccessStartTime: groupProjectMembershipRoleTemporaryAccessStartTime,
temporaryAccessEndTime: groupProjectMembershipRoleTemporaryAccessEndTime,
isTemporary: groupProjectMembershipRoleIsTemporary
})
}
]
});
return groupPermissions
.map((groupPermission) => {
if (!groupPermission) return undefined;
const activeGroupRoles =
groupPermission?.groupRoles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
return {
...groupPermission,
roles: activeGroupRoles
};
})
.filter((item): item is NonNullable<typeof item> => Boolean(item));
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectGroupPermissions" });
}
};
const getProjectUserPermissions = async (projectId: string) => {
try {
const docs = await db
.replicaNode()(TableName.Users)
.where("isGhost", "=", false)
.leftJoin(TableName.GroupProjectMembership, (queryBuilder) => {
void queryBuilder.on(`${TableName.GroupProjectMembership}.projectId`, db.raw("?", [projectId]));
})
.leftJoin(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin<TProjectRoles>(
{ groupCustomRoles: TableName.ProjectRoles },
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`groupCustomRoles.id`
)
.join(TableName.ProjectMembership, (queryBuilder) => {
void queryBuilder
.on(`${TableName.ProjectMembership}.projectId`, db.raw("?", [projectId]))
.andOn(`${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`);
})
.leftJoin(
TableName.ProjectUserMembershipRole,
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
`${TableName.ProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.ProjectUserMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.leftJoin(TableName.ProjectUserAdditionalPrivilege, (queryBuilder) => {
void queryBuilder
.on(`${TableName.ProjectUserAdditionalPrivilege}.projectId`, db.raw("?", [projectId]))
.andOn(`${TableName.ProjectUserAdditionalPrivilege}.userId`, `${TableName.Users}.id`);
})
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
void queryBuilder
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
.andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`);
})
.select(
db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("username").withSchema(TableName.Users).as("username"),
// groups specific
db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"),
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipUpdatedAt"),
db.ref("slug").withSchema("groupCustomRoles").as("userGroupProjectMembershipRoleCustomRoleSlug"),
db.ref("permissions").withSchema("groupCustomRoles").as("userGroupProjectMembershipRolePermission"),
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRoleId"),
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRole"),
db
.ref("customRoleId")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleCustomRoleId"),
db
.ref("isTemporary")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleIsTemporary"),
db
.ref("temporaryMode")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryMode"),
db
.ref("temporaryRange")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.GroupProjectMembershipRole)
.as("userGroupProjectMembershipRoleTemporaryAccessEndTime"),
// user specific
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("userProjectMembershipRoleCustomRoleSlug"),
db.ref("permissions").withSchema(TableName.ProjectRoles).as("userProjectCustomRolePermission"),
db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRoleId"),
db.ref("role").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRole"),
db
.ref("temporaryMode")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryMode"),
db
.ref("isTemporary")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleIsTemporary"),
db
.ref("temporaryRange")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserMembershipRole)
.as("userProjectMembershipRoleTemporaryAccessEndTime"),
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesId"),
db
.ref("permissions")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesPermissions"),
db
.ref("temporaryMode")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryMode"),
db
.ref("isTemporary")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesIsTemporary"),
db
.ref("temporaryRange")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryRange"),
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesUserId"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
// general
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project),
db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("id").withSchema(TableName.Project).as("projectId")
);
const userPermissions = sqlNestRelationships({
data: docs,
key: "userId",
parentMapper: ({
orgId,
username,
orgAuthEnforced,
membershipId,
groupMembershipId,
membershipCreatedAt,
groupMembershipCreatedAt,
groupMembershipUpdatedAt,
membershipUpdatedAt,
projectType,
userId
}) => ({
orgId,
orgAuthEnforced,
userId,
projectId,
username,
projectType,
id: membershipId || groupMembershipId,
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt
}),
childrenMapper: [
{
key: "userGroupProjectMembershipRoleId",
label: "userGroupRoles" as const,
mapper: ({
userGroupProjectMembershipRoleId,
userGroupProjectMembershipRole,
userGroupProjectMembershipRolePermission,
userGroupProjectMembershipRoleCustomRoleSlug,
userGroupProjectMembershipRoleIsTemporary,
userGroupProjectMembershipRoleTemporaryMode,
userGroupProjectMembershipRoleTemporaryAccessEndTime,
userGroupProjectMembershipRoleTemporaryAccessStartTime,
userGroupProjectMembershipRoleTemporaryRange
}) => ({
id: userGroupProjectMembershipRoleId,
role: userGroupProjectMembershipRole,
customRoleSlug: userGroupProjectMembershipRoleCustomRoleSlug,
permissions: userGroupProjectMembershipRolePermission,
temporaryRange: userGroupProjectMembershipRoleTemporaryRange,
temporaryMode: userGroupProjectMembershipRoleTemporaryMode,
temporaryAccessStartTime: userGroupProjectMembershipRoleTemporaryAccessStartTime,
temporaryAccessEndTime: userGroupProjectMembershipRoleTemporaryAccessEndTime,
isTemporary: userGroupProjectMembershipRoleIsTemporary
})
},
{
key: "userProjectMembershipRoleId",
label: "projectMembershipRoles" as const,
mapper: ({
userProjectMembershipRoleId,
userProjectMembershipRole,
userProjectCustomRolePermission,
userProjectMembershipRoleIsTemporary,
userProjectMembershipRoleTemporaryMode,
userProjectMembershipRoleTemporaryRange,
userProjectMembershipRoleTemporaryAccessEndTime,
userProjectMembershipRoleTemporaryAccessStartTime,
userProjectMembershipRoleCustomRoleSlug
}) => ({
id: userProjectMembershipRoleId,
role: userProjectMembershipRole,
customRoleSlug: userProjectMembershipRoleCustomRoleSlug,
permissions: userProjectCustomRolePermission,
temporaryRange: userProjectMembershipRoleTemporaryRange,
temporaryMode: userProjectMembershipRoleTemporaryMode,
temporaryAccessStartTime: userProjectMembershipRoleTemporaryAccessStartTime,
temporaryAccessEndTime: userProjectMembershipRoleTemporaryAccessEndTime,
isTemporary: userProjectMembershipRoleIsTemporary
})
},
{
key: "userAdditionalPrivilegesId",
label: "additionalPrivileges" as const,
mapper: ({
userAdditionalPrivilegesId,
userAdditionalPrivilegesPermissions,
userAdditionalPrivilegesIsTemporary,
userAdditionalPrivilegesTemporaryMode,
userAdditionalPrivilegesTemporaryRange,
userAdditionalPrivilegesTemporaryAccessEndTime,
userAdditionalPrivilegesTemporaryAccessStartTime
}) => ({
id: userAdditionalPrivilegesId,
permissions: userAdditionalPrivilegesPermissions,
temporaryRange: userAdditionalPrivilegesTemporaryRange,
temporaryMode: userAdditionalPrivilegesTemporaryMode,
temporaryAccessStartTime: userAdditionalPrivilegesTemporaryAccessStartTime,
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
isTemporary: userAdditionalPrivilegesIsTemporary
})
},
{
key: "metadataId",
label: "metadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return userPermissions
.map((userPermission) => {
if (!userPermission) return undefined;
if (!userPermission?.userGroupRoles?.[0] && !userPermission?.projectMembershipRoles?.[0]) return undefined;
// when introducting cron mode change it here
const activeRoles =
userPermission?.projectMembershipRoles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
const activeGroupRoles =
userPermission?.userGroupRoles?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
const activeAdditionalPrivileges =
userPermission?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
return {
...userPermission,
roles: [...activeRoles, ...activeGroupRoles],
additionalPrivileges: activeAdditionalPrivileges
};
})
.filter((item): item is NonNullable<typeof item> => Boolean(item));
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectUserPermissions" });
}
};
const getProjectPermission = async (userId: string, projectId: string) => {
try {
const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId");
@ -414,6 +812,163 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectIdentityPermissions = async (projectId: string) => {
try {
const docs = await db
.replicaNode()(TableName.IdentityProjectMembership)
.join(
TableName.IdentityProjectMembershipRole,
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
`${TableName.IdentityProjectMembership}.id`
)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityProjectMembership}.identityId`)
.leftJoin(
TableName.ProjectRoles,
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.leftJoin(
TableName.IdentityProjectAdditionalPrivilege,
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
`${TableName.IdentityProjectMembership}.id`
)
.join(
// Join the Project table to later select orgId
TableName.Project,
`${TableName.IdentityProjectMembership}.projectId`,
`${TableName.Project}.id`
)
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
void queryBuilder
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
.andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`);
})
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
.select(
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
db.ref("id").withSchema(TableName.Identity).as("identityId"),
db.ref("name").withSchema(TableName.Identity).as("identityName"),
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
db.ref("type").withSchema(TableName.Project).as("projectType"),
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("permissions").withSchema(TableName.ProjectRoles),
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
db
.ref("temporaryMode")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryMode"),
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
db
.ref("temporaryRange")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
.as("identityApTemporaryAccessEndTime"),
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
);
const permissions = sqlNestRelationships({
data: docs,
key: "identityId",
parentMapper: ({
membershipId,
membershipCreatedAt,
membershipUpdatedAt,
orgId,
identityName,
projectType,
identityId
}) => ({
id: membershipId,
identityId,
username: identityName,
projectId,
createdAt: membershipCreatedAt,
updatedAt: membershipUpdatedAt,
orgId,
projectType,
// just a prefilled value
orgAuthEnforced: false
}),
childrenMapper: [
{
key: "id",
label: "roles" as const,
mapper: (data) =>
IdentityProjectMembershipRoleSchema.extend({
permissions: z.unknown(),
customRoleSlug: z.string().optional().nullable()
}).parse(data)
},
{
key: "identityApId",
label: "additionalPrivileges" as const,
mapper: ({
identityApId,
identityApPermissions,
identityApIsTemporary,
identityApTemporaryMode,
identityApTemporaryRange,
identityApTemporaryAccessEndTime,
identityApTemporaryAccessStartTime
}) => ({
id: identityApId,
permissions: identityApPermissions,
temporaryRange: identityApTemporaryRange,
temporaryMode: identityApTemporaryMode,
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
isTemporary: identityApIsTemporary
})
},
{
key: "metadataId",
label: "metadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return permissions
.map((permission) => {
if (!permission) {
return undefined;
}
// when introducting cron mode change it here
const activeRoles = permission?.roles.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
const activeAdditionalPrivileges = permission?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
return { ...permission, roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
})
.filter((item): item is NonNullable<typeof item> => Boolean(item));
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectIdentityPermissions" });
}
};
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
try {
const docs = await db
@ -568,6 +1123,9 @@ export const permissionDALFactory = (db: TDbClient) => {
getOrgPermission,
getOrgIdentityPermission,
getProjectPermission,
getProjectIdentityPermission
getProjectIdentityPermission,
getProjectUserPermissions,
getProjectIdentityPermissions,
getProjectGroupPermissions
};
};

View File

@ -1,3 +1,6 @@
import { ActionProjectType } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
export type TBuildProjectPermissionDTO = {
permissions?: unknown;
role: string;
@ -7,3 +10,34 @@ export type TBuildOrgPermissionDTO = {
permissions?: unknown;
role: string;
}[];
export type TGetUserProjectPermissionArg = {
userId: string;
projectId: string;
authMethod: ActorAuthMethod;
actionProjectType: ActionProjectType;
userOrgId?: string;
};
export type TGetIdentityProjectPermissionArg = {
identityId: string;
projectId: string;
identityOrgId?: string;
actionProjectType: ActionProjectType;
};
export type TGetServiceTokenProjectPermissionArg = {
serviceTokenId: string;
projectId: string;
actorOrgId?: string;
actionProjectType: ActionProjectType;
};
export type TGetProjectPermissionArg = {
actor: ActorType;
actorId: string;
projectId: string;
actorAuthMethod: ActorAuthMethod;
actorOrgId?: string;
actionProjectType: ActionProjectType;
};

View File

@ -4,9 +4,9 @@ import { MongoQuery } from "@ucast/mongo2js";
import handlebars from "handlebars";
import {
ActionProjectType,
OrgMembershipRole,
ProjectMembershipRole,
ProjectType,
ServiceTokenScopes,
TIdentityProjectMemberships,
TProjectMemberships
@ -23,7 +23,14 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal";
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-service-types";
import {
TBuildOrgPermissionDTO,
TBuildProjectPermissionDTO,
TGetIdentityProjectPermissionArg,
TGetProjectPermissionArg,
TGetServiceTokenProjectPermissionArg,
TGetUserProjectPermissionArg
} from "./permission-service-types";
import {
buildServiceTokenProjectPermission,
projectAdminPermissions,
@ -193,12 +200,13 @@ export const permissionServiceFactory = ({
};
// user permission for a project in an organization
const getUserProjectPermission = async (
userId: string,
projectId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
): Promise<TProjectPermissionRT<ActorType.USER>> => {
const getUserProjectPermission = async ({
userId,
projectId,
authMethod,
userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
@ -219,6 +227,12 @@ export const permissionServiceFactory = ({
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
// join two permissions and pass to build the final permission set
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
@ -256,13 +270,6 @@ export const permissionServiceFactory = ({
return {
permission,
membership: userProjectPermission,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== userProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
});
}
},
hasRole: (role: string) =>
userProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
@ -270,11 +277,12 @@ export const permissionServiceFactory = ({
};
};
const getIdentityProjectPermission = async (
identityId: string,
projectId: string,
identityOrgId: string | undefined
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const getIdentityProjectPermission = async ({
identityId,
projectId,
identityOrgId,
actionProjectType
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
if (!identityProjectPermission)
throw new ForbiddenRequestError({
@ -293,6 +301,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
}
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
const rolePermissions =
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
@ -331,13 +345,6 @@ export const permissionServiceFactory = ({
return {
permission,
membership: identityProjectPermission,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== identityProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
});
}
},
hasRole: (role: string) =>
identityProjectPermission.roles.findIndex(
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
@ -345,11 +352,12 @@ export const permissionServiceFactory = ({
};
};
const getServiceTokenProjectPermission = async (
serviceTokenId: string,
projectId: string,
actorOrgId: string | undefined
) => {
const getServiceTokenProjectPermission = async ({
serviceTokenId,
projectId,
actorOrgId,
actionProjectType
}: TGetServiceTokenProjectPermissionArg) => {
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
@ -373,17 +381,16 @@ export const permissionServiceFactory = ({
});
}
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
throw new BadRequestError({
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
});
}
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
return {
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
membership: undefined,
ForbidOnInvalidProjectType: (productType: ProjectType) => {
if (productType !== serviceTokenProject.type) {
throw new BadRequestError({
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${productType} are not allowed.`
});
}
}
membership: undefined
};
};
@ -392,7 +399,6 @@ export const permissionServiceFactory = ({
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: undefined;
hasRole: (arg: string) => boolean;
ForbidOnInvalidProjectType: (type: ProjectType) => void;
} // service token doesn't have both membership and roles
: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
@ -402,23 +408,156 @@ export const permissionServiceFactory = ({
roles: Array<{ role: string }>;
};
hasRole: (role: string) => boolean;
ForbidOnInvalidProjectType: (type: ProjectType) => void;
};
const getProjectPermission = async <T extends ActorType>(
type: T,
id: string,
projectId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
): Promise<TProjectPermissionRT<T>> => {
switch (type) {
const getProjectPermissions = async (projectId: string) => {
// fetch user permissions
const rawUserProjectPermissions = await permissionDAL.getProjectUserPermissions(projectId);
const userPermissions = rawUserProjectPermissions.map((userProjectPermission) => {
const rolePermissions =
userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
role: ProjectMembershipRole.Custom,
permissions
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
objectify(
userProjectPermission.metadata,
(i) => i.key,
(i) => i.value
)
);
const interpolateRules = templatedRules(
{
identity: {
id: userProjectPermission.userId,
username: userProjectPermission.username,
metadata: metadataKeyValuePair
}
},
{ data: false }
);
const permission = createMongoAbility<ProjectPermissionSet>(
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
{
conditionsMatcher
}
);
return {
permission,
id: userProjectPermission.userId,
name: userProjectPermission.username,
membershipId: userProjectPermission.id
};
});
// fetch identity permissions
const rawIdentityProjectPermissions = await permissionDAL.getProjectIdentityPermissions(projectId);
const identityPermissions = rawIdentityProjectPermissions.map((identityProjectPermission) => {
const rolePermissions =
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges =
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
role: ProjectMembershipRole.Custom,
permissions
})) || [];
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
objectify(
identityProjectPermission.metadata,
(i) => i.key,
(i) => i.value
)
);
const interpolateRules = templatedRules(
{
identity: {
id: identityProjectPermission.identityId,
username: identityProjectPermission.username,
metadata: metadataKeyValuePair
}
},
{ data: false }
);
const permission = createMongoAbility<ProjectPermissionSet>(
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
{
conditionsMatcher
}
);
return {
permission,
id: identityProjectPermission.identityId,
name: identityProjectPermission.username,
membershipId: identityProjectPermission.id
};
});
// fetch group permissions
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId);
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {
const rolePermissions =
groupProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const rules = buildProjectPermissionRules(rolePermissions);
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
conditionsMatcher
});
return {
permission,
id: groupProjectPermission.groupId,
name: groupProjectPermission.username,
membershipId: groupProjectPermission.id
};
});
return {
userPermissions,
identityPermissions,
groupPermissions
};
};
const getProjectPermission = async <T extends ActorType>({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
switch (actor) {
case ActorType.USER:
return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
return getUserProjectPermission({
userId: actorId,
projectId,
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>;
case ActorType.SERVICE:
return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
return getServiceTokenProjectPermission({
serviceTokenId: actorId,
projectId,
actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>;
case ActorType.IDENTITY:
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
return getIdentityProjectPermission({
identityId: actorId,
projectId,
identityOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>;
default:
throw new BadRequestError({
message: "Invalid actor provided",
@ -455,6 +594,7 @@ export const permissionServiceFactory = ({
getOrgPermission,
getUserProjectPermission,
getProjectPermission,
getProjectPermissions,
getOrgPermissionByRole,
getProjectPermissionByRole,
buildOrgPermission,

View File

@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import ms from "ms";
import { TableName } from "@app/db/schemas";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
@ -55,21 +55,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
ActorType.USER,
projectMembership.userId,
projectMembership.projectId,
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId: projectMembership.userId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -140,21 +142,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
ActorType.USER,
projectMembership.userId,
projectMembership.projectId,
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId: projectMembership.userId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
// we need to validate that the privilege given is not higher than the assigning users permission
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
@ -224,13 +228,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
@ -260,13 +265,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return {
@ -286,13 +292,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectMembership.projectId,
projectId: projectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(

View File

@ -6,7 +6,8 @@ export enum SamlProviders {
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml",
GOOGLE_SAML = "google-saml",
KEYCLOAK_SAML = "keycloak-saml"
KEYCLOAK_SAML = "keycloak-saml",
AUTH0_SAML = "auth0-saml"
}
export type TCreateSamlCfgDTO = {

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import picomatch from "picomatch";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -79,14 +79,14 @@ export const secretApprovalPolicyServiceFactory = ({
if (!groupApprovers.length && approvals > approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
@ -193,14 +193,14 @@ export const secretApprovalPolicyServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
secretApprovalPolicy.projectId,
projectId: secretApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
const plan = await licenseService.getPlan(actorOrgId);
@ -288,14 +288,14 @@ export const secretApprovalPolicyServiceFactory = ({
if (!sapPolicy)
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sapPolicy.projectId,
projectId: sapPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
@ -328,13 +328,14 @@ export const secretApprovalPolicyServiceFactory = ({
actorAuthMethod,
projectId
}: TListSapDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
@ -372,7 +373,14 @@ export const secretApprovalPolicyServiceFactory = ({
environment,
secretPath
}: TGetBoardSapDTO) => {
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
return getSecretApprovalPolicy(projectId, environment, secretPath);
};
@ -392,13 +400,14 @@ export const secretApprovalPolicyServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sapPolicy.projectId,
projectId: sapPolicy.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);

View File

@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({
firstName: reviewerUser.firstName,
projectName: project.name,
organizationName: project.organization.name,
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval?requestId=${secretApprovalRequest.id}`
approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval?requestId=${secretApprovalRequest.id}`
},
template: SmtpTemplates.SecretApprovalRequestNeedsReview
});

View File

@ -256,6 +256,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
db.ref("id").withSchema("secVerTag")
)
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2))
.select({
secVerTagId: "secVerTag.id",
@ -279,6 +280,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
);
const formatedDoc = sqlNestRelationships({
data: doc,
@ -338,9 +344,19 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
})
}
]
},
{
key: "metadataId",
label: "oldSecretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
...el,
secret: secret?.[0],

View File

@ -1,8 +1,8 @@
import { ForbiddenError, subject } from "@casl/ability";
import {
ActionProjectType,
ProjectMembershipRole,
ProjectType,
SecretEncryptionAlgo,
SecretKeyEncoding,
SecretType,
@ -22,6 +22,8 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import {
decryptSecretWithBot,
@ -91,6 +93,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">;
@ -138,18 +141,20 @@ export const secretApprovalRequestServiceFactory = ({
secretVersionV2BridgeDAL,
secretVersionTagV2BridgeDAL,
licenseService,
projectSlackConfigDAL
projectSlackConfigDAL,
resourceMetadataDAL
}: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
await permissionService.getProjectPermission(
actor as ActorType.USER,
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
return count;
@ -169,7 +174,14 @@ export const secretApprovalRequestServiceFactory = ({
}: TListApprovalsDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
if (shouldUseSecretV2Bridge) {
@ -212,13 +224,14 @@ export const secretApprovalRequestServiceFactory = ({
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
const { policy } = secretApprovalRequest;
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerUserId !== actorId &&
@ -241,6 +254,7 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.key,
id: el.id,
version: el.version,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
secretComment: el.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
@ -269,7 +283,8 @@ export const secretApprovalRequestServiceFactory = ({
secretComment: el.secretVersion.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: "",
tags: el.secretVersion.tags
tags: el.secretVersion.tags,
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO
}
: undefined
}));
@ -330,13 +345,14 @@ export const secretApprovalRequestServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId,
secretApprovalRequest.projectId,
projectId: secretApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerUserId !== actorId &&
@ -396,13 +412,14 @@ export const secretApprovalRequestServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId,
secretApprovalRequest.projectId,
projectId: secretApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerUserId !== actorId &&
@ -452,13 +469,14 @@ export const secretApprovalRequestServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (
!hasRole(ProjectMembershipRole.Admin) &&
@ -543,6 +561,7 @@ export const secretApprovalRequestServiceFactory = ({
? await fnSecretV2BridgeBulkInsert({
tx,
folderId,
orgId: actorOrgId,
inputSecrets: secretCreationCommits.map((el) => ({
tagIds: el?.tags.map(({ id }) => id),
version: 1,
@ -550,6 +569,7 @@ export const secretApprovalRequestServiceFactory = ({
encryptedValue: el.encryptedValue,
skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
references: el.encryptedValue
? getAllSecretReferencesV2Bridge(
secretManagerDecryptor({
@ -559,6 +579,7 @@ export const secretApprovalRequestServiceFactory = ({
: [],
type: SecretType.Shared
})),
resourceMetadataDAL,
secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL,
@ -568,6 +589,7 @@ export const secretApprovalRequestServiceFactory = ({
const updatedSecrets = secretUpdationCommits.length
? await fnSecretV2BridgeBulkUpdate({
folderId,
orgId: actorOrgId,
tx,
inputSecrets: secretUpdationCommits.map((el) => {
const encryptedValue =
@ -592,6 +614,7 @@ export const secretApprovalRequestServiceFactory = ({
skipMultilineEncoding: el.skipMultilineEncoding,
key: el.key,
tags: el?.tags.map(({ id }) => id),
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
...encryptedValue
}
};
@ -599,7 +622,8 @@ export const secretApprovalRequestServiceFactory = ({
secretDAL: secretV2BridgeDAL,
secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
resourceMetadataDAL
})
: [];
const deletedSecret = secretDeletionCommits.length
@ -824,6 +848,7 @@ export const secretApprovalRequestServiceFactory = ({
}
await secretQueueService.syncSecrets({
projectId,
orgId: actorOrgId,
secretPath: folder.path,
environmentSlug: folder.environmentSlug,
actorId,
@ -852,7 +877,7 @@ export const secretApprovalRequestServiceFactory = ({
bypassReason,
secretPath: policy.secretPath,
environment: env.name,
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval`
approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval`
},
template: SmtpTemplates.AccessSecretRequestBypassed
});
@ -876,14 +901,14 @@ export const secretApprovalRequestServiceFactory = ({
}: TGenerateSecretApprovalRequestDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
@ -1157,14 +1182,14 @@ export const secretApprovalRequestServiceFactory = ({
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
throw new NotFoundError({
@ -1208,6 +1233,7 @@ export const secretApprovalRequestServiceFactory = ({
),
skipMultilineEncoding: createdSecret.skipMultilineEncoding,
key: createdSecret.secretKey,
secretMetadata: createdSecret.secretMetadata,
type: SecretType.Shared
}))
);
@ -1263,12 +1289,14 @@ export const secretApprovalRequestServiceFactory = ({
reminderNote,
secretComment,
metadata,
skipMultilineEncoding
skipMultilineEncoding,
secretMetadata
}) => {
const secretId = updatingSecretsGroupByKey[secretKey][0].id;
if (tagIds?.length) commitTagIds[secretKey] = tagIds;
return {
...latestSecretVersions[secretId],
secretMetadata,
key: newSecretName || secretKey,
encryptedComment: setKnexStringValue(
secretComment,
@ -1370,7 +1398,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays,
encryptedValue,
secretId,
secretVersion
secretVersion,
secretMetadata
}) => ({
version,
requestId: doc.id,
@ -1383,7 +1412,8 @@ export const secretApprovalRequestServiceFactory = ({
reminderRepeatDays,
reminderNote,
encryptedComment,
key
key,
secretMetadata: JSON.stringify(secretMetadata)
})
),
tx

View File

@ -1,5 +1,6 @@
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations } from "@app/services/secret/secret-types";
export enum RequestState {
@ -34,6 +35,7 @@ export type TApprovalCreateSecretV2Bridge = {
reminderRepeatDays?: number | null;
skipMultilineEncoding?: boolean;
metadata?: Record<string, string>;
secretMetadata?: ResourceMetadataDTO;
tagIds?: string[];
};

View File

@ -13,6 +13,8 @@ import { ActorType } from "@app/services/auth/auth-type";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { KmsDataKey } from "@app/services/kms/kms-types";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
@ -56,6 +58,7 @@ type TSecretReplicationServiceFactoryDep = {
>;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">;
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
@ -121,7 +124,8 @@ export const secretReplicationServiceFactory = ({
secretVersionV2TagBridgeDAL,
secretVersionV2BridgeDAL,
secretV2BridgeDAL,
kmsService
kmsService,
resourceMetadataDAL
}: TSecretReplicationServiceFactoryDep) => {
const $getReplicatedSecrets = (
botKey: string,
@ -151,8 +155,10 @@ export const secretReplicationServiceFactory = ({
};
const $getReplicatedSecretsV2 = (
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[],
importedSecrets: { secrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[] }[]
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[],
importedSecrets: {
secrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[];
}[]
) => {
const deDupe = new Set<string>();
const secrets = [...localSecrets];
@ -178,6 +184,7 @@ export const secretReplicationServiceFactory = ({
secretPath,
environmentSlug,
projectId,
orgId,
actorId,
actor,
pickOnlyImportIds,
@ -222,6 +229,7 @@ export const secretReplicationServiceFactory = ({
.map(({ folderId }) =>
secretQueueService.replicateSecrets({
projectId,
orgId,
secretPath: foldersGroupedById[folderId][0]?.path as string,
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
actorId,
@ -267,6 +275,7 @@ export const secretReplicationServiceFactory = ({
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
: undefined
}));
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
@ -333,13 +342,29 @@ export const secretReplicationServiceFactory = ({
.map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create
const locallyUpdatedSecrets = sourceSecrets
.filter(
({ key, secretKey, secretValue }) =>
.filter(({ key, secretKey, secretValue, secretMetadata }) => {
const sourceSecretMetadataJson = JSON.stringify(
(secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
const destinationSecretMetadataJson = JSON.stringify(
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretMetadata ?? []).map((entry) => ({
key: entry.key,
value: entry.value
}))
);
return (
destinationLocalSecretsGroupedByKey[key]?.[0] &&
// if key or value changed
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey ||
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue)
)
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue ||
sourceSecretMetadataJson !== destinationSecretMetadataJson)
);
})
.map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create
const locallyDeletedSecrets = destinationLocalSecrets
@ -387,6 +412,7 @@ export const secretReplicationServiceFactory = ({
op: operation,
requestId: approvalRequestDoc.id,
metadata: doc.metadata,
secretMetadata: JSON.stringify(doc.secretMetadata),
key: doc.key,
encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment,
@ -406,10 +432,12 @@ export const secretReplicationServiceFactory = ({
if (locallyCreatedSecrets.length) {
await fnSecretV2BridgeBulkInsert({
folderId: destinationReplicationFolderId,
orgId,
secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL,
tx,
secretTagDAL,
resourceMetadataDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyCreatedSecrets.map((doc) => {
return {
@ -419,6 +447,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue,
encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
};
})
@ -426,10 +455,12 @@ export const secretReplicationServiceFactory = ({
}
if (locallyUpdatedSecrets.length) {
await fnSecretV2BridgeBulkUpdate({
orgId,
folderId: destinationReplicationFolderId,
secretVersionDAL: secretVersionV2BridgeDAL,
secretDAL: secretV2BridgeDAL,
tx,
resourceMetadataDAL,
secretTagDAL,
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
inputSecrets: locallyUpdatedSecrets.map((doc) => {
@ -445,6 +476,7 @@ export const secretReplicationServiceFactory = ({
encryptedValue: doc.encryptedValue as Buffer,
encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding,
secretMetadata: doc.secretMetadata,
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
}
};
@ -466,6 +498,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({
projectId,
orgId,
secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug,
actorId,
@ -751,6 +784,7 @@ export const secretReplicationServiceFactory = ({
await secretQueueService.syncSecrets({
projectId,
orgId,
secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environmentSlug,
actorId,

View File

@ -180,6 +180,8 @@ export const secretRotationQueueFactory = ({
provider.template.client === TDbProviderClients.MsSqlServer
? ({
encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT,
// when ca is provided use that
trustServerCertificate: !ca,
cryptoCredentialsDetails: ca ? { ca } : {}
} as Record<string, unknown>)
: undefined;

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import Ajv from "ajv";
import { ProjectType, ProjectVersion, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types";
@ -53,14 +53,14 @@ export const secretRotationServiceFactory = ({
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
return {
@ -82,14 +82,14 @@ export const secretRotationServiceFactory = ({
secretPath,
environment
}: TCreateSecretRotationDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRotation
@ -191,13 +191,14 @@ export const secretRotationServiceFactory = ({
};
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
if (shouldUseSecretV2Bridge) {
@ -236,14 +237,14 @@ export const secretRotationServiceFactory = ({
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
});
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
doc.projectId,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
await secretRotationQueue.addToQueue(doc.id, doc.interval);
@ -254,14 +255,14 @@ export const secretRotationServiceFactory = ({
const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
doc.projectId,
projectId: doc.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretRotation

View File

@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { InternalServerError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
@ -83,13 +83,14 @@ export const secretSnapshotServiceFactory = ({
actorAuthMethod,
path
}: TProjectSnapshotCountDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
@ -119,13 +120,14 @@ export const secretSnapshotServiceFactory = ({
limit = 20,
offset = 0
}: TProjectSnapshotListDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
@ -147,13 +149,14 @@ export const secretSnapshotServiceFactory = ({
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
const snapshot = await snapshotDAL.findById(id);
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
snapshot.projectId,
projectId: snapshot.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const shouldUseBridge = snapshot.projectVersion === 3;
@ -322,14 +325,14 @@ export const secretSnapshotServiceFactory = ({
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
const shouldUseBridge = snapshot.projectVersion === 3;
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
snapshot.projectId,
projectId: snapshot.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretRollback

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -54,15 +54,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificateTemplates
@ -127,15 +127,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SshCertificateTemplates
@ -196,15 +196,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certificateTemplate.projectId,
projectId: certificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SshCertificateTemplates
@ -223,15 +223,15 @@ export const sshCertificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
@ -65,15 +65,15 @@ export const sshCertificateAuthorityServiceFactory = ({
actor,
actorOrgId
}: TCreateSshCaDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificateAuthorities
@ -118,15 +118,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateAuthorities
@ -187,15 +187,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
ProjectPermissionSub.SshCertificateAuthorities
@ -226,15 +226,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SshCertificateAuthorities
@ -268,15 +268,15 @@ export const sshCertificateAuthorityServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sshCertificateTemplate.projectId,
projectId: sshCertificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificates
@ -390,15 +390,15 @@ export const sshCertificateAuthorityServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
sshCertificateTemplate.projectId,
projectId: sshCertificateTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SshCertificates
@ -488,15 +488,15 @@ export const sshCertificateAuthorityServiceFactory = ({
const ca = await sshCertificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates

View File

@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { TProjectPermission } from "@app/lib/types";
@ -27,13 +28,14 @@ export const trustedIpServiceFactory = ({
projectDAL
}: TTrustedIpServiceFactoryDep) => {
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
const trustedIps = await trustedIpDAL.find({
projectId
@ -51,13 +53,14 @@ export const trustedIpServiceFactory = ({
comment,
isActive
}: TCreateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);
@ -96,13 +99,14 @@ export const trustedIpServiceFactory = ({
comment,
trustedIpId
}: TUpdateIpDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);
@ -141,13 +145,14 @@ export const trustedIpServiceFactory = ({
actorAuthMethod,
trustedIpId
}: TDeleteIpDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
const project = await projectDAL.findById(projectId);

View File

@ -741,6 +741,12 @@ export const RAW_SECRETS = {
workspaceId: "The ID of the project where the secret is located.",
environment: "The slug of the environment where the the secret is located.",
secretPath: "The folder path where the secret is located."
},
GET_ACCESS_LIST: {
secretName: "The name of the secret to get the access list for.",
workspaceId: "The ID of the project where the secret is located.",
environment: "The slug of the environment where the the secret is located.",
secretPath: "The folder path where the secret is located."
}
} as const;
@ -1150,7 +1156,8 @@ export const INTEGRATION = {
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
shouldEnableDelete: "The flag to enable deletion of secrets.",
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy."
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy.",
metadataSyncMode: "The mode for syncing metadata to external system"
}
},
UPDATE: {

View File

@ -157,6 +157,8 @@ const envSchema = z
INFISICAL_CLOUD: zodStrBool.default("false"),
MAINTENANCE_MODE: zodStrBool.default("false"),
CAPTCHA_SECRET: zpStr(z.string().optional()),
CAPTCHA_SITE_KEY: zpStr(z.string().optional()),
INTERCOM_ID: zpStr(z.string().optional()),
// TELEMETRY
OTEL_TELEMETRY_COLLECTION_ENABLED: zodStrBool.default("false"),
@ -197,7 +199,29 @@ const envSchema = z
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET: zpStr(z.string().optional()),
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY: zpStr(z.string().optional()),
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional())
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional()),
/* CORS ----------------------------------------------------------------------------- */
CORS_ALLOWED_ORIGINS: zpStr(
z
.string()
.optional()
.transform((val) => {
if (!val) return undefined;
return JSON.parse(val) as string[];
})
),
CORS_ALLOWED_HEADERS: zpStr(
z
.string()
.optional()
.transform((val) => {
if (!val) return undefined;
return JSON.parse(val) as string[];
})
)
})
// To ensure that basic encryption is always possible.
.refine(

View File

@ -27,10 +27,10 @@ import { globalRateLimiterCfg } from "./config/rateLimiter";
import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas";
import { apiMetrics } from "./plugins/api-metrics";
import { fastifyErrHandler } from "./plugins/error-handler";
import { registerExternalNextjs } from "./plugins/external-nextjs";
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
import { fastifyIp } from "./plugins/ip";
import { maintenanceMode } from "./plugins/maintenanceMode";
import { registerServeUI } from "./plugins/serve-ui";
import { fastifySwagger } from "./plugins/swagger";
import { registerRoutes } from "./routes";
@ -87,7 +87,16 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
await server.register<FastifyCorsOptions>(cors, {
credentials: true,
origin: appCfg.SITE_URL || true
...(appCfg.CORS_ALLOWED_ORIGINS?.length
? {
origin: [...appCfg.CORS_ALLOWED_ORIGINS, ...(appCfg.SITE_URL ? [appCfg.SITE_URL] : [])]
}
: {
origin: appCfg.SITE_URL || true
}),
...(appCfg.CORS_ALLOWED_HEADERS?.length && {
allowedHeaders: appCfg.CORS_ALLOWED_HEADERS
})
});
await server.register(addErrorsToResponseSchemas);
@ -120,13 +129,10 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule });
if (appCfg.isProductionMode) {
await server.register(registerExternalNextjs, {
standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED,
dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../"),
port: appCfg.PORT
});
}
await server.register(registerServeUI, {
standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED,
dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../")
});
await server.ready();
server.swagger();

View File

@ -32,13 +32,21 @@ export const getUserAgentType = (userAgent: string | undefined) => {
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
server.decorateRequest("auditLogInfo", null);
server.addHook("onRequest", async (req) => {
if (!req.auth) return;
const userAgent = req.headers["user-agent"] ?? "";
const payload = {
ipAddress: req.realIp,
userAgent,
userAgentType: getUserAgentType(userAgent)
} as typeof req.auditLogInfo;
if (!req.auth) {
payload.actor = {
type: ActorType.UNKNOWN_USER,
metadata: {}
};
req.auditLogInfo = payload;
return;
}
if (req.auth.actor === ActorType.USER) {
payload.actor = {
type: ActorType.USER,

View File

@ -1,76 +0,0 @@
// this plugins allows to run infisical in standalone mode
// standalone mode = infisical backend and nextjs frontend in one server
// this way users don't need to deploy two things
import path from "node:path";
import { IS_PACKAGED } from "@app/lib/config/env";
// to enabled this u need to set standalone mode to true
export const registerExternalNextjs = async (
server: FastifyZodProvider,
{
standaloneMode,
dir,
port
}: {
standaloneMode?: boolean;
dir: string;
port: number;
}
) => {
if (standaloneMode) {
const frontendName = IS_PACKAGED ? "frontend" : "frontend-build";
const nextJsBuildPath = path.join(dir, frontendName);
const { default: conf } = (await import(
path.join(dir, `${frontendName}/.next/required-server-files.json`),
// @ts-expect-error type
{
assert: { type: "json" }
}
)) as { default: { config: string } };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let NextServer: any;
if (!IS_PACKAGED) {
/* eslint-disable */
const { default: nextServer } = (
await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`))
).default;
NextServer = nextServer;
} else {
/* eslint-disable */
const nextServer = await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`));
NextServer = nextServer.default;
}
const nextApp = new NextServer({
dev: false,
dir: nextJsBuildPath,
port,
conf: conf.config,
hostname: "local",
customServer: false
});
server.route({
method: ["GET", "PUT", "PATCH", "POST", "DELETE"],
url: "/*",
schema: {
hide: true
},
handler: (req, res) =>
nextApp
.getRequestHandler()(req.raw, res.raw)
.then(() => {
res.hijack();
})
});
server.addHook("onClose", () => nextApp.close());
await nextApp.prepare();
/* eslint-enable */
}
};

View File

@ -0,0 +1,64 @@
import path from "node:path";
import staticServe from "@fastify/static";
import { getConfig, IS_PACKAGED } from "@app/lib/config/env";
// to enabled this u need to set standalone mode to true
export const registerServeUI = async (
server: FastifyZodProvider,
{
standaloneMode,
dir
}: {
standaloneMode?: boolean;
dir: string;
}
) => {
// use this only for frontend runtime static non-sensitive configuration in standalone mode
// that app needs before loading like posthog dsn key
// for most of the other usecase use server config
server.route({
method: "GET",
url: "/runtime-ui-env.js",
schema: {
hide: true
},
handler: (_req, res) => {
const appCfg = getConfig();
void res.type("application/javascript");
const config = {
CAPTCHA_SITE_KEY: appCfg.CAPTCHA_SITE_KEY,
POSTHOG_API_KEY: appCfg.POSTHOG_PROJECT_API_KEY,
INTERCOM_ID: appCfg.INTERCOM_ID,
TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED
};
const js = `window.__INFISICAL_RUNTIME_ENV__ = Object.freeze(${JSON.stringify(config)});`;
void res.send(js);
}
});
if (standaloneMode) {
const frontendName = IS_PACKAGED ? "frontend" : "frontend-build";
const frontendPath = path.join(dir, frontendName);
await server.register(staticServe, {
root: frontendPath,
wildcard: false
});
server.route({
method: "GET",
url: "/*",
schema: {
hide: true
},
handler: (request, reply) => {
if (request.url.startsWith("/api")) {
reply.callNotFound();
return;
}
void reply.sendFile("index.html");
}
});
}
};

View File

@ -181,6 +181,7 @@ import { projectUserMembershipRoleDALFactory } from "@app/services/project-membe
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service";
@ -374,6 +375,7 @@ export const registerRoutes = async (
const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db);
const projectTemplateDAL = projectTemplateDALFactory(db);
const resourceMetadataDAL = resourceMetadataDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
@ -606,6 +608,7 @@ export const registerRoutes = async (
});
const superAdminService = superAdminServiceFactory({
userDAL,
userAliasDAL,
authService: loginService,
serverCfgDAL: superAdminDAL,
kmsRootConfigDAL,
@ -854,7 +857,8 @@ export const registerRoutes = async (
secretApprovalRequestDAL,
projectKeyDAL,
projectUserMembershipRoleDAL,
orgService
orgService,
resourceMetadataDAL
});
const projectService = projectServiceFactory({
@ -980,7 +984,8 @@ export const registerRoutes = async (
secretApprovalPolicyService,
secretApprovalRequestSecretDAL,
kmsService,
snapshotService
snapshotService,
resourceMetadataDAL
});
const secretApprovalRequestService = secretApprovalRequestServiceFactory({
@ -1007,7 +1012,8 @@ export const registerRoutes = async (
projectEnvDAL,
userDAL,
licenseService,
projectSlackConfigDAL
projectSlackConfigDAL,
resourceMetadataDAL
});
const secretService = secretServiceFactory({
@ -1028,7 +1034,8 @@ export const registerRoutes = async (
secretApprovalRequestDAL,
secretApprovalRequestSecretDAL,
secretV2BridgeService,
secretApprovalRequestService
secretApprovalRequestService,
licenseService
});
const secretSharingService = secretSharingServiceFactory({
@ -1086,8 +1093,10 @@ export const registerRoutes = async (
kmsService,
secretV2BridgeDAL,
secretVersionV2TagBridgeDAL: secretVersionTagV2BridgeDAL,
secretVersionV2BridgeDAL
secretVersionV2BridgeDAL,
resourceMetadataDAL
});
const secretRotationQueue = secretRotationQueueFactory({
telemetryService,
secretRotationDAL,
@ -1339,7 +1348,8 @@ export const registerRoutes = async (
folderDAL,
secretDAL: secretV2BridgeDAL,
queueService,
secretV2BridgeService
secretV2BridgeService,
resourceMetadataDAL
});
const migrationService = externalMigrationServiceFactory({

View File

@ -63,7 +63,8 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
schema: {
response: {
200: z.object({
token: z.string()
token: z.string(),
organizationId: z.string().optional()
})
}
},
@ -115,7 +116,7 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }
);
return { token };
return { token, organizationId: decodedToken.organizationId };
}
});
};

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import { z } from "zod";
import { SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
import { ActionProjectType, SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
import {
ProjectPermissionDynamicSecretActions,
@ -17,6 +17,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@ -116,6 +117,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -218,13 +220,14 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
totalCount: totalFolderCount ?? 0
};
const { permission } = await server.services.permission.getProjectPermission(
req.permission.type,
req.permission.id,
const { permission } = await server.services.permission.getProjectPermission({
actor: req.permission.type,
actorId: req.permission.id,
projectId,
req.permission.authMethod,
req.permission.orgId
);
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const allowedDynamicSecretEnvironments = // filter envs user has access to
environments.filter((environment) =>
@ -408,6 +411,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -693,6 +697,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -864,6 +869,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,

View File

@ -131,7 +131,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
body: z.object({
app: z.string().trim().optional().describe(INTEGRATION.UPDATE.app),
appId: z.string().trim().optional().describe(INTEGRATION.UPDATE.appId),
isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive),
isActive: z.boolean().optional().describe(INTEGRATION.UPDATE.isActive),
secretPath: z
.string()
.trim()

View File

@ -1,6 +1,7 @@
import { z } from "zod";
import { SecretSharingSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { SecretSharingAccessType } from "@app/lib/types";
import {
publicEndpointLimit,
@ -88,6 +89,21 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
orgId: req.permission?.orgId
});
if (sharedSecret.secret?.orgId) {
await server.services.auditLog.createAuditLog({
orgId: sharedSecret.secret.orgId,
...req.auditLogInfo,
event: {
type: EventType.READ_SHARED_SECRET,
metadata: {
id: req.params.id,
name: sharedSecret.secret.name || undefined,
accessType: sharedSecret.secret.accessType
}
}
});
}
return sharedSecret;
}
});
@ -151,6 +167,23 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
actorOrgId: req.permission.orgId,
...req.body
});
await server.services.auditLog.createAuditLog({
orgId: req.permission.orgId,
...req.auditLogInfo,
event: {
type: EventType.CREATE_SHARED_SECRET,
metadata: {
accessType: req.body.accessType,
expiresAt: req.body.expiresAt,
expiresAfterViews: req.body.expiresAfterViews,
name: req.body.name,
id: sharedSecret.id,
usingPassword: !!req.body.password
}
}
});
return { id: sharedSecret.id };
}
});
@ -181,6 +214,18 @@ export const registerSecretSharingRouter = async (server: FastifyZodProvider) =>
sharedSecretId
});
await server.services.auditLog.createAuditLog({
orgId: req.permission.orgId,
...req.auditLogInfo,
event: {
type: EventType.DELETE_SHARED_SECRET,
metadata: {
id: sharedSecretId,
name: deletedSharedSecret.name || undefined
}
}
});
return { ...deletedSharedSecret };
}
});

View File

@ -331,12 +331,8 @@ export const registerSlackRouter = async (server: FastifyZodProvider) => {
failureAsync: async () => {
return res.redirect(appCfg.SITE_URL as string);
},
successAsync: async (installation) => {
const metadata = JSON.parse(installation.metadata || "") as {
orgId: string;
};
return res.redirect(`${appCfg.SITE_URL}/org/${metadata.orgId}/settings?selectedTab=workflow-integrations`);
successAsync: async () => {
return res.redirect(`${appCfg.SITE_URL}/organization/settings?selectedTab=workflow-integrations`);
}
});
}

View File

@ -18,6 +18,7 @@ import { getUserAgentType } from "@app/server/plugins/audit-log";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { ProjectFilterType } from "@app/services/project/project-types";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { SecretOperations, SecretProtectionType } from "@app/services/secret/secret-types";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
@ -35,6 +36,12 @@ const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReference
children: z.lazy(() => SecretReferenceNodeTree.array())
});
const SecretNameSchema = z
.string()
.trim()
.min(1)
.refine((el) => !el.includes(" "), "Secret name cannot contain spaces.");
export const registerSecretRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
@ -50,7 +57,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(SECRETS.ATTACH_TAGS.secretName)
secretName: SecretNameSchema.describe(SECRETS.ATTACH_TAGS.secretName)
}),
body: z.object({
projectSlug: z.string().trim().describe(SECRETS.ATTACH_TAGS.projectSlug),
@ -113,7 +120,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(SECRETS.DETACH_TAGS.secretName)
secretName: z.string().describe(SECRETS.DETACH_TAGS.secretName)
}),
body: z.object({
projectSlug: z.string().trim().describe(SECRETS.DETACH_TAGS.projectSlug),
@ -205,6 +212,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secrets: secretRawSchema
.extend({
secretPath: z.string().optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tags: SecretTagsSchema.pick({
id: true,
slug: true,
@ -220,7 +228,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretPath: z.string(),
environment: z.string(),
folderId: z.string().optional(),
secrets: secretRawSchema.omit({ createdAt: true, updatedAt: true }).array()
secrets: secretRawSchema
.omit({ createdAt: true, updatedAt: true })
.extend({
secretMetadata: ResourceMetadataSchema.optional()
})
.array()
})
.array()
.optional()
@ -348,7 +361,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
})
.extend({ name: z.string() })
.array()
.optional()
.optional(),
secretMetadata: ResourceMetadataSchema.optional()
})
})
}
@ -434,7 +448,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.CREATE.secretName)
secretName: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.CREATE.workspaceId),
@ -450,6 +464,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.CREATE.secretValue),
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type),
@ -484,6 +499,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretValue: req.body.secretValue,
skipMultilineEncoding: req.body.skipMultilineEncoding,
secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata,
tagIds: req.body.tagIds,
secretReminderNote: req.body.secretReminderNote,
secretReminderRepeatDays: req.body.secretReminderRepeatDays
@ -539,7 +555,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName)
secretName: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.UPDATE.workspaceId),
@ -558,13 +574,14 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretReminderRepeatDays: z
.number()
.optional()
.nullable()
.describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays),
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment)
}),
response: {
@ -595,8 +612,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretReminderNote: req.body.secretReminderNote,
metadata: req.body.metadata,
newSecretName: req.body.newSecretName,
secretComment: req.body.secretComment
secretComment: req.body.secretComment,
secretMetadata: req.body.secretMetadata
});
if (secretOperation.type === SecretProtectionType.Approval) {
return { approval: secretOperation.approval };
}
@ -647,7 +666,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
}
],
params: z.object({
secretName: z.string().trim().describe(RAW_SECRETS.DELETE.secretName)
secretName: z.string().min(1).describe(RAW_SECRETS.DELETE.secretName)
}),
body: z.object({
workspaceId: z.string().trim().describe(RAW_SECRETS.DELETE.workspaceId),
@ -1842,7 +1861,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.describe(RAW_SECRETS.CREATE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.CREATE.secretName),
secretKey: SecretNameSchema.describe(RAW_SECRETS.CREATE.secretName),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
@ -1850,6 +1869,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding),
metadata: z.record(z.string()).optional(),
secretMetadata: ResourceMetadataSchema.optional(),
tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds)
})
.array()
@ -1942,16 +1962,17 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.describe(RAW_SECRETS.UPDATE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.UPDATE.secretName),
secretKey: SecretNameSchema.describe(RAW_SECRETS.UPDATE.secretName),
secretValue: z
.string()
.transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim()))
.describe(RAW_SECRETS.UPDATE.secretValue),
secretComment: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.secretComment),
skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding),
newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName),
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds),
secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote),
secretMetadata: ResourceMetadataSchema.optional(),
secretReminderRepeatDays: z
.number()
.optional()
@ -2047,7 +2068,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
.describe(RAW_SECRETS.DELETE.secretPath),
secrets: z
.object({
secretKey: z.string().trim().describe(RAW_SECRETS.DELETE.secretName),
secretKey: z.string().describe(RAW_SECRETS.DELETE.secretName),
type: z.nativeEnum(SecretType).default(SecretType.Shared)
})
.array()

View File

@ -39,7 +39,8 @@ export enum ActorType { // would extend to AWS, Azure, ...
SERVICE = "service",
IDENTITY = "identity",
Machine = "machine",
SCIM_CLIENT = "scimClient"
SCIM_CLIENT = "scimClient",
UNKNOWN_USER = "unknownUser"
}
// This will be null unless the token-type is JWT

View File

@ -5,7 +5,7 @@ import crypto, { KeyObject } from "crypto";
import ms from "ms";
import { z } from "zod";
import { ProjectType, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
import { ActionProjectType, ProjectType, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
@ -136,14 +136,14 @@ export const certificateAuthorityServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -305,13 +305,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.CertificateAuthorities
@ -336,14 +337,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -362,14 +363,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -388,13 +389,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -449,14 +451,14 @@ export const certificateAuthorityServiceFactory = ({
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -720,13 +722,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -755,13 +758,14 @@ export const certificateAuthorityServiceFactory = ({
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -835,14 +839,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: "CA not found" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -982,14 +986,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -1145,14 +1149,14 @@ export const certificateAuthorityServiceFactory = ({
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
@ -1474,14 +1478,14 @@ export const certificateAuthorityServiceFactory = ({
}
if (!dto.isInternal) {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
dto.actor,
dto.actorId,
ca.projectId,
dto.actorAuthMethod,
dto.actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
const { permission } = await permissionService.getProjectPermission({
actor: dto.actor,
actorId: dto.actorId,
projectId: ca.projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -1832,13 +1836,14 @@ export const certificateAuthorityServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import bcrypt from "bcrypt";
import { ProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
import { ActionProjectType, TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -67,14 +67,14 @@ export const certificateTemplateServiceFactory = ({
message: `CA with ID ${caId} not found`
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -129,14 +129,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -187,14 +187,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -214,13 +214,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -255,14 +256,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -340,14 +341,14 @@ export const certificateTemplateServiceFactory = ({
});
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
certTemplate.projectId,
projectId: certTemplate.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -422,13 +423,14 @@ export const certificateTemplateServiceFactory = ({
}
if (!dto.isInternal) {
const { permission } = await permissionService.getProjectPermission(
dto.actor,
dto.actorId,
certTemplate.projectId,
dto.actorAuthMethod,
dto.actorOrgId
);
const { permission } = await permissionService.getProjectPermission({
actor: dto.actor,
actorId: dto.actorId,
projectId: certTemplate.projectId,
actorAuthMethod: dto.actorAuthMethod,
actorOrgId: dto.actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -50,14 +50,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
@ -74,14 +74,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
@ -109,14 +109,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
@ -156,13 +156,14 @@ export const certificateServiceFactory = ({
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
ca.projectId,
projectId: ca.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionCmekActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -34,14 +34,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
projectId = cmekProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Create, ProjectPermissionSub.Cmek);
const cmek = await kmsService.generateKmsKey({
@ -60,14 +60,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Edit, ProjectPermissionSub.Cmek);
@ -83,14 +83,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Delete, ProjectPermissionSub.Cmek);
@ -109,13 +109,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
projectId = cmekProjectFromSplit.id;
}
const { permission } = await permissionService.getProjectPermission(
actor.type,
actor.id,
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId,
actor.authMethod,
actor.orgId
);
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
@ -133,15 +134,15 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbidOnInvalidProjectType(ProjectType.KMS);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Encrypt, ProjectPermissionSub.Cmek);
const encrypt = await kmsService.encryptWithKmsKey({ kmsId: keyId });
@ -160,14 +161,14 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
actor.type,
actor.id,
key.projectId,
actor.authMethod,
actor.orgId
);
ForbidOnInvalidProjectType(ProjectType.KMS);
const { permission } = await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId: key.projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.KMS
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Decrypt, ProjectPermissionSub.Cmek);

View File

@ -16,6 +16,7 @@ import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@ -35,6 +36,8 @@ export type TImportDataIntoInfisicalDTO = {
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findById">;
projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
@ -503,6 +506,7 @@ export const importDataIntoInfisicalFn = async ({
secretTagDAL,
secretVersionTagDAL,
folderDAL,
resourceMetadataDAL,
input: { data, actor, actorId, actorOrgId, actorAuthMethod }
}: TImportDataIntoInfisicalDTO) => {
// Import data to infisical
@ -762,6 +766,8 @@ export const importDataIntoInfisicalFn = async ({
};
}),
folderId: selectedFolder.id,
orgId: actorOrgId,
resourceMetadataDAL,
secretDAL,
secretVersionDAL,
secretTagDAL,

View File

@ -8,6 +8,7 @@ import { TProjectDALFactory } from "../project/project-dal";
import { TProjectServiceFactory } from "../project/project-service";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TProjectEnvServiceFactory } from "../project-env/project-env-service";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@ -35,6 +36,8 @@ export type TExternalMigrationQueueFactoryDep = {
projectService: Pick<TProjectServiceFactory, "createProject">;
projectEnvService: Pick<TProjectEnvServiceFactory, "createEnvironment">;
secretV2BridgeService: Pick<TSecretV2BridgeServiceFactory, "createManySecret">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
};
export type TExternalMigrationQueueFactory = ReturnType<typeof externalMigrationQueueFactory>;
@ -52,7 +55,8 @@ export const externalMigrationQueueFactory = ({
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL,
folderDAL
folderDAL,
resourceMetadataDAL
}: TExternalMigrationQueueFactoryDep) => {
const startImport = async (dto: {
actorEmail: string;
@ -109,7 +113,8 @@ export const externalMigrationQueueFactory = ({
kmsService,
projectService,
projectEnvService,
secretV2BridgeService
secretV2BridgeService,
resourceMetadataDAL
});
if (projectsNotImported.length) {

View File

@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ProjectMembershipRole, SecretKeyEncoding } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
@ -69,13 +69,14 @@ export const groupProjectServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
if (project.version < 2) throw new BadRequestError({ message: `Failed to add group to E2EE project` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Groups);
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
@ -237,13 +238,14 @@ export const groupProjectServiceFactory = ({
if (!project) throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups);
const group = await groupDAL.findOne({ orgId: actorOrgId, id: groupId });
@ -339,13 +341,14 @@ export const groupProjectServiceFactory = ({
const groupProjectMembership = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (!groupProjectMembership) throw new NotFoundError({ message: `Failed to find group with ID ${groupId}` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
const deletedProjectGroup = await groupProjectDAL.transaction(async (tx) => {
@ -383,13 +386,14 @@ export const groupProjectServiceFactory = ({
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
const groupMemberships = await groupProjectDAL.findByProjectId(project.id);
@ -410,13 +414,14 @@ export const groupProjectServiceFactory = ({
throw new NotFoundError({ message: `Failed to find project with ID ${projectId}` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
const [groupMembership] = await groupProjectDAL.findByProjectId(project.id, {

View File

@ -2,3 +2,11 @@ import picomatch from "picomatch";
export const doesFieldValueMatchOidcPolicy = (fieldValue: string, policyValue: string) =>
policyValue === fieldValue || picomatch.isMatch(fieldValue, policyValue);
export const doesAudValueMatchOidcPolicy = (fieldValue: string | string[], policyValue: string) => {
if (Array.isArray(fieldValue)) {
return fieldValue.some((entry) => entry === policyValue || picomatch.isMatch(entry, policyValue));
}
return policyValue === fieldValue || picomatch.isMatch(fieldValue, policyValue);
};

View File

@ -27,7 +27,7 @@ import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identit
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TOrgBotDALFactory } from "../org/org-bot-dal";
import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal";
import { doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns";
import { doesAudValueMatchOidcPolicy, doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns";
import {
TAttachOidcAuthDTO,
TGetOidcAuthDTO,
@ -148,7 +148,7 @@ export const identityOidcAuthServiceFactory = ({
if (
!identityOidcAuth.boundAudiences
.split(", ")
.some((policyValue) => doesFieldValueMatchOidcPolicy(tokenData.aud, policyValue))
.some((policyValue) => doesAudValueMatchOidcPolicy(tokenData.aud, policyValue))
) {
throw new UnauthorizedError({
message: "Access denied: OIDC audience not allowed."

View File

@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability";
import ms from "ms";
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
@ -54,13 +54,14 @@ export const identityProjectServiceFactory = ({
projectId,
roles
}: TCreateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Identity, {
@ -159,13 +160,14 @@ export const identityProjectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TUpdateProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
@ -254,25 +256,27 @@ export const identityProjectServiceFactory = ({
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
identityProjectMembership.projectId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
ActorType.IDENTITY,
identityId,
identityProjectMembership.projectId,
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
actor: ActorType.IDENTITY,
actorId: identityId,
projectId: identityProjectMembership.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!isAtLeastAsPrivileged(permission, identityRolePermission))
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
@ -292,13 +296,14 @@ export const identityProjectServiceFactory = ({
orderDirection,
search
}: TListProjectIdentityDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
const identityMemberships = await identityProjectDAL.findByProjectId(projectId, {
@ -322,13 +327,14 @@ export const identityProjectServiceFactory = ({
actorOrgId,
identityId
}: TGetProjectIdentityByIdentityIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -5,7 +5,7 @@ import { Client as OctopusClient, SpaceRepository as OctopusSpaceRepository } fr
import AWS from "aws-sdk";
import {
ProjectType,
ActionProjectType,
SecretEncryptionAlgo,
SecretKeyEncoding,
TIntegrationAuths,
@ -97,13 +97,14 @@ export const integrationAuthServiceFactory = ({
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const authorizations = await integrationAuthDAL.find({ projectId });
return authorizations;
@ -114,13 +115,14 @@ export const integrationAuthServiceFactory = ({
return Promise.all(
authorizations.filter(async (auth) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
auth.projectId,
projectId: auth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
})
@ -131,13 +133,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
return integrationAuth;
};
@ -156,14 +159,14 @@ export const integrationAuthServiceFactory = ({
if (!Object.values(Integrations).includes(integration as Integrations))
throw new BadRequestError({ message: "Invalid integration" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const tokenExchange = await exchangeCode({ integration, code, url, installationId });
@ -266,14 +269,14 @@ export const integrationAuthServiceFactory = ({
if (!Object.values(Integrations).includes(integration as Integrations))
throw new BadRequestError({ message: "Invalid integration" });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const updateDoc: TIntegrationAuthsInsert = {
@ -401,13 +404,14 @@ export const integrationAuthServiceFactory = ({
throw new NotFoundError({ message: `Integration auth with id ${integrationAuthId} not found.` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
const { projectId } = integrationAuth;
@ -662,13 +666,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(integrationAuth.projectId);
@ -696,13 +701,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@ -726,13 +732,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -767,13 +774,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -795,13 +803,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
@ -869,13 +878,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -916,13 +926,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -950,13 +961,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessId, accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1008,13 +1020,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1044,13 +1057,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1085,13 +1099,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1125,13 +1140,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1165,13 +1181,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID ${id} not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1204,13 +1221,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1244,13 +1262,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1312,13 +1331,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1386,13 +1406,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1436,13 +1457,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1484,13 +1506,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1552,13 +1575,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1593,13 +1617,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1705,13 +1730,14 @@ export const integrationAuthServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TDeleteIntegrationAuthsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const integrations = await integrationAuthDAL.delete({ integration, projectId });
@ -1728,13 +1754,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const delIntegrationAuth = await integrationAuthDAL.transaction(async (tx) => {
@ -1761,26 +1788,28 @@ export const integrationAuthServiceFactory = ({
throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
}
const { permission: sourcePermission } = await permissionService.getProjectPermission(
const { permission: sourcePermission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(sourcePermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const { permission: targetPermission } = await permissionService.getProjectPermission(
const { permission: targetPermission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(targetPermission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -1806,13 +1835,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
@ -1846,13 +1876,14 @@ export const integrationAuthServiceFactory = ({
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);

View File

@ -427,3 +427,8 @@ export const getIntegrationOptions = async () => {
return INTEGRATION_OPTIONS;
};
export enum IntegrationMetadataSyncMode {
CUSTOM = "custom",
SECRET_METADATA = "secret-metadata"
}

View File

@ -38,6 +38,7 @@ import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/
import { TIntegrationDALFactory } from "../integration/integration-dal";
import { IntegrationMetadataSchema } from "../integration/integration-schema";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { IntegrationAuthMetadataSchema } from "./integration-auth-schema";
import {
CircleCiScope,
@ -48,6 +49,7 @@ import {
import {
IntegrationInitialSyncBehavior,
IntegrationMappingBehavior,
IntegrationMetadataSyncMode,
Integrations,
IntegrationUrls
} from "./integration-list";
@ -305,10 +307,16 @@ const syncSecretsAzureAppConfig = async ({
value: string;
}
const getCompleteAzureAppConfigValues = async (url: string) => {
if (!integration.app || !integration.app.endsWith(".azconfig.io"))
throw new BadRequestError({
message: "Invalid Azure App Configuration URL provided."
});
const getCompleteAzureAppConfigValues = async (baseURL: string, url: string) => {
let result: AzureAppConfigKeyValue[] = [];
while (url) {
const res = await request.get(url, {
baseURL,
headers: {
Authorization: `Bearer ${accessToken}`
},
@ -319,7 +327,7 @@ const syncSecretsAzureAppConfig = async ({
});
result = result.concat(res.data.items);
url = res.data.nextLink;
url = res.data?.["@nextLink"];
}
return result;
@ -327,11 +335,13 @@ const syncSecretsAzureAppConfig = async ({
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
const azureAppConfigValuesUrl = `${integration.app}/kv?api-version=2023-11-01&key=${metadata.secretPrefix}*${
metadata.azureLabel ? `&label=${metadata.azureLabel}` : ""
const azureAppConfigValuesUrl = `/kv?api-version=2023-11-01&key=${metadata.secretPrefix}*${
metadata.azureLabel ? `&label=${metadata.azureLabel}` : "&label=%00"
}`;
const azureAppConfigSecrets = (await getCompleteAzureAppConfigValues(azureAppConfigValuesUrl)).reduce(
const azureAppConfigSecrets = (
await getCompleteAzureAppConfigValues(integration.app, azureAppConfigValuesUrl)
).reduce(
(accum, entry) => {
accum[entry.key] = entry.value;
@ -1074,14 +1084,14 @@ const syncSecretsAWSSecretManager = async ({
projectId
}: {
integration: TIntegrations;
secrets: Record<string, { value: string; comment?: string }>;
secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null;
accessToken: string;
awsAssumeRoleArn: string | null;
projectId?: string;
}) => {
const appCfg = getConfig();
const metadata = z.record(z.any()).parse(integration.metadata || {});
const metadata = IntegrationMetadataSchema.parse(integration.metadata || {});
if (!accessId && !awsAssumeRoleArn) {
throw new Error("AWS access ID/AWS Assume Role is required");
@ -1129,8 +1139,25 @@ const syncSecretsAWSSecretManager = async ({
const processAwsSecret = async (
secretId: string,
secretValue: Record<string, string | null | undefined> | string
secretValue: Record<string, string | null | undefined> | string,
secretMetadata?: ResourceMetadataDTO
) => {
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
const shouldTag =
(secretAWSTag && secretAWSTag.length) ||
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA &&
metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE);
const tagArray =
(metadata.metadataSyncMode === IntegrationMetadataSyncMode.SECRET_METADATA ? secretMetadata : secretAWSTag) ?? [];
const integrationTagObj = tagArray.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
try {
const awsSecretManagerSecret = await secretsManager.send(
new GetSecretValueCommand({
@ -1159,15 +1186,14 @@ const syncSecretsAWSSecretManager = async ({
} else {
await secretsManager.send(
new DeleteSecretCommand({
SecretId: secretId
SecretId: secretId,
ForceDeleteWithoutRecovery: true
})
);
}
}
const secretAWSTag = metadata.secretAWSTag as { key: string; value: string }[] | undefined;
if (secretAWSTag && secretAWSTag.length) {
if (shouldTag) {
const describedSecret = await secretsManager.send(
// requires secretsmanager:DescribeSecret policy
new DescribeSecretCommand({
@ -1177,14 +1203,6 @@ const syncSecretsAWSSecretManager = async ({
if (!describedSecret.Tags) return;
const integrationTagObj = secretAWSTag.reduce(
(acc, item) => {
acc[item.key] = item.value;
return acc;
},
{} as Record<string, string>
);
const awsTagObj = (describedSecret.Tags || []).reduce(
(acc, item) => {
if (item.Key && item.Value) {
@ -1216,7 +1234,7 @@ const syncSecretsAWSSecretManager = async ({
}
});
secretAWSTag?.forEach((tag) => {
tagArray.forEach((tag) => {
if (!(tag.key in awsTagObj)) {
// create tag in AWS secret manager
tagsToUpdate.push({
@ -1253,8 +1271,8 @@ const syncSecretsAWSSecretManager = async ({
Name: secretId,
SecretString: typeof secretValue === "string" ? secretValue : JSON.stringify(secretValue),
...(metadata.kmsKeyId && { KmsKeyId: metadata.kmsKeyId }),
Tags: metadata.secretAWSTag
? metadata.secretAWSTag.map((tag: { key: string; value: string }) => ({
Tags: shouldTag
? tagArray.map((tag: { key: string; value: string }) => ({
Key: tag.key,
Value: tag.value
}))
@ -1271,7 +1289,10 @@ const syncSecretsAWSSecretManager = async ({
if (metadata.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE) {
for await (const [key, value] of Object.entries(secrets)) {
await processAwsSecret(key, value.value);
await processAwsSecret(key, value.value, value.secretMetadata).catch((error) => {
error.secretKey = key;
throw error;
});
}
} else {
await processAwsSecret(integration.app as string, getSecretKeyValuePair(secrets));
@ -2754,13 +2775,23 @@ const syncSecretsAzureDevops = async ({
* Sync/push [secrets] to GitLab repo with name [integration.app]
*/
const syncSecretsGitLab = async ({
createManySecretsRawFn,
integrationAuth,
integration,
secrets,
accessToken
}: {
createManySecretsRawFn: (params: TCreateManySecretsRawFn) => Promise<Array<{ id: string }>>;
integrationAuth: TIntegrationAuths;
integration: TIntegrations;
integration: TIntegrations & {
projectId: string;
environment: {
id: string;
name: string;
slug: string;
};
secretPath: string;
};
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
}) => {
@ -2817,6 +2848,81 @@ const syncSecretsGitLab = async ({
return isValid;
});
if (!integration.lastUsed) {
const secretsToAddToInfisical: { [key: string]: GitLabSecret } = {};
const secretsToRemoveInGitlab: GitLabSecret[] = [];
if (!metadata.initialSyncBehavior) {
metadata.initialSyncBehavior = IntegrationInitialSyncBehavior.OVERWRITE_TARGET;
}
getSecretsRes.forEach((gitlabSecret) => {
// first time using integration
// -> apply initial sync behavior
switch (metadata.initialSyncBehavior) {
// Override all the secrets in GitLab
case IntegrationInitialSyncBehavior.OVERWRITE_TARGET: {
if (!(gitlabSecret.key in secrets)) {
secretsToRemoveInGitlab.push(gitlabSecret);
}
break;
}
case IntegrationInitialSyncBehavior.PREFER_SOURCE: {
// if the secret is not in infisical, we need to add it to infisical
if (!(gitlabSecret.key in secrets)) {
secrets[gitlabSecret.key] = {
value: gitlabSecret.value
};
// need to remove prefix and suffix from what we're saving to Infisical
const prefix = metadata?.secretPrefix || "";
const suffix = metadata?.secretSuffix || "";
let processedKey = gitlabSecret.key;
// Remove prefix if it exists at the start
if (prefix && processedKey.startsWith(prefix)) {
processedKey = processedKey.slice(prefix.length);
}
// Remove suffix if it exists at the end
if (suffix && processedKey.endsWith(suffix)) {
processedKey = processedKey.slice(0, -suffix.length);
}
secretsToAddToInfisical[processedKey] = gitlabSecret;
}
break;
}
default: {
throw new Error(`Invalid initial sync behavior: ${metadata.initialSyncBehavior}`);
}
}
});
if (Object.keys(secretsToAddToInfisical).length) {
await createManySecretsRawFn({
projectId: integration.projectId,
environment: integration.environment.slug,
path: integration.secretPath,
secrets: Object.keys(secretsToAddToInfisical).map((key) => ({
secretName: key,
secretValue: secretsToAddToInfisical[key].value,
type: SecretType.Shared
}))
});
}
for await (const gitlabSecret of secretsToRemoveInGitlab) {
await request.delete(
`${gitLabApiUrl}/v4/projects/${integration?.appId}/variables/${gitlabSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
{
headers: {
Authorization: `Bearer ${accessToken}`
}
}
);
}
}
for await (const key of Object.keys(secrets)) {
const existingSecret = getSecretsRes.find((s) => s.key === key);
if (!existingSecret) {
@ -3644,17 +3750,28 @@ const syncSecretsCloudflarePages = async ({
);
const metadata = z.record(z.any()).parse(integration.metadata);
if (metadata.shouldAutoRedeploy) {
await request.post(
`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}/deployments`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
if (metadata.shouldAutoRedeploy && integration.targetEnvironment === "production") {
await request
.post(
`${IntegrationUrls.CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}/deployments`,
{},
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json"
}
}
}
);
)
.catch((error) => {
if (error instanceof AxiosError && error.response?.status === 304) {
logger.info(
`syncSecretsCloudflarePages: CF pages redeployment returned status code 304 for integration [id=${integration.id}]`
);
return;
}
throw error;
});
}
};
@ -4431,7 +4548,7 @@ export const syncIntegrationSecrets = async ({
secretPath: string;
};
integrationAuth: TIntegrationAuths;
secrets: Record<string, { value: string; comment?: string }>;
secrets: Record<string, { value: string; comment?: string; secretMetadata?: ResourceMetadataDTO }>;
accessId: string | null;
awsAssumeRoleArn: string | null;
accessToken: string;
@ -4536,7 +4653,8 @@ export const syncIntegrationSecrets = async ({
integrationAuth,
integration,
secrets,
accessToken
accessToken,
createManySecretsRawFn
});
break;
case Integrations.RENDER:

View File

@ -1,3 +1,5 @@
import { AxiosResponse } from "axios";
import { request } from "@app/lib/config/request";
import { BadRequestError } from "@app/lib/errors";
@ -11,19 +13,27 @@ const getTeamsGitLab = async ({ url, accessToken }: { url: string; accessToken:
const gitLabApiUrl = url ? `${url}/api` : IntegrationUrls.GITLAB_API_URL;
let teams: Team[] = [];
const res = (
await request.get<{ name: string; id: string }[]>(`${gitLabApiUrl}/v4/groups`, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
let page: number = 1;
while (page > 0) {
// eslint-disable-next-line no-await-in-loop
const { data, headers }: AxiosResponse<{ name: string; id: string }[]> = await request.get(
`${gitLabApiUrl}/v4/groups?page=${page}`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json"
}
}
})
).data;
);
teams = res.map((t) => ({
name: t.name,
id: t.id.toString()
}));
page = Number(headers["x-next-page"] ?? "");
teams = teams.concat(
data.map((t) => ({
name: t.name,
id: t.id.toString()
}))
);
}
return teams;
};

View File

@ -2,7 +2,7 @@ import { z } from "zod";
import { INTEGRATION } from "@app/lib/api-docs";
import { IntegrationMappingBehavior } from "../integration-auth/integration-list";
import { IntegrationMappingBehavior, IntegrationMetadataSyncMode } from "../integration-auth/integration-list";
export const IntegrationMetadataSchema = z.object({
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
@ -50,6 +50,11 @@ export const IntegrationMetadataSchema = z.object({
shouldMaskSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldMaskSecrets),
shouldProtectSecrets: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldProtectSecrets),
metadataSyncMode: z
.nativeEnum(IntegrationMetadataSyncMode)
.optional()
.describe(INTEGRATION.CREATE.metadata.metadataSyncMode),
octopusDeployScopeValues: z
.object({
// in Octopus Deploy Scope Value Format

View File

@ -1,6 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { NotFoundError } from "@app/lib/errors";
@ -81,14 +81,14 @@ export const integrationServiceFactory = ({
if (!integrationAuth)
throw new NotFoundError({ message: `Integration auth with ID '${integrationAuthId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integrationAuth.projectId,
projectId: integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
ForbiddenError.from(permission).throwUnlessCan(
@ -160,14 +160,14 @@ export const integrationServiceFactory = ({
const integration = await integrationDAL.findById(id);
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration.projectId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
const newEnvironment = environment || integration.environment.slug;
@ -227,13 +227,14 @@ export const integrationServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration?.projectId || "",
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
if (!integration) {
@ -254,13 +255,14 @@ export const integrationServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration?.projectId || "",
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const integrationAuth = await integrationAuthDAL.findById(integration.integrationAuthId);
@ -296,14 +298,14 @@ export const integrationServiceFactory = ({
const integration = await integrationDAL.findById(id);
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration.projectId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
const integrationAuth = await integrationAuthDAL.findById(integration.integrationAuthId);
@ -333,13 +335,14 @@ export const integrationServiceFactory = ({
actorAuthMethod,
projectId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const integrations = await integrationDAL.findByProjectId(projectId);
@ -352,13 +355,14 @@ export const integrationServiceFactory = ({
throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
integration.projectId,
projectId: integration.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
await secretQueueService.syncIntegrations({

View File

@ -5,6 +5,7 @@ import jwt from "jsonwebtoken";
import { Knex } from "knex";
import {
ActionProjectType,
OrgMembershipRole,
OrgMembershipStatus,
ProjectMembershipRole,
@ -773,13 +774,14 @@ export const orgServiceFactory = ({
// if there exist no project membership we set is as given by the request
for await (const project of projectsToInvite) {
const projectId = project.id;
const { permission: projectPermission } = await permissionService.getProjectPermission(
const { permission: projectPermission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(projectPermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Member

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType, ProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@ -86,14 +86,14 @@ export const pkiAlertServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
@ -116,13 +116,14 @@ export const pkiAlertServiceFactory = ({
const alert = await pkiAlertDAL.findById(alertId);
if (!alert) throw new NotFoundError({ message: `Alert with ID '${alertId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
alert.projectId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
return alert;
@ -142,14 +143,14 @@ export const pkiAlertServiceFactory = ({
let alert = await pkiAlertDAL.findById(alertId);
if (!alert) throw new NotFoundError({ message: `Alert with ID '${alertId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
alert.projectId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
@ -175,14 +176,14 @@ export const pkiAlertServiceFactory = ({
let alert = await pkiAlertDAL.findById(alertId);
if (!alert) throw new NotFoundError({ message: `Alert with ID '${alertId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
alert.projectId,
projectId: alert.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts);
alert = await pkiAlertDAL.deleteById(alertId);

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType, TPkiCollectionItems } from "@app/db/schemas";
import { ActionProjectType, ProjectType, TPkiCollectionItems } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -62,14 +62,14 @@ export const pkiCollectionServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -95,13 +95,14 @@ export const pkiCollectionServiceFactory = ({
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
return pkiCollection;
@ -119,14 +120,14 @@ export const pkiCollectionServiceFactory = ({
let pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
pkiCollection = await pkiCollectionDAL.updateById(collectionId, {
@ -147,14 +148,14 @@ export const pkiCollectionServiceFactory = ({
let pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -177,13 +178,14 @@ export const pkiCollectionServiceFactory = ({
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
@ -220,14 +222,14 @@ export const pkiCollectionServiceFactory = ({
const pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${collectionId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -314,14 +316,14 @@ export const pkiCollectionServiceFactory = ({
if (!pkiCollectionItem) throw new NotFoundError({ message: `PKI collection item with ID '${itemId}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
pkiCollection.projectId,
projectId: pkiCollection.projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectVersion } from "@app/db/schemas";
import { ActionProjectType, ProjectVersion } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
@ -41,13 +41,14 @@ export const projectBotServiceFactory = ({
botKey,
publicKey
}: TFindBotByProjectIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const bot = await projectBotDAL.transaction(async (tx) => {
@ -107,13 +108,14 @@ export const projectBotServiceFactory = ({
const bot = await projectBotDAL.findById(botId);
if (!bot) throw new NotFoundError({ message: `Project bot with ID '${botId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
bot.projectId,
projectId: bot.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
const project = await projectBotDAL.findProjectByBotId(botId);

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -42,14 +42,14 @@ export const projectEnvServiceFactory = ({
name,
slug
}: TCreateEnvDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
const lock = await keyStore
@ -131,14 +131,14 @@ export const projectEnvServiceFactory = ({
id,
position
}: TUpdateEnvDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
const lock = await keyStore
@ -195,14 +195,14 @@ export const projectEnvServiceFactory = ({
};
const deleteEnvironment = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, id }: TDeleteEnvDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
const lock = await keyStore
@ -251,13 +251,14 @@ export const projectEnvServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
environment.projectId,
projectId: environment.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);

View File

@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
@ -31,13 +32,14 @@ export const projectKeyServiceFactory = ({
nonce,
encryptedKey
}: TUploadProjectKeyDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const receiverMembership = await projectMembershipDAL.findOne({
@ -60,7 +62,14 @@ export const projectKeyServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TGetLatestProjectKeyDTO) => {
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
const latestKey = await projectKeyDAL.findLatestProjectKey(actorId, projectId);
return latestKey;
};
@ -72,13 +81,14 @@ export const projectKeyServiceFactory = ({
actorAuthMethod,
projectId
}: TGetLatestProjectKeyDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return projectKeyDAL.findAllProjectUserPubKeys(projectId);
};

View File

@ -2,7 +2,7 @@
import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -78,13 +78,14 @@ export const projectMembershipServiceFactory = ({
includeGroupMembers,
projectId
}: TGetProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
@ -121,13 +122,14 @@ export const projectMembershipServiceFactory = ({
projectId,
username
}: TGetProjectMembershipByUsernameDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { username });
@ -143,13 +145,14 @@ export const projectMembershipServiceFactory = ({
projectId,
id
}: TGetProjectMembershipByIdDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
const [membership] = await projectMembershipDAL.findAllProjectMembers(projectId, { id });
@ -169,13 +172,14 @@ export const projectMembershipServiceFactory = ({
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
const orgMembers = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: project.orgId,
@ -249,13 +253,14 @@ export const projectMembershipServiceFactory = ({
membershipId,
roles
}: TUpdateProjectMembershipDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
const membershipUser = await userDAL.findUserByProjectMembershipId(membershipId);
@ -348,13 +353,14 @@ export const projectMembershipServiceFactory = ({
projectId,
membershipId
}: TDeleteProjectMembershipOldDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
const member = await userDAL.findUserByProjectMembershipId(membershipId);
@ -383,13 +389,14 @@ export const projectMembershipServiceFactory = ({
emails,
usernames
}: TDeleteProjectMembershipsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
const project = await projectDAL.findById(projectId);

View File

@ -1,7 +1,7 @@
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { ProjectMembershipRole, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import {
ProjectPermissionActions,
@ -58,13 +58,14 @@ export const projectRoleServiceFactory = ({
projectId = filter.projectId;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
if (existingRole) {
@ -95,13 +96,14 @@ export const projectRoleServiceFactory = ({
projectId = filter.projectId;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
if (roleSlug !== "custom" && Object.values(ProjectMembershipRole).includes(roleSlug as ProjectMembershipRole)) {
const predefinedRole = getPredefinedRoles(projectId, roleSlug as ProjectMembershipRole)[0];
@ -117,13 +119,14 @@ export const projectRoleServiceFactory = ({
const projectRole = await projectRoleDAL.findById(roleId);
if (!projectRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectRole.projectId,
projectId: projectRole.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
if (data?.slug) {
@ -144,13 +147,14 @@ export const projectRoleServiceFactory = ({
const deleteRole = async ({ actor, actorId, actorAuthMethod, actorOrgId, roleId }: TDeleteRoleDTO) => {
const projectRole = await projectRoleDAL.findById(roleId);
if (!projectRole) throw new NotFoundError({ message: "Project role not found", name: "Delete role" });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectRole.projectId,
projectId: projectRole.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
const identityRole = await identityProjectMembershipRoleDAL.findOne({ customRoleId: roleId });
@ -185,13 +189,14 @@ export const projectRoleServiceFactory = ({
projectId = filter.projectId;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
const customRoles = await projectRoleDAL.find(
{ projectId },
@ -208,12 +213,13 @@ export const projectRoleServiceFactory = ({
actorAuthMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => {
const { permission, membership } = await permissionService.getUserProjectPermission(
const { permission, membership } = await permissionService.getUserProjectPermission({
userId,
projectId,
actorAuthMethod,
actorOrgId
);
authMethod: actorAuthMethod,
userOrgId: actorOrgId,
actionProjectType: ActionProjectType.Any
});
return { permissions: packRules(permission.rules), membership };
};

View File

@ -1,7 +1,13 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import { ProjectMembershipRole, ProjectType, ProjectVersion, TProjectEnvironments } from "@app/db/schemas";
import {
ActionProjectType,
ProjectMembershipRole,
ProjectType,
ProjectVersion,
TProjectEnvironments
} from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
@ -427,13 +433,14 @@ export const projectServiceFactory = ({
const deleteProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, filter }: TDeleteProjectDTO) => {
const project = await projectDAL.findProjectByFilter(filter);
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
const deletedProject = await projectDAL.transaction(async (tx) => {
@ -510,20 +517,28 @@ export const projectServiceFactory = ({
const getAProject = async ({ actorId, actorOrgId, actorAuthMethod, filter, actor }: TGetProjectDTO) => {
const project = await projectDAL.findProjectByFilter(filter);
await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId: project.id,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.Any
});
return project;
};
const updateProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, update, filter }: TUpdateProjectDTO) => {
const project = await projectDAL.findProjectByFilter(filter);
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(project.id, {
@ -542,13 +557,14 @@ export const projectServiceFactory = ({
actorAuthMethod,
autoCapitalization
}: TToggleProjectAutoCapitalizationDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization });
@ -570,13 +586,14 @@ export const projectServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!hasRole(ProjectMembershipRole.Admin))
throw new ForbiddenRequestError({
@ -601,13 +618,14 @@ export const projectServiceFactory = ({
});
}
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({
@ -633,13 +651,14 @@ export const projectServiceFactory = ({
actorAuthMethod,
name
}: TUpdateProjectNameDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
const updatedProject = await projectDAL.updateById(projectId, { name });
@ -654,13 +673,14 @@ export const projectServiceFactory = ({
actorOrgId,
userPrivateKey
}: TUpgradeProjectDTO) => {
const { permission, hasRole } = await permissionService.getProjectPermission(
const { permission, hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
@ -691,13 +711,14 @@ export const projectServiceFactory = ({
actorOrgId,
actorId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
const project = await projectDAL.findProjectById(projectId);
@ -736,13 +757,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -786,14 +808,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
@ -841,14 +863,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
@ -877,14 +899,14 @@ export const projectServiceFactory = ({
if (certManagerProjectFromSplit) {
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
@ -914,14 +936,14 @@ export const projectServiceFactory = ({
projectId = certManagerProjectFromSplit.id;
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.CertificateManager);
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -945,15 +967,15 @@ export const projectServiceFactory = ({
actor,
projectId
}: TListProjectSshCasDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateAuthorities
@ -981,15 +1003,15 @@ export const projectServiceFactory = ({
actor,
projectId
}: TListProjectSshCertificatesDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
const cas = await sshCertificateAuthorityDAL.find({
@ -1020,15 +1042,15 @@ export const projectServiceFactory = ({
actor,
projectId
}: TListProjectSshCertificateTemplatesDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SSH
});
ForbidOnInvalidProjectType(ProjectType.SSH);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.SshCertificateTemplates
@ -1055,13 +1077,14 @@ export const projectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TUpdateProjectKmsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@ -1082,13 +1105,14 @@ export const projectServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@ -1111,13 +1135,14 @@ export const projectServiceFactory = ({
actorOrgId,
backup
}: TLoadProjectKmsBackupDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
@ -1133,13 +1158,14 @@ export const projectServiceFactory = ({
};
const getProjectKmsKeys = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TGetProjectKmsKey) => {
const { membership } = await permissionService.getProjectPermission(
const { membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@ -1165,13 +1191,14 @@ export const projectServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
@ -1213,13 +1240,14 @@ export const projectServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.Any
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);

View File

@ -0,0 +1,11 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TResourceMetadataDALFactory = ReturnType<typeof resourceMetadataDALFactory>;
export const resourceMetadataDALFactory = (db: TDbClient) => {
const orm = ormify(db, TableName.ResourceMetadata);
return orm;
};

View File

@ -0,0 +1,10 @@
import z from "zod";
export const ResourceMetadataSchema = z
.object({
key: z.string().trim().min(1),
value: z.string().trim().default("")
})
.array();
export type ResourceMetadataDTO = z.infer<typeof ResourceMetadataSchema>;

View File

@ -1,4 +1,4 @@
import { ProjectMembershipRole } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@ -31,7 +31,14 @@ export const secretBlindIndexServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TGetProjectBlindIndexStatusDTO) => {
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId);
return Number(secretCount);
@ -44,13 +51,14 @@ export const secretBlindIndexServiceFactory = ({
actorOrgId,
actor
}: TGetProjectSecretsDTO) => {
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" });
}
@ -67,13 +75,14 @@ export const secretBlindIndexServiceFactory = ({
actorOrgId,
secretsToUpdate
}: TUpdateProjectSecretNameDTO) => {
const { hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin)) {
throw new ForbiddenRequestError({ message: "Insufficient privileges, user must be admin" });
}

View File

@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import path from "path";
import { v4 as uuidv4, validate as uuidValidate } from "uuid";
import { ProjectType, TSecretFoldersInsert } from "@app/db/schemas";
import { ActionProjectType, TSecretFoldersInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretSnapshotServiceFactory } from "@app/ee/services/secret-snapshot/secret-snapshot-service";
@ -52,14 +52,14 @@ export const secretFolderServiceFactory = ({
environment,
path: secretPath
}: TCreateFolderDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
@ -151,14 +151,14 @@ export const secretFolderServiceFactory = ({
throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
project.id,
projectId: project.id,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
folders.forEach(({ environment, path: secretPath }) => {
ForbiddenError.from(permission).throwUnlessCan(
@ -261,14 +261,14 @@ export const secretFolderServiceFactory = ({
path: secretPath,
id
}: TUpdateFolderDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -342,14 +342,14 @@ export const secretFolderServiceFactory = ({
path: secretPath,
idOrName
}: TDeleteFolderDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -399,7 +399,14 @@ export const secretFolderServiceFactory = ({
}: TGetFolderDTO) => {
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const env = await projectEnvDAL.findOne({ projectId, slug: environment });
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
@ -436,7 +443,14 @@ export const secretFolderServiceFactory = ({
}: Omit<TGetFolderDTO, "environment"> & { environments: string[] }) => {
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const envs = await projectEnvDAL.findBySlugs(projectId, environments);
@ -471,7 +485,14 @@ export const secretFolderServiceFactory = ({
}: Omit<TGetFolderDTO, "environment"> & { environments: string[] }) => {
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const envs = await projectEnvDAL.findBySlugs(projectId, environments);
@ -500,7 +521,14 @@ export const secretFolderServiceFactory = ({
if (!folder) throw new NotFoundError({ message: `Folder with ID '${id}' not found` });
// folder list is allowed to be read by anyone
// permission to check does user has access
await permissionService.getProjectPermission(actor, actorId, folder.projectId, actorAuthMethod, actorOrgId);
await permissionService.getProjectPermission({
actor,
actorId,
projectId: folder.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const [folderWithPath] = await folderDAL.findSecretPathByFolderIds(folder.projectId, [folder.id]);
@ -522,7 +550,14 @@ export const secretFolderServiceFactory = ({
) => {
// folder list is allowed to be read by anyone
// permission to check does user have access
await permissionService.getProjectPermission(actor.type, actor.id, projectId, actor.authMethod, actor.orgId);
await permissionService.getProjectPermission({
actor: actor.type,
actorId: actor.id,
projectId,
actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
});
const envs = await projectEnvDAL.findBySlugs(projectId, environments);

View File

@ -1,6 +1,7 @@
import { SecretType, TSecretImports, TSecrets, TSecretsV2 } from "@app/db/schemas";
import { groupBy, unique } from "@app/lib/fn";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretDALFactory } from "../secret/secret-dal";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
@ -39,6 +40,7 @@ type TSecretImportSecretsV2 = {
// But for somereason ts consider ? and undefined explicit as different just ts things
secretValue: string;
secretComment: string;
secretMetadata?: ResourceMetadataDTO;
})[];
};

View File

@ -2,7 +2,7 @@ import path from "node:path";
import { ForbiddenError, subject } from "@casl/ability";
import { ProjectType, TableName } from "@app/db/schemas";
import { ActionProjectType, TableName } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@ -73,14 +73,14 @@ export const secretImportServiceFactory = ({
isReplication,
path: secretPath
}: TCreateSecretImportDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// check if user has permission to import into destination path
ForbiddenError.from(permission).throwUnlessCan(
@ -160,6 +160,7 @@ export const secretImportServiceFactory = ({
if (secImport.isReplication && sourceFolder) {
await secretQueueService.replicateSecrets({
secretPath: secImport.importPath,
orgId: actorOrgId,
projectId,
environmentSlug: importEnv.slug,
pickOnlyImportIds: [secImport.id],
@ -169,6 +170,7 @@ export const secretImportServiceFactory = ({
} else {
await secretQueueService.syncSecrets({
secretPath,
orgId: actorOrgId,
projectId,
environmentSlug: environment,
actorId,
@ -190,14 +192,14 @@ export const secretImportServiceFactory = ({
data,
id
}: TUpdateSecretImportDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
@ -286,14 +288,14 @@ export const secretImportServiceFactory = ({
actorAuthMethod,
id
}: TDeleteSecretImportDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
@ -340,6 +342,7 @@ export const secretImportServiceFactory = ({
await secretQueueService.syncSecrets({
secretPath,
orgId: actorOrgId,
projectId,
environmentSlug: environment,
actor,
@ -359,13 +362,14 @@ export const secretImportServiceFactory = ({
path: secretPath,
id: secretImportDocId
}: TResyncSecretImportReplicationDTO) => {
const { permission, membership } = await permissionService.getProjectPermission(
const { permission, membership } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// check if user has permission to import into destination path
ForbiddenError.from(permission).throwUnlessCan(
@ -415,6 +419,7 @@ export const secretImportServiceFactory = ({
if (membership && sourceFolder) {
await secretQueueService.replicateSecrets({
orgId: actorOrgId,
secretPath: secretImportDoc.importPath,
projectId,
environmentSlug: secretImportDoc.importEnv.slug,
@ -437,13 +442,14 @@ export const secretImportServiceFactory = ({
actorOrgId,
search
}: TGetSecretImportsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
@ -472,13 +478,14 @@ export const secretImportServiceFactory = ({
limit,
offset
}: TGetSecretImportsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
@ -521,13 +528,14 @@ export const secretImportServiceFactory = ({
});
}
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
folder.projectId,
projectId: folder.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
@ -569,13 +577,14 @@ export const secretImportServiceFactory = ({
actorId,
actorOrgId
}: TGetSecretsFromImportDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })
@ -606,13 +615,14 @@ export const secretImportServiceFactory = ({
actorId,
actorOrgId
}: TGetSecretsFromImportDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
subject(ProjectPermissionSub.SecretImports, { environment, secretPath })

View File

@ -206,8 +206,13 @@ export const secretSharingServiceFactory = ({
const orgName = sharedSecret.orgId ? (await orgDAL.findOrgById(sharedSecret.orgId))?.name : "";
if (accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId)
if (accessType === SecretSharingAccessType.Organization && orgId === undefined) {
throw new UnauthorizedError();
}
if (accessType === SecretSharingAccessType.Organization && orgId !== sharedSecret.orgId) {
throw new ForbiddenRequestError();
}
// all secrets pass through here, meaning we check if its expired first and then check if it needs verification
// or can be safely sent to the client.

View File

@ -1,6 +1,6 @@
import { ForbiddenError } from "@casl/ability";
import { ProjectType } from "@app/db/schemas";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
@ -24,15 +24,15 @@ export type TSecretTagServiceFactory = ReturnType<typeof secretTagServiceFactory
export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => {
const createTag = async ({ slug, actor, color, actorId, actorOrgId, actorAuthMethod, projectId }: TCreateTagDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
const existingTag = await secretTagDAL.findOne({ slug, projectId });
if (existingTag) throw new BadRequestError({ message: "Tag already exist" });
@ -56,15 +56,15 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
if (existingTag && existingTag.id !== tag.id) throw new BadRequestError({ message: "Tag already exist" });
}
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
tag.projectId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
const updatedTag = await secretTagDAL.updateById(tag.id, { color, slug });
return updatedTag;
@ -74,15 +74,15 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
const tag = await secretTagDAL.findById(id);
if (!tag) throw new NotFoundError({ message: `Tag with ID '${id}' not found` });
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
tag.projectId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
const deletedTag = await secretTagDAL.deleteById(tag.id);
return deletedTag;
@ -92,13 +92,14 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
const tag = await secretTagDAL.findById(id);
if (!tag) throw new NotFoundError({ message: `Tag with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
tag.projectId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
return { ...tag, name: tag.slug };
@ -108,26 +109,28 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
const tag = await secretTagDAL.findOne({ projectId, slug });
if (!tag) throw new NotFoundError({ message: `Tag with slug '${slug}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
tag.projectId,
projectId: tag.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
return { ...tag, name: tag.slug };
};
const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
const tags = await secretTagDAL.find({ projectId }, { sort: [["createdAt", "asc"]] });

View File

@ -78,6 +78,12 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id`
)
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
@ -103,6 +109,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug,
name: slug
})
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
@ -221,7 +236,9 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
const secs = await (tx || db.replicaNode())(TableName.SecretV2)
.where({ folderId })
.where((bd) => {
void bd.whereNull("userId").orWhere({ userId: userId || null });
void bd
.whereNull(`${TableName.SecretV2}.userId`)
.orWhere({ [`${TableName.SecretV2}.userId` as "userId"]: userId || null });
})
.leftJoin(
TableName.SecretV2JnTag,
@ -233,10 +250,16 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id`
)
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.orderBy("id", "asc");
const data = sqlNestRelationships({
@ -253,6 +276,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug,
name: slug
})
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
@ -367,7 +399,9 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
}
})
.where((bd) => {
void bd.whereNull(`${TableName.SecretV2}.userId`).orWhere({ userId: userId || null });
void bd
.whereNull(`${TableName.SecretV2}.userId`)
.orWhere({ [`${TableName.SecretV2}.userId` as "userId"]: userId || null });
})
.leftJoin(
TableName.SecretV2JnTag,
@ -379,13 +413,23 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id`
)
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(
selectAllTableCols(TableName.SecretV2),
db.raw(`DENSE_RANK() OVER (ORDER BY "key" ${filters?.orderDirection ?? OrderByDirection.ASC}) as rank`)
db.raw(
`DENSE_RANK() OVER (ORDER BY "${TableName.SecretV2}".key ${
filters?.orderDirection ?? OrderByDirection.ASC
}) as rank`
)
)
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
)
.where((bd) => {
const slugs = filters?.tagSlugs?.filter(Boolean);
if (slugs && slugs.length > 0) {
@ -425,6 +469,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug,
name: slug
})
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});
@ -545,10 +598,17 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
`${TableName.SecretTag}.id`
)
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
.select(selectAllTableCols(TableName.SecretV2))
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
.select(
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
);
const docs = sqlNestRelationships({
data: rawDocs,
key: "id",
@ -563,6 +623,15 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
slug,
name: slug
})
},
{
key: "metadataId",
label: "secretMetadata" as const,
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
id: metadataId,
key: metadataKey,
value: metadataValue
})
}
]
});

View File

@ -6,6 +6,7 @@ import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
@ -54,9 +55,11 @@ export const getAllSecretReferences = (maybeSecretReference: string) => {
export const fnSecretBulkInsert = async ({
// TODO: Pick types here
folderId,
orgId,
inputSecrets,
secretDAL,
secretVersionDAL,
resourceMetadataDAL,
secretTagDAL,
secretVersionTagDAL,
tx
@ -91,6 +94,7 @@ export const fnSecretBulkInsert = async ({
sanitizedInputSecrets.map((el) => ({ ...el, folderId })),
tx
);
const newSecretGroupedByKeyName = groupBy(newSecrets, (item) => item.key);
const newSecretTags = inputSecrets.flatMap(({ tagIds: secretTags = [], key }) =>
secretTags.map((tag) => ({
@ -106,6 +110,7 @@ export const fnSecretBulkInsert = async ({
})),
tx
);
await secretDAL.upsertSecretReferences(
inputSecrets.map(({ references = [], key }) => ({
secretId: newSecretGroupedByKeyName[key][0].id,
@ -113,6 +118,22 @@ export const fnSecretBulkInsert = async ({
})),
tx
);
await resourceMetadataDAL.insertMany(
inputSecrets.flatMap(({ key: secretKey, secretMetadata }) => {
if (secretMetadata) {
return secretMetadata.map(({ key, value }) => ({
key,
value,
secretId: newSecretGroupedByKeyName[secretKey][0].id,
orgId
}));
}
return [];
}),
tx
);
if (newSecretTags.length) {
const secTags = await secretTagDAL.saveTagsToSecretV2(newSecretTags, tx);
const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);
@ -120,6 +141,7 @@ export const fnSecretBulkInsert = async ({
[`${TableName.SecretVersionV2}Id` as const]: secVersionsGroupBySecId[secrets_v2Id][0].id,
[`${TableName.SecretTag}Id` as const]: secret_tagsId
}));
await secretVersionTagDAL.insertMany(newSecretVersionTags, tx);
}
@ -130,10 +152,12 @@ export const fnSecretBulkUpdate = async ({
tx,
inputSecrets,
folderId,
orgId,
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL
secretVersionTagDAL,
resourceMetadataDAL
}: TFnSecretBulkUpdate) => {
const sanitizedInputSecrets = inputSecrets.map(
({
@ -231,6 +255,34 @@ export const fnSecretBulkUpdate = async ({
}
}
const inputSecretIdsWithMetadata = inputSecrets
.filter((sec) => Boolean(sec.data.secretMetadata))
.map((sec) => sec.filter.id);
await resourceMetadataDAL.delete(
{
$in: {
secretId: inputSecretIdsWithMetadata
}
},
tx
);
await resourceMetadataDAL.insertMany(
inputSecrets.flatMap(({ filter: { id }, data: { secretMetadata } }) => {
if (secretMetadata) {
return secretMetadata.map(({ key, value }) => ({
key,
value,
secretId: id,
orgId
}));
}
return [];
}),
tx
);
return newSecrets.map((secret) => ({ ...secret, _id: secret.id }));
};
@ -570,6 +622,7 @@ export const reshapeBridgeSecret = (
color?: string | null;
name: string;
}[];
secretMetadata?: ResourceMetadataDTO;
}
) => ({
secretKey: secret.key,
@ -588,6 +641,7 @@ export const reshapeBridgeSecret = (
secretReminderRepeatDays: secret.reminderRepeatDays,
secretReminderNote: secret.reminderNote,
metadata: secret.metadata,
secretMetadata: secret.secretMetadata,
createdAt: secret.createdAt,
updatedAt: secret.updatedAt
});

View File

@ -1,7 +1,7 @@
import { ForbiddenError, PureAbility, subject } from "@casl/ability";
import { z } from "zod";
import { ProjectMembershipRole, ProjectType, SecretsV2Schema, SecretType, TableName } from "@app/db/schemas";
import { ActionProjectType, ProjectMembershipRole, SecretsV2Schema, SecretType, TableName } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
@ -18,6 +18,7 @@ import { ActorType } from "../auth/auth-type";
import { TKmsServiceFactory } from "../kms/kms-service";
import { KmsDataKey } from "../kms/kms-types";
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { TSecretQueueFactory } from "../secret/secret-queue";
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
@ -74,6 +75,7 @@ type TSecretV2BridgeServiceFactoryDep = {
"insertV2Bridge" | "insertApprovalSecretV2Tags"
>;
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
};
export type TSecretV2BridgeServiceFactory = ReturnType<typeof secretV2BridgeServiceFactory>;
@ -95,7 +97,8 @@ export const secretV2BridgeServiceFactory = ({
secretApprovalPolicyService,
secretApprovalRequestDAL,
secretApprovalRequestSecretDAL,
kmsService
kmsService,
resourceMetadataDAL
}: TSecretV2BridgeServiceFactoryDep) => {
const $validateSecretReferences = async (
projectId: string,
@ -141,7 +144,7 @@ export const secretV2BridgeServiceFactory = ({
},
{
operator: "eq",
field: "key",
field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey
}
]
@ -186,16 +189,17 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod,
projectId,
secretPath,
secretMetadata,
...inputSecret
}: TCreateSecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
@ -255,6 +259,7 @@ export const secretV2BridgeServiceFactory = ({
const secret = await secretDAL.transaction((tx) =>
fnSecretBulkInsert({
folderId,
orgId: actorOrgId,
inputSecrets: [
{
version: 1,
@ -272,9 +277,11 @@ export const secretV2BridgeServiceFactory = ({
key: secretName,
userId: inputSecret.type === SecretType.Personal ? actorId : null,
tagIds: inputSecret.tagIds,
references: nestedReferences
references: nestedReferences,
secretMetadata
}
],
resourceMetadataDAL,
secretDAL,
secretVersionDAL,
secretTagDAL,
@ -287,6 +294,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(folderId);
await secretQueueService.syncSecrets({
secretPath,
orgId: actorOrgId,
actorId,
actor,
projectId,
@ -309,16 +317,17 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod,
projectId,
secretPath,
secretMetadata,
...inputSecret
}: TUpdateSecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (inputSecret.newSecretName === "") {
throw new BadRequestError({ message: "New secret name cannot be empty" });
@ -435,6 +444,8 @@ export const secretV2BridgeServiceFactory = ({
const updatedSecret = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({
folderId,
orgId: actorOrgId,
resourceMetadataDAL,
inputSecrets: [
{
filter: { id: secretId },
@ -448,6 +459,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: inputSecret.skipMultilineEncoding,
key: inputSecret.newSecretName || secretName,
tags: inputSecret.tagIds,
secretMetadata,
...encryptedValue
}
}
@ -475,6 +487,7 @@ export const secretV2BridgeServiceFactory = ({
actorId,
actor,
projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug
});
}
@ -496,14 +509,14 @@ export const secretV2BridgeServiceFactory = ({
secretPath,
...inputSecret
}: TDeleteSecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
@ -562,6 +575,7 @@ export const secretV2BridgeServiceFactory = ({
actorId,
actor,
projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug
});
}
@ -597,13 +611,14 @@ export const secretV2BridgeServiceFactory = ({
isInternal?: boolean;
}) => {
if (!isInternal) {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
}
@ -643,13 +658,14 @@ export const secretV2BridgeServiceFactory = ({
| "environment"
| "search"
>) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
@ -726,13 +742,14 @@ export const secretV2BridgeServiceFactory = ({
environments: string[];
isInternal?: boolean;
}) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!isInternal) {
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
}
@ -775,13 +792,14 @@ export const secretV2BridgeServiceFactory = ({
expandSecretReferences: shouldExpandSecretReferences,
...params
}: TGetSecretsDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
@ -928,13 +946,14 @@ export const secretV2BridgeServiceFactory = ({
includeImports,
expandSecretReferences: shouldExpandSecretReferences
}: TGetASecretDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder)
@ -961,8 +980,8 @@ export const secretV2BridgeServiceFactory = ({
? secretDAL.findOneWithTags({
folderId,
type: secretType,
key: secretName,
userId: secretType === SecretType.Personal ? actorId : null
[`${TableName.SecretV2}.key` as "key"]: secretName,
[`${TableName.SecretV2}.userId` as "userId"]: secretType === SecretType.Personal ? actorId : null
})
: secretVersionDAL
.findOne({
@ -1084,14 +1103,14 @@ export const secretV2BridgeServiceFactory = ({
projectId,
secrets: inputSecrets
}: TCreateManySecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
@ -1113,7 +1132,7 @@ export const secretV2BridgeServiceFactory = ({
value: [
{
operator: "eq",
field: "key",
field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey
},
{
@ -1185,11 +1204,14 @@ export const secretV2BridgeServiceFactory = ({
key: el.secretKey,
tagIds: el.tagIds,
references,
secretMetadata: el.secretMetadata,
type: SecretType.Shared
};
}),
folderId,
orgId: actorOrgId,
secretDAL,
resourceMetadataDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL,
@ -1203,6 +1225,7 @@ export const secretV2BridgeServiceFactory = ({
actorId,
secretPath,
projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug
});
@ -1225,14 +1248,14 @@ export const secretV2BridgeServiceFactory = ({
secretPath,
secrets: inputSecrets
}: TUpdateManySecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
@ -1254,7 +1277,7 @@ export const secretV2BridgeServiceFactory = ({
value: [
{
operator: "eq",
field: "key",
field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey
},
{
@ -1319,7 +1342,7 @@ export const secretV2BridgeServiceFactory = ({
value: [
{
operator: "eq",
field: "key",
field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey
},
{
@ -1371,6 +1394,7 @@ export const secretV2BridgeServiceFactory = ({
const secrets = await secretDAL.transaction(async (tx) =>
fnSecretBulkUpdate({
folderId,
orgId: actorOrgId,
tx,
inputSecrets: inputSecrets.map((el) => {
const originalSecret = secretsToUpdateInDBGroupedByKey[el.secretKey][0];
@ -1394,6 +1418,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: el.skipMultilineEncoding,
key: el.newSecretName || el.secretKey,
tags: el.tagIds,
secretMetadata: el.secretMetadata,
...encryptedValue
}
};
@ -1401,7 +1426,8 @@ export const secretV2BridgeServiceFactory = ({
secretDAL,
secretVersionDAL,
secretTagDAL,
secretVersionTagDAL
secretVersionTagDAL,
resourceMetadataDAL
})
);
await snapshotService.performSnapshot(folderId);
@ -1410,6 +1436,7 @@ export const secretV2BridgeServiceFactory = ({
actorId,
secretPath,
projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug
});
@ -1432,14 +1459,14 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TDeleteManySecretDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
@ -1461,7 +1488,7 @@ export const secretV2BridgeServiceFactory = ({
value: [
{
operator: "eq",
field: "key",
field: `${TableName.SecretV2}.key` as "key",
value: el.secretKey
},
{
@ -1512,6 +1539,7 @@ export const secretV2BridgeServiceFactory = ({
actorId,
secretPath,
projectId,
orgId: actorOrgId,
environmentSlug: folder.environment.slug
});
@ -1543,13 +1571,14 @@ export const secretV2BridgeServiceFactory = ({
const folder = await folderDAL.findById(secret.folderId);
if (!folder) throw new NotFoundError({ message: `Folder with ID '${secret.folderId}' not found` });
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
folder.projectId,
projectId: folder.projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
@ -1575,14 +1604,14 @@ export const secretV2BridgeServiceFactory = ({
actorOrgId,
actorAuthMethod
}: TBackFillSecretReferencesDTO) => {
const { hasRole, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
if (!hasRole(ProjectMembershipRole.Admin))
throw new ForbiddenRequestError({ message: "Only admins are allowed to take this action" });
@ -1623,14 +1652,14 @@ export const secretV2BridgeServiceFactory = ({
actorAuthMethod,
actorOrgId
}: TMoveSecretsDTO) => {
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbidOnInvalidProjectType(ProjectType.SecretManager);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
const sourceFolder = await folderDAL.findBySecretPath(projectId, sourceEnvironment, sourceSecretPath);
if (!sourceFolder) {
@ -1815,10 +1844,12 @@ export const secretV2BridgeServiceFactory = ({
if (locallyCreatedSecrets.length) {
await fnSecretBulkInsert({
folderId: destinationFolder.id,
orgId: actorOrgId,
secretVersionDAL,
secretDAL,
tx,
secretTagDAL,
resourceMetadataDAL,
secretVersionTagDAL,
inputSecrets: locallyCreatedSecrets.map((doc) => {
return {
@ -1830,6 +1861,7 @@ export const secretV2BridgeServiceFactory = ({
skipMultilineEncoding: doc.skipMultilineEncoding,
reminderNote: doc.reminderNote,
reminderRepeatDays: doc.reminderRepeatDays,
secretMetadata: doc.secretMetadata,
references: doc.value ? getAllSecretReferences(doc.value).nestedReferences : []
};
})
@ -1838,6 +1870,8 @@ export const secretV2BridgeServiceFactory = ({
if (locallyUpdatedSecrets.length) {
await fnSecretBulkUpdate({
folderId: destinationFolder.id,
orgId: actorOrgId,
resourceMetadataDAL,
secretVersionDAL,
secretDAL,
tx,
@ -1855,6 +1889,7 @@ export const secretV2BridgeServiceFactory = ({
encryptedComment: doc.encryptedComment,
skipMultilineEncoding: doc.skipMultilineEncoding,
reminderNote: doc.reminderNote,
secretMetadata: doc.secretMetadata,
reminderRepeatDays: doc.reminderRepeatDays,
...(doc.encryptedValue
? {
@ -1938,6 +1973,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(destinationFolder.id);
await secretQueueService.syncSecrets({
projectId,
orgId: actorOrgId,
secretPath: destinationFolder.path,
environmentSlug: destinationFolder.environment.slug,
actorId,
@ -1949,6 +1985,7 @@ export const secretV2BridgeServiceFactory = ({
await snapshotService.performSnapshot(sourceFolder.id);
await secretQueueService.syncSecrets({
projectId,
orgId: actorOrgId,
secretPath: sourceFolder.path,
environmentSlug: sourceFolder.environment.slug,
actorId,
@ -1973,13 +2010,14 @@ export const secretV2BridgeServiceFactory = ({
secretName,
actorAuthMethod
}: TGetSecretReferencesTreeDTO) => {
const { permission } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,

View File

@ -7,6 +7,8 @@ import { SecretsOrderBy } from "@app/services/secret/secret-types";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
import { TResourceMetadataDALFactory } from "../resource-metadata/resource-metadata-dal";
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
import { TSecretVersionV2DALFactory } from "./secret-version-dal";
import { TSecretVersionV2TagDALFactory } from "./secret-version-tag-dal";
@ -58,6 +60,7 @@ export type TCreateSecretDTO = TProjectPermission & {
skipMultilineEncoding?: boolean;
secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
};
export type TUpdateSecretDTO = TProjectPermission & {
@ -75,6 +78,7 @@ export type TUpdateSecretDTO = TProjectPermission & {
metadata?: {
source?: string;
};
secretMetadata?: ResourceMetadataDTO;
};
export type TDeleteSecretDTO = TProjectPermission & {
@ -94,6 +98,7 @@ export type TCreateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
secretComment?: string;
skipMultilineEncoding?: boolean;
tagIds?: string[];
secretMetadata?: ResourceMetadataDTO;
metadata?: {
source?: string;
};
@ -113,6 +118,7 @@ export type TUpdateManySecretDTO = Omit<TProjectPermission, "projectId"> & {
tagIds?: string[];
secretReminderRepeatDays?: number | null;
secretReminderNote?: string | null;
secretMetadata?: ResourceMetadataDTO;
}[];
};
@ -136,8 +142,16 @@ export type TSecretReference = { environment: string; secretPath: string; secret
export type TFnSecretBulkInsert = {
folderId: string;
orgId: string;
tx?: Knex;
inputSecrets: Array<Omit<TSecretsV2Insert, "folderId"> & { tagIds?: string[]; references: TSecretReference[] }>;
inputSecrets: Array<
Omit<TSecretsV2Insert, "folderId"> & {
tagIds?: string[];
references: TSecretReference[];
secretMetadata?: ResourceMetadataDTO;
}
>;
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2">;
@ -156,10 +170,12 @@ type TRequireReferenceIfValue =
export type TFnSecretBulkUpdate = {
folderId: string;
orgId: string;
inputSecrets: {
filter: Partial<TSecretsV2>;
data: TRequireReferenceIfValue & { tags?: string[] };
data: TRequireReferenceIfValue & { tags?: string[]; secretMetadata?: ResourceMetadataDTO };
}[];
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
secretDAL: Pick<TSecretV2BridgeDALFactory, "bulkUpdate" | "upsertSecretReferences">;
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany">;
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "deleteTagsToSecretV2">;

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