Compare commits

...

232 Commits

Author SHA1 Message Date
f5238598aa misc: updated admin integration picture 2025-06-23 14:12:54 +00:00
982aa80092 misc: added tabs for admin integrations 2025-06-23 22:05:08 +08:00
f85efdc6f8 misc: add auto-sync after config update 2025-06-21 02:57:34 +08:00
8680c52412 Merge branch 'misc/add-self-serve-for-github-app-connection-setup' of https://github.com/Infisical/infisical into misc/add-self-serve-for-github-app-connection-setup 2025-06-21 02:41:39 +08:00
0ad3c67f82 misc: minor renames 2025-06-21 02:41:15 +08:00
f75fff0565 doc: add image 2025-06-20 18:31:36 +00:00
1fa1d0a15a misc: add self-serve for github connection setup 2025-06-21 02:23:20 +08:00
8d6712aa58 Merge pull request #3824 from Infisical/doc/add-helm-install-for-pki-issuer
doc: add mention of helm install for pki issuer
2025-06-20 19:20:19 +08:00
a767870ad6 Merge pull request #3813 from akhilmhdh/patch/min-knex
feat: added min 0 for knexjs pool
2025-06-19 21:16:08 -04:00
a0c432628a Merge pull request #3831 from Infisical/docs/fix-broken-link
Docs links fix
2025-06-19 21:15:22 -04:00
08a74a63b5 Docs links fix 2025-06-19 21:10:58 -04:00
8329240822 Merge pull request #3821 from Infisical/ENG-2832
feat(dynamic-secret): Github App Tokens
2025-06-19 21:03:46 -04:00
ec3cbb9460 Merge pull request #3830 from Infisical/revert-cli-refresh
Revert CLI refresh PR
2025-06-19 20:58:11 -04:00
f167ba0fb8 Revert "Merge pull request #3797 from Infisical/ENG-2690"
This reverts commit 7d90d183fb, reversing
changes made to f385386a4b.
2025-06-19 20:46:55 -04:00
f291aa1c01 Merge pull request #3829 from Infisical/fix/cli-jwt-issue
Revert back to `RefreshToken` from `refreshToken` to support older CLI versions
2025-06-19 19:41:31 -04:00
72131373ec Merge branch 'main' into fix/cli-jwt-issue 2025-06-19 19:19:12 -04:00
16c48de031 refreshToken -> RefreshToken 2025-06-19 19:18:02 -04:00
436a5afab5 Merge pull request #3828 from Infisical/fix/cli-jwt-issue 2025-06-19 19:01:17 -04:00
9445f717f4 Revert back to JTWToken 2025-06-19 18:55:41 -04:00
251e83a3fb Merge pull request #3827 from Infisical/fix/cli-jwt-issue
Fix CLI issue
2025-06-19 17:33:37 -04:00
66df285245 Improvements 2025-06-19 17:26:58 -04:00
73fe2659b5 Fix CLI issue 2025-06-19 17:17:10 -04:00
091f02d1cd Merge pull request #3826 from akhilmhdh/feat/aws-auth-increase-limit
feat: patched up approval sequence ui bugs
2025-06-19 14:15:54 -07:00
66140dc151 Merge pull request #3809 from Infisical/feat/dynamicSecretAwsIamCustomTags
feat(dynamic-secret): Add custom tags to AWS IAM dynamic secret
2025-06-19 16:42:53 -03:00
a8c54d27ef remove debug console logs 2025-06-19 16:19:02 -03:00
9ac4453523 Review fixes 2025-06-19 15:12:41 -04:00
=
a6a9c2404d feat: patched up approval sequence ui bugs 2025-06-20 00:12:49 +05:30
e5352e7aa8 Merge pull request #3806 from Infisical/feat/addHerokuSecretSync
feat(secret-sync): Add Heroku Secret Sync
2025-06-19 15:28:56 -03:00
c52180c890 feat(secret-sync): minor fix on heroku docs 2025-06-19 15:17:36 -03:00
20f0eeed35 Moved tags to aws iam provider inputs 2025-06-19 15:01:35 -03:00
7581300a67 feat(secret-sync): minor fix on heroku sync 2025-06-19 13:38:20 -03:00
7d90d183fb Merge pull request #3797 from Infisical/ENG-2690
feat: Lower token lifetime to 1 day (refresh 14 days) and fix CLI refresh token functionality
2025-06-19 12:05:24 -04:00
f27d4ee973 doc: add mention of helm install for pki issuer 2025-06-19 22:41:39 +08:00
7473e3e21e Add Heroku PR suggestions 2025-06-19 09:28:43 -03:00
6720217cee Merge remote-tracking branch 'origin/main' into feat/addHerokuSecretSync 2025-06-19 08:47:03 -03:00
f385386a4b Merge pull request #3823 from akhilmhdh/feat/aws-auth-increase-limit
feat: resolved okta oidc failing
2025-06-19 07:06:21 -04:00
=
62a0d6e614 feat: corrected the error message 2025-06-19 16:10:15 +05:30
=
8c64c731f9 feat: added additional validation for name 2025-06-19 16:09:22 +05:30
=
d51f6ca4fd feat: resolved okta oidc failing 2025-06-19 16:04:55 +05:30
5abcbe36ca Update oncall-summery-template.mdx 2025-06-18 18:51:48 -04:00
7a13c27055 Greptile review comments and lint 2025-06-18 18:41:58 -04:00
e7ac783b10 feat(dynamic-secret): Github App Tokens 2025-06-18 18:33:11 -04:00
0a509e5033 Merge pull request #3791 from Infisical/feat/add-render-app-connection-and-secret-sync
feat: render app connection and secret sync
2025-06-19 04:49:01 +08:00
d0c01755fe misc: addressed type issue 2025-06-19 03:29:42 +08:00
41e65775ab misc: addressed comments 2025-06-19 03:24:32 +08:00
e3f4a2e604 Merge pull request #3819 from akhilmhdh/feat/aws-auth-increase-limit
fix: resolved failing duplication of predefined roles
2025-06-19 00:49:18 +05:30
f6e6bdb691 Merge remote-tracking branch 'origin/main' into feat/add-render-app-connection-and-secret-sync 2025-06-19 03:14:23 +08:00
=
819a021e9c feat: corrected enum usage 2025-06-19 00:05:40 +05:30
=
80113c2cea fix: resolved failing duplication of predefined roles 2025-06-19 00:02:17 +05:30
1f1fb3f3d1 Merge pull request #3817 from akhilmhdh/feat/aws-auth-increase-limit
fix: updated aws principal arn field size to 2048
2025-06-18 23:21:59 +05:30
=
d35331b0a8 fix: updated aws principal arn field size to 2048 2025-06-18 23:00:52 +05:30
ff6d94cbd0 Merge pull request #3815 from Infisical/daniel/update-dotnet-docs
docs: update .net sdk
2025-06-18 18:55:09 +04:00
=
01ef498397 feat: added min 0 for knexjs pool 2025-06-18 15:16:07 +05:30
59ac14380a Merge pull request #3810 from Infisical/daniel/secret-syncs-permissions
feat(secret-syncs): better permissioning
2025-06-17 21:44:47 -04:00
7b5c86f4ef revert previous commit 2025-06-17 17:34:00 -07:00
a745be2546 improvements: remove secret permission checks from secret syncs 2025-06-17 17:28:21 -07:00
02f311515c feat(secret-sync): Add PR suggestions for Heroku Integration 2025-06-17 21:19:21 -03:00
e8cb3f8b4a improvements: fix secret sync policy parsing, add read checks/filters and disable ui based of conditions 2025-06-17 16:18:41 -07:00
4c8063c532 docs: update .net sdk 2025-06-18 01:51:33 +04:00
6a9b2d3d48 Merge pull request #3804 from Infisical/service-tokens-table-improvements
improvement(service-tokens): Improve Service Tokens Table
2025-06-17 14:15:07 -07:00
0a39e138a1 fix: move service token form to separate component to prevent reset issue 2025-06-17 14:10:48 -07:00
0dce2045ec Merge pull request #3802 from Infisical/ENG-2929
feat(secret-sync, app-connection): Fly.io Secret Sync + App Connection
2025-06-17 16:57:58 -04:00
b4c118d246 requested changes 2025-06-18 00:26:26 +04:00
90e675de1e docs(secret-syncs): add conditions support 2025-06-18 00:22:25 +04:00
741e0ec78f Fixed credential validation 2025-06-17 16:18:35 -04:00
3f654e115d feat(secret-syncs): better permissioning 2025-06-18 00:17:39 +04:00
1921346b4f Review fixes 2025-06-17 16:05:09 -04:00
76c95ace63 Merge branch 'main' into ENG-2929 2025-06-17 15:57:31 -04:00
f4ae40cb86 Merge pull request #3805 from Infisical/access-control-tab-consistency
improvement(project-access-control): minor UI adjustments for consistency
2025-06-17 12:47:11 -07:00
b790dbb36f feat(dynamic-secret): Add tags to AWS IAM docs and add aws key-value limits to the schema 2025-06-17 16:21:29 -03:00
14449b8b41 improvements: address feedback 2025-06-17 12:17:53 -07:00
489bd124d2 feat(dynamic-secret): Add custom tags to AWS IAM dynamic secret 2025-06-17 16:06:35 -03:00
bcdcaa33a4 Merge pull request #3807 from Infisical/conditional-dynamic-secret-access-display
improvement(access-tree): dynamic secret conditional display
2025-06-17 11:49:45 -07:00
e8a8542757 Merge pull request #3803 from Infisical/project-roles-table-improvements
improvement(project-roles): Add pagination, search and column sorting to Project Roles table
2025-06-17 11:49:31 -07:00
e61d35d824 Merge pull request #3808 from Infisical/daniel/fix-editing-value-hidden-secrets
fix: editing secrets with value hidden
2025-06-17 22:08:50 +04:00
714d6831bd Update SecretListView.tsx 2025-06-17 21:23:30 +04:00
956f75eb43 fix: editing secrets with value hidden 2025-06-17 21:02:47 +04:00
73902c3ad6 improvement: hide secret path/enviornment in conditional tooltip 2025-06-17 09:45:56 -07:00
da792d144d improvements: address feedback 2025-06-17 09:29:17 -07:00
f7b09f5fc2 improvement: add conditional display to access tree for dynamic secret metadata 2025-06-17 08:16:39 -07:00
bfee34f38d Merge pull request #3801 from Infisical/doc/production-hardening
doc: production hardening
2025-06-17 22:10:22 +08:00
840b64a049 fix mint.json openapi url used for local test 2025-06-17 10:54:52 -03:00
c2612f242c feat(secret-sync): Add Heroku Secret Sync 2025-06-17 10:52:55 -03:00
092b89c59e misc: corrected kms section 2025-06-17 20:28:28 +08:00
3d76ae3399 misc: some more updates in examples 2025-06-17 20:25:38 +08:00
23aa97feff misc: addressed comments 2025-06-17 20:17:17 +08:00
0c5155f8e6 improvement: minor UI adjustments to make project access control tabs more uniform 2025-06-16 17:17:55 -07:00
796d6bfc85 improvement: add scope handling to service token search 2025-06-16 16:42:11 -07:00
4afe2f2377 improvements: use stored preferred page size for project roles table and add reset helper 2025-06-16 16:36:03 -07:00
6eaa16bd07 improvement: add pagination, search and column sort to service token table and improve table rows 2025-06-16 16:13:09 -07:00
1e07c2fe23 improvements: add sorting, search, and pagination to project roles table and improve dropdown menu 2025-06-16 15:00:40 -07:00
149f98a1b7 Update docs/integrations/secret-syncs/flyio.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-06-16 16:55:34 -04:00
14745b560c Update docs/integrations/app-connections/flyio.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-06-16 16:55:22 -04:00
dcfa0a2386 Update docs/integrations/secret-syncs/flyio.mdx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-06-16 16:55:07 -04:00
199339ac32 Minor schema improvements 2025-06-16 16:28:09 -04:00
2aeb02b74a Fly.io secret sync & app connection docs 2025-06-16 16:26:54 -04:00
fe75627ab7 Fly.io secret sync 2025-06-16 15:49:42 -04:00
191486519f Merge branch 'doc/production-hardening' of https://github.com/Infisical/infisical into doc/production-hardening 2025-06-17 03:45:54 +08:00
cab8fb0d8e misc: reorganized 2025-06-17 03:45:35 +08:00
8bfd728ce4 misc: added mint json 2025-06-16 19:22:35 +00:00
c9eab0af18 misc: updated section on db 2025-06-17 03:21:26 +08:00
d7dfc531fc doc: added guide for production hardening 2025-06-17 03:20:11 +08:00
a89bd08c08 Merge pull request #3795 from Infisical/ENG-2928
feat(machine-identities): Delete protection
2025-06-16 14:57:45 -04:00
4bfb9e8e74 Merge pull request #3789 from Infisical/misc/add-custom-role-slug-in-fetch-group
misc: add custom role slug in fetch group
2025-06-16 22:40:37 +04:00
da5f054a65 Fly.io app connection 2025-06-16 14:08:42 -04:00
9b13619efa Merge pull request #3799 from Infisical/daniel/hotfix-2
Fix: increase PIT tree checkout interval
2025-06-16 20:28:51 +04:00
c076a900dc Update env.ts 2025-06-16 20:27:02 +04:00
8a5279cf0d Merge pull request #3798 from Infisical/daniel/hotfix
fix: increase PIT checkpoint window
2025-06-16 20:09:29 +04:00
d45c29cd23 Update env.ts 2025-06-16 20:08:22 +04:00
77fe2ffb3b Add error handling 2025-06-14 01:43:32 -04:00
edf4e75e55 Spelling fix "JTW" -> "JWT" 2025-06-14 01:38:29 -04:00
de917a5d74 Fix CLI refresh token functionality + reduce token lifetime to 1d & 14d
for refresh
2025-06-14 01:31:44 -04:00
46f9927cf1 Merge pull request #3796 from Infisical/fix/applyWorkspaceLimitToSecretManager
Add a condition to only limit the number of projects to SecretManager
2025-06-13 17:46:35 -03:00
92508d19e6 Add a condition to only limit the number of projects to SecretManager 2025-06-13 17:39:15 -03:00
a73c0c05af Merge pull request #3794 from akhilmhdh/feat/ts-fix
feat: refactoring ts to avoid infer each time
2025-06-14 01:36:27 +05:30
c12bfa766c Review fixes 2025-06-13 14:51:39 -04:00
3432a16d4f Update frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityModal.tsx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-06-13 14:45:38 -04:00
19a403f467 feat(machine-identities): Delete protection 2025-06-13 14:37:15 -04:00
=
7a00ade119 feat: updated more types to avoid infer 2025-06-13 23:52:40 +05:30
35127db635 Merge pull request #3793 from Infisical/doc/add-scope-breakdown-section-in-design-doc
doc: add scope breakdown section to design doc
2025-06-13 13:21:45 -04:00
1b9eecc8f4 doc: add scope breakdown section 2025-06-13 17:11:02 +00:00
f0b8c1537c Merge pull request #3788 from Infisical/fix/vercelNotDisplayingAllBranches
fix(secret-sync): allow users to input custom branch names on Vercel
2025-06-13 13:49:00 -03:00
4e60cff4bd feat(secret-sync): Improve Vercel custom option label 2025-06-13 13:41:34 -03:00
ed1100bc90 misc: api references 2025-06-13 23:58:57 +08:00
dabe7e42ec misc: add deprecation for native render integration 2025-06-13 23:52:18 +08:00
c8ca6710ba misc: add secret sync docs 2025-06-13 15:48:59 +00:00
7adac40756 Merge pull request #3790 from Infisical/daniel/approval-ui-fix
fix: hidden secret value in secret approvals
2025-06-13 19:46:35 +04:00
400dc75656 Update SecretApprovalRequestChangeItem.tsx 2025-06-13 19:38:40 +04:00
4ecb2eb383 doc: added docs for render app connection 2025-06-13 15:24:45 +00:00
23a7c1b8cc Merge pull request #3792 from Infisical/fix/alibaba-cloud
Add left join
2025-06-13 11:08:00 -04:00
e51278c276 misc: added max length to apiKey 2025-06-13 23:03:04 +08:00
c014c12ecb misc: addressed frontend lint 2025-06-13 23:01:09 +08:00
097b04afee misc: addressed type 2025-06-13 22:59:08 +08:00
f304024235 add left join 2025-06-13 10:56:30 -04:00
63ccfc40ac feat: added render secret sync 2025-06-13 22:53:35 +08:00
=
5311daed64 feat: checkpoint before disaster strikes 2025-06-13 20:22:38 +05:30
d5e9ac82d0 fix: hidden secret value in secret approvals 2025-06-13 18:45:48 +04:00
b43ecef112 Merge pull request #3786 from Infisical/fix/auth0SamlMappingsTipWarning
fix(docs): replace tip to warning on Auth0 Auth
2025-06-13 10:37:03 -03:00
f9c012387c feat: added render app connection 2025-06-13 20:14:24 +08:00
=
5b51ab3216 feat: updated orm, keystore and queue 2025-06-13 17:40:51 +05:30
b26e56c97e Merge pull request #3777 from akhilmhdh/feat/seq-access-request
feat: Sequentail access approval request
2025-06-13 16:10:17 +05:30
=
7cced29c74 feat: resolved scim failure 2025-06-13 16:04:11 +05:30
06a7e804eb misc: add custom role slug in fetch group 2025-06-13 17:26:36 +08:00
0f00474243 Merge pull request #3735 from Infisical/misc/add-checks-for-helm-verification
misc: add verification pipelines for helm charts
2025-06-12 22:29:44 -04:00
3df010f266 Merge branch 'main' into misc/add-checks-for-helm-verification 2025-06-12 22:22:17 -04:00
333ce9d164 Merge pull request #3755 from Infisical/ENG-2773
feat(secret-rotation): Oracle Database
2025-06-12 21:06:57 -04:00
9621df4f8b Merge pull request #3736 from Infisical/feat/azureDevopsSecretSync
Feat/azure devops secret sync
2025-06-12 22:06:05 -03:00
3f2de2c5ef Rename API token mentions to access token 2025-06-12 20:36:34 -04:00
b2b1c13393 Lint 2025-06-12 20:24:09 -04:00
ee98992d9e fix(secret-sync): allow users to input custom branch names as Vercel only send a max of 100 with no pagination available 2025-06-12 20:56:15 -03:00
1fb0c638d6 Merge pull request #3787 from Infisical/ENG-2909
Update wording for service tokens
2025-06-12 19:32:54 -04:00
c1ad49a532 Update wording for service tokens 2025-06-12 19:28:41 -04:00
d1fcc739c9 Merge pull request #3552 from Infisical/ENG-2705
feat(dynamic-secrets): GCP IAM
2025-06-12 18:01:17 -04:00
8c0287681b fix(docs): replace tip to warning on Auth0 Auth 2025-06-12 18:15:44 -03:00
c7458d94aa Warning about tokens 2025-06-12 15:45:30 -04:00
93570df318 TForm update 2025-06-12 15:39:52 -04:00
e798b4a7ba Merge branch 'main' into ENG-2705 2025-06-12 15:18:00 -04:00
36c93f47d9 Review fixes 2025-06-12 15:17:22 -04:00
dbbcb157ef Merge branch 'main' into ENG-2773 2025-06-12 15:09:38 -04:00
=
d5f0b4dad9 feat: fullstop 2025-06-13 00:30:08 +05:30
bdc23d22e7 Merge pull request #3775 from Infisical/ENG-2861
feat(machine-identity): Alibaba Cloud
2025-06-12 13:57:14 -04:00
=
0fd1b1c9d7 feat: resolved type issue 2025-06-12 23:24:12 +05:30
=
79df946f02 feat: fixed migration issue 2025-06-12 23:00:12 +05:30
=
da2fa7f3ca feat: fixed sort by sequence 2025-06-12 21:42:35 +05:30
08c1740afc Merge pull request #3782 from Infisical/ENG-2900
improvement(secret-scanning): Multi-select actions
2025-06-12 11:56:28 -04:00
3cac4ef927 Reviews 2025-06-12 11:43:32 -04:00
2667f8f0f2 Merge pull request #3785 from Infisical/fix/auth0SamlMappingsTip
fix(docs): add a tip on Auth0 SAML doc tip
2025-06-12 12:03:10 -03:00
b39537472b fix(docs): fix indentation issue 2025-06-12 11:56:19 -03:00
6b60b2562d Merge pull request #3784 from Infisical/fix/pitBannerImprovements
feat(pit): improve banner messaging
2025-06-12 11:46:39 -03:00
c2a7827080 fix(docs): add a tip on Auth0 SAML doc to remind that the mappings could be adapted to the custom settings of the organization 2025-06-12 11:42:41 -03:00
64e09b0dcd feat(pit): improve banner messaging 2025-06-12 11:28:56 -03:00
a7176d44dd Merge pull request #3762 from Infisical/daniel/aws-auth-eks
docs(identities/aws-auth): eks pod auth
2025-06-12 18:11:59 +04:00
09d4cdc634 requested changes 2025-06-12 18:03:30 +04:00
=
547ef17c10 feat: corrected validation 2025-06-12 16:01:48 +05:30
=
841408042e feat: ui resolved sequence grouping error 2025-06-12 16:00:45 +05:30
=
e5fb1ac808 feat: updated ui based on review 2025-06-12 15:31:41 +05:30
8a93c0bd59 Cap array 2025-06-12 02:16:07 -04:00
c0f8f50981 lint 2025-06-12 02:04:01 -04:00
fec47ef81c Mass-update endpoint 2025-06-12 01:59:47 -04:00
348f4b9787 Greptile review fixes + pagination tweaks 2025-06-12 01:39:23 -04:00
aa577b095c improvement(secret-scanning): Multi-select actions 2025-06-12 01:25:53 -04:00
f515cc83d7 Fix lint issue 2025-06-11 20:18:58 -03:00
17bbdbe7bb feat(secret-sync): Add Azure Devops PR suggestions 2025-06-11 20:06:45 -03:00
427de068d5 Merge remote-tracking branch 'origin/main' into feat/azureDevopsSecretSync 2025-06-11 19:20:26 -03:00
dbf7ecc9b6 Merge pull request #3763 from Infisical/docs/add-packer-plugin-docs
feat(docs): Packer Plugin Docs
2025-06-11 17:44:35 -04:00
1ef9885062 Review fixes 2025-06-11 17:09:17 -04:00
de48c3e161 Merge pull request #3781 from Infisical/fix/inviteUsersWithIdentities
feat(invite-users): fix issue where invitations were not sent when the actor was an identity
2025-06-11 16:42:04 -03:00
852664e2cb feat(invite-users): fix issue where invitations were not sent when the actor was an identity 2025-06-11 16:11:34 -03:00
fbc8264732 Merge pull request #3779 from Infisical/misc/cli-dynamic-secret-and-agent-improvements
misc: added project slug flag support to dynamic secret commands
2025-06-12 02:08:17 +08:00
baa05714ab Merge pull request #3780 from Infisical/fix/azureClientSecretsManualDeletionCheck
feat(secret-rotation): Azure Client Secrets manually deleted client secrets check
2025-06-11 14:31:21 -03:00
c487614c38 feat(secret-rotation): fix Azure Client Secrets to check if the client secret has been manually deleted to avoid blocking the process 2025-06-11 13:28:34 -03:00
a55c8cacea Merge pull request #3778 from Infisical/fix/secretRequestReadIssue
feat(secret-request): hide secret value on missing secret read permission
2025-06-11 12:13:22 -03:00
c5c7adbc42 feat(secret-request): hide secret value on missing secret read permission 2025-06-11 11:43:14 -03:00
=
2d4476f99c feat: resolved ts error in rhf 2025-06-11 15:33:32 +05:30
=
81df491d5e feat: reptile feedback and resolved type failure 2025-06-11 15:20:46 +05:30
d2c5603664 Update frontend/src/pages/secret-manager/SecretApprovalsPage/components/AccessApprovalRequest/components/ReviewAccessModal.tsx
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-06-11 15:20:46 +05:30
=
096930cb8f feat: updated doc and fixed overflow in model for access policy 2025-06-11 15:20:46 +05:30
=
f9c00cf442 feat: ui changes for approval to work 2025-06-11 15:20:46 +05:30
=
d32b6ad41d feat: updated policy to have sequence order 2025-06-11 15:20:45 +05:30
53968e07d0 Lint + greptile review fixes 2025-06-11 02:59:04 -04:00
c315eed4d4 feat(machine-identity): Alibaba Cloud 2025-06-11 02:44:53 -04:00
2be56f6a70 Greptile review fix 2025-06-09 16:57:39 -04:00
1ff1f3fad3 feat(docs): Packer Plugin Docs 2025-06-09 16:55:41 -04:00
0ae96dfff4 Proper quote escaping 2025-06-09 13:26:47 -04:00
8ad6488bd9 Bug fix 2025-06-09 13:17:59 -04:00
e264b68b7e Merge branch 'check-non-re2-regex-workflow' into ENG-2773 2025-06-09 13:12:24 -04:00
9e881534ec Merge branch 'check-non-re2-regex-workflow' into ENG-2773 2025-06-09 12:31:45 -04:00
2832ff5c76 Merge RE2 regex workflow for performance testing 2025-06-09 12:11:49 -04:00
4c6cca0864 Greptile review fixes 2025-06-09 12:10:47 -04:00
c06bbf0b9b Merge branch 'main' into ENG-2773 2025-06-09 12:03:54 -04:00
69392a4a51 fix(identity/aws-auth): allow for lowercase authoriazation header 2025-06-09 19:45:05 +04:00
130f1a167e docs: add docs for eks pod auth 2025-06-09 19:44:36 +04:00
8ab710817d Fixes 2025-06-09 10:01:56 -04:00
ca39e75434 Merge 2025-06-09 09:20:13 -04:00
265b25a4c6 Update some username stuff 2025-06-07 01:44:58 -04:00
54f6e0b5c6 docs 2025-06-07 01:08:32 -04:00
f2cdefaeec Remove comment 2025-06-07 00:08:43 -04:00
2d588d87ac Tweaks 2025-06-07 00:08:32 -04:00
5ee2eb1aa2 feat(secret-rotation): Oracle DB 2025-06-07 00:07:34 -04:00
ff5f66a75f feat(secret-sync): Add Azure Devops PR suggestions 2025-06-06 10:27:13 -03:00
bf72638600 feat(secret-sync): Add Azure Devops PR suggestions 2025-06-06 10:08:31 -03:00
d9bc4da6f1 feat(secret-sync): Add Azure Devops docs 2025-06-05 15:17:35 -03:00
7f8d5ec11a feat(secret-sync): Add Azure Devops Secret Sync 2025-06-05 13:57:41 -03:00
141d0ede2d misc: add pr checks for gateway 2025-06-05 22:29:54 +08:00
ab78a79415 misc: add test workflow for gateway helm 2025-06-05 22:25:24 +08:00
8fa6af9ba4 misc: added checks for infisical standalone helm 2025-06-05 21:26:53 +08:00
f0a2845637 Merge remote-tracking branch 'origin/main' into misc/add-checks-for-helm-verification 2025-06-05 21:24:46 +08:00
8ffc88ba28 misc: add verification check for secret operator 2025-06-05 03:28:04 +08:00
05d132a1bb lint fix 2025-05-06 16:32:36 -04:00
bd7c4fc4eb review fixes 2025-05-06 16:26:51 -04:00
45c84d4936 Merge branch 'main' into ENG-2705 2025-05-06 15:28:16 -04:00
8e8e2e0dfe feat(dynamic-secrets): GCP IAM 2025-05-06 15:27:55 -04:00
660 changed files with 19495 additions and 2561 deletions

View File

@ -3,7 +3,62 @@ name: Release Infisical Core Helm chart
on: [workflow_dispatch]
jobs:
test-helm:
name: Test Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4.2.0
with:
version: v3.17.0
- uses: actions/setup-python@v5.3.0
with:
python-version: "3.x"
check-latest: true
- name: Add Helm repositories
run: |
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.7.0
- name: Run chart-testing (lint)
run: ct lint --config ct.yaml --charts helm-charts/infisical-standalone-postgres
- name: Create kind cluster
uses: helm/kind-action@v1.12.0
- name: Create namespace
run: kubectl create namespace infisical-standalone-postgres
- name: Create Infisical secrets
run: |
kubectl create secret generic infisical-secrets \
--namespace infisical-standalone-postgres \
--from-literal=AUTH_SECRET=6c1fe4e407b8911c104518103505b218 \
--from-literal=ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 \
--from-literal=SITE_URL=http://localhost:8080
- name: Run chart-testing (install)
run: |
ct install \
--config ct.yaml \
--charts helm-charts/infisical-standalone-postgres \
--helm-extra-args="--timeout=300s" \
--helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \
--namespace infisical-standalone-postgres
release:
needs: test-helm
runs-on: ubuntu-latest
steps:
- name: Checkout
@ -19,4 +74,4 @@ jobs:
- name: Build and push helm package to Cloudsmith
run: cd helm-charts && sh upload-infisical-core-helm-cloudsmith.sh
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}

View File

@ -1,27 +1,59 @@
name: Release K8 Operator Helm Chart
on:
workflow_dispatch:
workflow_dispatch:
jobs:
release-helm:
name: Release Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
test-helm:
name: Test Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: Set up Helm
uses: azure/setup-helm@v4.2.0
with:
version: v3.17.0
- name: Install python
uses: actions/setup-python@v4
- uses: actions/setup-python@v5.3.0
with:
python-version: "3.x"
check-latest: true
- name: Install Cloudsmith CLI
run: pip install --upgrade cloudsmith-cli
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.7.0
- name: Build and push helm package to CloudSmith
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
- name: Run chart-testing (lint)
run: ct lint --config ct.yaml --charts helm-charts/secrets-operator
- name: Create kind cluster
uses: helm/kind-action@v1.12.0
- name: Run chart-testing (install)
run: ct install --config ct.yaml --charts helm-charts/secrets-operator
release-helm:
name: Release Helm Chart
needs: test-helm
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: Install python
uses: actions/setup-python@v4
- name: Install Cloudsmith CLI
run: pip install --upgrade cloudsmith-cli
- name: Build and push helm package to CloudSmith
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}

View File

@ -1,27 +1,70 @@
name: Release Gateway Helm Chart
on:
workflow_dispatch:
workflow_dispatch:
jobs:
release-helm:
name: Release Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
test-helm:
name: Test Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: Set up Helm
uses: azure/setup-helm@v4.2.0
with:
version: v3.17.0
- name: Install python
uses: actions/setup-python@v4
- uses: actions/setup-python@v5.3.0
with:
python-version: "3.x"
check-latest: true
- name: Install Cloudsmith CLI
run: pip install --upgrade cloudsmith-cli
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.7.0
- name: Build and push helm package to CloudSmith
run: cd helm-charts && sh upload-gateway-cloudsmith.sh
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
- name: Run chart-testing (lint)
run: ct lint --config ct.yaml --charts helm-charts/infisical-gateway
- name: Create kind cluster
uses: helm/kind-action@v1.12.0
- name: Create namespace
run: kubectl create namespace infisical-gateway
- name: Create gateway secret
run: kubectl create secret generic infisical-gateway-environment --from-literal=TOKEN=my-test-token -n infisical-gateway
- name: Run chart-testing (install)
run: |
ct install \
--config ct.yaml \
--charts helm-charts/infisical-gateway \
--helm-extra-args="--timeout=300s" \
--namespace infisical-gateway
release-helm:
name: Release Helm Chart
needs: test-helm
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.10.0
- name: Install python
uses: actions/setup-python@v4
- name: Install Cloudsmith CLI
run: pip install --upgrade cloudsmith-cli
- name: Build and push helm package to CloudSmith
run: cd helm-charts && sh upload-gateway-cloudsmith.sh
env:
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}

View File

@ -0,0 +1,49 @@
name: Run Helm Chart Tests for Gateway
on:
pull_request:
paths:
- "helm-charts/infisical-gateway/**"
- ".github/workflows/run-helm-chart-tests-infisical-gateway.yml"
jobs:
test-helm:
name: Test Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4.2.0
with:
version: v3.17.0
- uses: actions/setup-python@v5.3.0
with:
python-version: "3.x"
check-latest: true
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.7.0
- name: Run chart-testing (lint)
run: ct lint --config ct.yaml --charts helm-charts/infisical-gateway
- name: Create kind cluster
uses: helm/kind-action@v1.12.0
- name: Create namespace
run: kubectl create namespace infisical-gateway
- name: Create gateway secret
run: kubectl create secret generic infisical-gateway-environment --from-literal=TOKEN=my-test-token -n infisical-gateway
- name: Run chart-testing (install)
run: |
ct install \
--config ct.yaml \
--charts helm-charts/infisical-gateway \
--helm-extra-args="--timeout=300s" \
--namespace infisical-gateway

View File

@ -0,0 +1,61 @@
name: Run Helm Chart Tests for Infisical Standalone Postgres
on:
pull_request:
paths:
- "helm-charts/infisical-standalone-postgres/**"
- ".github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml"
jobs:
test-helm:
name: Test Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4.2.0
with:
version: v3.17.0
- uses: actions/setup-python@v5.3.0
with:
python-version: "3.x"
check-latest: true
- name: Add Helm repositories
run: |
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.7.0
- name: Run chart-testing (lint)
run: ct lint --config ct.yaml --charts helm-charts/infisical-standalone-postgres
- name: Create kind cluster
uses: helm/kind-action@v1.12.0
- name: Create namespace
run: kubectl create namespace infisical-standalone-postgres
- name: Create Infisical secrets
run: |
kubectl create secret generic infisical-secrets \
--namespace infisical-standalone-postgres \
--from-literal=AUTH_SECRET=6c1fe4e407b8911c104518103505b218 \
--from-literal=ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 \
--from-literal=SITE_URL=http://localhost:8080
- name: Run chart-testing (install)
run: |
ct install \
--config ct.yaml \
--charts helm-charts/infisical-standalone-postgres \
--helm-extra-args="--timeout=300s" \
--helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \
--namespace infisical-standalone-postgres

View File

@ -0,0 +1,38 @@
name: Run Helm Chart Tests for Secret Operator
on:
pull_request:
paths:
- "helm-charts/secrets-operator/**"
- ".github/workflows/run-helm-chart-tests-secret-operator.yml"
jobs:
test-helm:
name: Test Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Helm
uses: azure/setup-helm@v4.2.0
with:
version: v3.17.0
- uses: actions/setup-python@v5.3.0
with:
python-version: "3.x"
check-latest: true
- name: Set up chart-testing
uses: helm/chart-testing-action@v2.7.0
- name: Run chart-testing (lint)
run: ct lint --config ct.yaml --charts helm-charts/secrets-operator
- name: Create kind cluster
uses: helm/kind-action@v1.12.0
- name: Run chart-testing (install)
run: ct install --config ct.yaml --charts helm-charts/secrets-operator

View File

@ -40,4 +40,8 @@ cli/detect/config/gitleaks.toml:gcp-api-key:578
cli/detect/config/gitleaks.toml:gcp-api-key:579
cli/detect/config/gitleaks.toml:gcp-api-key:581
cli/detect/config/gitleaks.toml:gcp-api-key:582
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml:generic-api-key:51
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml:generic-api-key:50
.github/workflows/helm-release-infisical-core.yml:generic-api-key:48
.github/workflows/helm-release-infisical-core.yml:generic-api-key:47
backend/src/services/smtp/smtp-service.ts:generic-api-key:79

View File

@ -3,13 +3,12 @@ import "fastify";
import { Redis } from "ioredis";
import { TUsers } from "@app/db/schemas";
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service";
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-types";
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-types";
import { TAuditLogServiceFactory, TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-types";
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-types";
import { TCertificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service";
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
@ -25,14 +24,13 @@ import { TKmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { TPitServiceFactory } from "@app/ee/services/pit/pit-service";
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
import { RateLimitConfiguration } from "@app/ee/services/rate-limit/rate-limit-types";
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-types";
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
import { RateLimitConfiguration, TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-types";
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-types";
import { TScimServiceFactory } from "@app/ee/services/scim/scim-types";
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
@ -44,7 +42,7 @@ import { TSshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh
import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types";
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
@ -65,6 +63,7 @@ import { TGroupProjectServiceFactory } from "@app/services/group-project/group-p
import { THsmServiceFactory } from "@app/services/hsm/hsm-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
@ -218,6 +217,7 @@ declare module "fastify" {
identityUa: TIdentityUaServiceFactory;
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
identityGcpAuth: TIdentityGcpAuthServiceFactory;
identityAliCloudAuth: TIdentityAliCloudAuthServiceFactory;
identityAwsAuth: TIdentityAwsAuthServiceFactory;
identityAzureAuth: TIdentityAzureAuthServiceFactory;
identityOciAuth: TIdentityOciAuthServiceFactory;

View File

@ -125,6 +125,9 @@ import {
TIdentityAccessTokens,
TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate,
TIdentityAlicloudAuths,
TIdentityAlicloudAuthsInsert,
TIdentityAlicloudAuthsUpdate,
TIdentityAwsAuths,
TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate,
@ -786,6 +789,11 @@ declare module "knex/types/tables" {
TIdentityGcpAuthsInsert,
TIdentityGcpAuthsUpdate
>;
[TableName.IdentityAliCloudAuth]: KnexOriginal.CompositeTableType<
TIdentityAlicloudAuths,
TIdentityAlicloudAuthsInsert,
TIdentityAlicloudAuthsUpdate
>;
[TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType<
TIdentityAwsAuths,
TIdentityAwsAuthsInsert,

View File

@ -1,6 +1,6 @@
import knex, { Knex } from "knex";
export type TDbClient = ReturnType<typeof initDbConnection>;
export type TDbClient = Knex;
export const initDbConnection = ({
dbConnectionUri,
dbRootCert,
@ -50,6 +50,8 @@ export const initDbConnection = ({
}
: false
},
// https://knexjs.org/guide/#pool
pool: { min: 0, max: 10 },
migrations: {
tableName: "infisical_migrations"
}
@ -70,7 +72,8 @@ export const initDbConnection = ({
},
migrations: {
tableName: "infisical_migrations"
}
},
pool: { min: 0, max: 10 }
});
});

View File

@ -0,0 +1,44 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasStepColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "sequence");
const hasApprovalRequiredColumn = await knex.schema.hasColumn(
TableName.AccessApprovalPolicyApprover,
"approvalsRequired"
);
if (!hasStepColumn || !hasApprovalRequiredColumn) {
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (t) => {
if (!hasStepColumn) t.integer("sequence").defaultTo(1);
if (!hasApprovalRequiredColumn) t.integer("approvalsRequired").nullable();
});
}
// set rejected status for all access request that was rejected and still has status pending
const subquery = knex(TableName.AccessApprovalRequest)
.leftJoin(
TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequestReviewer}.requestId`,
`${TableName.AccessApprovalRequest}.id`
)
.where(`${TableName.AccessApprovalRequest}.status` as "status", "pending")
.where(`${TableName.AccessApprovalRequestReviewer}.status` as "status", "rejected")
.select(`${TableName.AccessApprovalRequest}.id`);
await knex(TableName.AccessApprovalRequest).where("id", "in", subquery).update("status", "rejected");
}
export async function down(knex: Knex): Promise<void> {
const hasStepColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "sequence");
const hasApprovalRequiredColumn = await knex.schema.hasColumn(
TableName.AccessApprovalPolicyApprover,
"approvalsRequired"
);
if (hasStepColumn || hasApprovalRequiredColumn) {
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (t) => {
if (hasStepColumn) t.dropColumn("sequence");
if (hasApprovalRequiredColumn) t.dropColumn("approvalsRequired");
});
}
}

View File

@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityAliCloudAuth))) {
await knex.schema.createTable(TableName.IdentityAliCloudAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.timestamps(true, true, true);
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.string("type").notNullable();
t.string("allowedArns").notNullable();
});
}
await createOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAliCloudAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
}

View File

@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.Identity, "hasDeleteProtection");
if (!hasCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.boolean("hasDeleteProtection").notNullable().defaultTo(false);
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasCol = await knex.schema.hasColumn(TableName.Identity, "hasDeleteProtection");
if (hasCol) {
await knex.schema.alterTable(TableName.Identity, (t) => {
t.dropColumn("hasDeleteProtection");
});
}
}

View File

@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
if (hasColumn) {
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
t.string("allowedPrincipalArns", 2048).notNullable().alter();
});
}
}
export async function down(knex: Knex): Promise<void> {
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
if (hasColumn) {
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
t.string("allowedPrincipalArns", 255).notNullable().alter();
});
}
}

View File

@ -0,0 +1,91 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasEncryptedGithubAppConnectionClientIdColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionClientId"
);
const hasEncryptedGithubAppConnectionClientSecretColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionClientSecret"
);
const hasEncryptedGithubAppConnectionSlugColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionSlug"
);
const hasEncryptedGithubAppConnectionAppIdColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionId"
);
const hasEncryptedGithubAppConnectionAppPrivateKeyColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionPrivateKey"
);
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
if (!hasEncryptedGithubAppConnectionClientIdColumn) {
t.binary("encryptedGitHubAppConnectionClientId").nullable();
}
if (!hasEncryptedGithubAppConnectionClientSecretColumn) {
t.binary("encryptedGitHubAppConnectionClientSecret").nullable();
}
if (!hasEncryptedGithubAppConnectionSlugColumn) {
t.binary("encryptedGitHubAppConnectionSlug").nullable();
}
if (!hasEncryptedGithubAppConnectionAppIdColumn) {
t.binary("encryptedGitHubAppConnectionId").nullable();
}
if (!hasEncryptedGithubAppConnectionAppPrivateKeyColumn) {
t.binary("encryptedGitHubAppConnectionPrivateKey").nullable();
}
});
}
export async function down(knex: Knex): Promise<void> {
const hasEncryptedGithubAppConnectionClientIdColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionClientId"
);
const hasEncryptedGithubAppConnectionClientSecretColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionClientSecret"
);
const hasEncryptedGithubAppConnectionSlugColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionSlug"
);
const hasEncryptedGithubAppConnectionAppIdColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionId"
);
const hasEncryptedGithubAppConnectionAppPrivateKeyColumn = await knex.schema.hasColumn(
TableName.SuperAdmin,
"encryptedGitHubAppConnectionPrivateKey"
);
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
if (hasEncryptedGithubAppConnectionClientIdColumn) {
t.dropColumn("encryptedGitHubAppConnectionClientId");
}
if (hasEncryptedGithubAppConnectionClientSecretColumn) {
t.dropColumn("encryptedGitHubAppConnectionClientSecret");
}
if (hasEncryptedGithubAppConnectionSlugColumn) {
t.dropColumn("encryptedGitHubAppConnectionSlug");
}
if (hasEncryptedGithubAppConnectionAppIdColumn) {
t.dropColumn("encryptedGitHubAppConnectionId");
}
if (hasEncryptedGithubAppConnectionAppPrivateKeyColumn) {
t.dropColumn("encryptedGitHubAppConnectionPrivateKey");
}
});
}

View File

@ -13,7 +13,9 @@ export const AccessApprovalPoliciesApproversSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
approverUserId: z.string().uuid().nullable().optional(),
approverGroupId: z.string().uuid().nullable().optional()
approverGroupId: z.string().uuid().nullable().optional(),
sequence: z.number().default(0).nullable().optional(),
approvalsRequired: z.number().default(1).nullable().optional()
});
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;

View File

@ -12,7 +12,8 @@ export const IdentitiesSchema = z.object({
name: z.string(),
authMethod: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
hasDeleteProtection: z.boolean().default(false)
});
export type TIdentities = z.infer<typeof IdentitiesSchema>;

View File

@ -0,0 +1,25 @@
// 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 IdentityAlicloudAuthsSchema = z.object({
id: z.string().uuid(),
accessTokenTTL: z.coerce.number().default(7200),
accessTokenMaxTTL: z.coerce.number().default(7200),
accessTokenNumUsesLimit: z.coerce.number().default(0),
accessTokenTrustedIps: z.unknown(),
createdAt: z.date(),
updatedAt: z.date(),
identityId: z.string().uuid(),
type: z.string(),
allowedArns: z.string()
});
export type TIdentityAlicloudAuths = z.infer<typeof IdentityAlicloudAuthsSchema>;
export type TIdentityAlicloudAuthsInsert = Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>;
export type TIdentityAlicloudAuthsUpdate = Partial<Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>>;

View File

@ -39,6 +39,7 @@ export * from "./group-project-memberships";
export * from "./groups";
export * from "./identities";
export * from "./identity-access-tokens";
export * from "./identity-alicloud-auths";
export * from "./identity-aws-auths";
export * from "./identity-azure-auths";
export * from "./identity-gcp-auths";

View File

@ -80,6 +80,7 @@ export enum TableName {
IdentityGcpAuth = "identity_gcp_auths",
IdentityAzureAuth = "identity_azure_auths",
IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityAliCloudAuth = "identity_alicloud_auths",
IdentityAwsAuth = "identity_aws_auths",
IdentityOciAuth = "identity_oci_auths",
IdentityOidcAuth = "identity_oidc_auths",
@ -247,6 +248,7 @@ export enum IdentityAuthMethod {
UNIVERSAL_AUTH = "universal-auth",
KUBERNETES_AUTH = "kubernetes-auth",
GCP_AUTH = "gcp-auth",
ALICLOUD_AUTH = "alicloud-auth",
AWS_AUTH = "aws-auth",
AZURE_AUTH = "azure-auth",
OCI_AUTH = "oci-auth",

View File

@ -29,7 +29,12 @@ export const SuperAdminSchema = z.object({
adminIdentityIds: z.string().array().nullable().optional(),
encryptedMicrosoftTeamsAppId: zodBuffer.nullable().optional(),
encryptedMicrosoftTeamsClientSecret: zodBuffer.nullable().optional(),
encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional()
encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional(),
encryptedGitHubAppConnectionClientId: zodBuffer.nullable().optional(),
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional()
});
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;

View File

@ -23,12 +23,26 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
environment: z.string(),
approvers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
z.object({
type: z.literal(ApproverType.Group),
id: z.string(),
sequence: z.number().int().default(1)
}),
z.object({
type: z.literal(ApproverType.User),
id: z.string().optional(),
username: z.string().optional(),
sequence: z.number().int().default(1)
})
])
.array()
.max(100, "Cannot have more than 100 approvers")
.min(1, { message: "At least one approver should be provided" }),
.min(1, { message: "At least one approver should be provided" })
.refine(
// @ts-expect-error this is ok
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
"Must provide either username or id"
),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
@ -37,6 +51,13 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvalsRequired: z
.object({
numberOfApprovals: z.number().int(),
stepNumber: z.number().int()
})
.array()
.optional(),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
@ -78,7 +99,12 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
approvals: sapPubSchema
.extend({
approvers: z
.object({ type: z.nativeEnum(ApproverType), id: z.string().nullable().optional() })
.object({
type: z.nativeEnum(ApproverType),
id: z.string().nullable().optional(),
sequence: z.number().nullable().optional(),
approvalsRequired: z.number().nullable().optional()
})
.array()
.nullable()
.optional(),
@ -152,12 +178,26 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.transform((val) => (val === "" ? "/" : val)),
approvers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
z.object({
type: z.literal(ApproverType.Group),
id: z.string(),
sequence: z.number().int().default(1)
}),
z.object({
type: z.literal(ApproverType.User),
id: z.string().optional(),
username: z.string().optional(),
sequence: z.number().int().default(1)
})
])
.array()
.min(1, { message: "At least one approver should be provided" })
.max(100, "Cannot have more than 100 approvers"),
.max(100, "Cannot have more than 100 approvers")
.refine(
// @ts-expect-error this is ok
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
"Must provide either username or id"
),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
@ -168,7 +208,14 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.optional(),
approvals: z.number().min(1).optional(),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
allowedSelfApprovals: z.boolean().default(true),
approvalsRequired: z
.object({
numberOfApprovals: z.number().int(),
stepNumber: z.number().int()
})
.array()
.optional()
}),
response: {
200: z.object({
@ -235,7 +282,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
.object({
type: z.nativeEnum(ApproverType),
id: z.string().nullable().optional(),
name: z.string().nullable().optional()
name: z.string().nullable().optional(),
approvalsRequired: z.number().nullable().optional()
})
.array()
.nullable()

View File

@ -112,7 +112,15 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
id: z.string(),
name: z.string(),
approvals: z.number(),
approvers: z.string().array(),
approvers: z
.object({
userId: z.string().nullable().optional(),
sequence: z.number().nullable().optional(),
approvalsRequired: z.number().nullable().optional(),
email: z.string().nullable().optional(),
username: z.string().nullable().optional()
})
.array(),
bypassers: z.string().array(),
secretPath: z.string().nullish(),
envId: z.string(),

View File

@ -0,0 +1,17 @@
import {
CreateOracleDBConnectionSchema,
SanitizedOracleDBConnectionSchema,
UpdateOracleDBConnectionSchema
} from "@app/ee/services/app-connections/oracledb";
import { registerAppConnectionEndpoints } from "@app/server/routes/v1/app-connection-routers/app-connection-endpoints";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const registerOracleDBConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.OracleDB,
server,
sanitizedResponseSchema: SanitizedOracleDBConnectionSchema,
createSchema: CreateOracleDBConnectionSchema,
updateSchema: UpdateOracleDBConnectionSchema
});
};

View File

@ -48,7 +48,9 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
id: z.string().trim().describe(GROUPS.GET_BY_ID.id)
}),
response: {
200: GroupsSchema
200: GroupsSchema.extend({
customRoleSlug: z.string().nullable()
})
}
},
handler: async (req) => {

View File

@ -270,7 +270,6 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}),
body: z.object({
schemas: z.array(z.string()),
id: z.string().trim(),
userName: z.string().trim(),
name: z
.object({
@ -278,7 +277,6 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
givenName: z.string().trim().optional()
})
.optional(),
displayName: z.string().trim(),
emails: z
.array(
z.object({

View File

@ -285,6 +285,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
commits: secretRawSchema
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
.extend({
secretValueHidden: z.boolean(),
secretValue: z.string().optional(),
isRotatedSecret: z.boolean().optional(),
op: z.string(),
@ -296,6 +297,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
version: z.number(),
secretKey: z.string(),
secretValue: z.string().optional(),
secretValueHidden: z.boolean(),
secretComment: z.string().optional()
})
.optional()
@ -306,6 +308,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
version: z.number(),
secretKey: z.string(),
secretValue: z.string().optional(),
secretValueHidden: z.boolean(),
secretComment: z.string().optional(),
tags: SanitizedTagSchema.array().optional(),
secretMetadata: ResourceMetadataSchema.nullish()

View File

@ -6,6 +6,7 @@ import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-r
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
import { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router";
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
export * from "./secret-rotation-v2-router";
@ -17,6 +18,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
[SecretRotation.MySqlCredentials]: registerMySqlCredentialsRotationRouter,
[SecretRotation.OracleDBCredentials]: registerOracleDBCredentialsRotationRouter,
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,

View File

@ -0,0 +1,19 @@
import {
CreateOracleDBCredentialsRotationSchema,
OracleDBCredentialsRotationSchema,
UpdateOracleDBCredentialsRotationSchema
} from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
export const registerOracleDBCredentialsRotationRouter = async (server: FastifyZodProvider) =>
registerSecretRotationEndpoints({
type: SecretRotation.OracleDBCredentials,
server,
responseSchema: OracleDBCredentialsRotationSchema,
createSchema: CreateOracleDBCredentialsRotationSchema,
updateSchema: UpdateOracleDBCredentialsRotationSchema,
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
});

View File

@ -7,6 +7,7 @@ import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
import { OracleDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
@ -18,6 +19,7 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
PostgresCredentialsRotationListItemSchema,
MsSqlCredentialsRotationListItemSchema,
MySqlCredentialsRotationListItemSchema,
OracleDBCredentialsRotationListItemSchema,
Auth0ClientSecretRotationListItemSchema,
AzureClientSecretRotationListItemSchema,
AwsIamUserSecretRotationListItemSchema,

View File

@ -187,6 +187,56 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider)
}
});
server.route({
method: "PATCH",
url: "/findings",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.SecretScanning],
description: "Update one or more Secret Scanning Findings in a batch.",
body: z
.object({
findingId: z.string().trim().min(1, "Finding ID required").describe(SecretScanningFindings.UPDATE.findingId),
status: z.nativeEnum(SecretScanningFindingStatus).optional().describe(SecretScanningFindings.UPDATE.status),
remarks: z.string().nullish().describe(SecretScanningFindings.UPDATE.remarks)
})
.array()
.max(500),
response: {
200: z.object({ findings: SecretScanningFindingSchema.array() })
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { body, permission } = req;
const updatedFindingPromises = body.map(async (findingUpdatePayload) => {
const { finding, projectId } = await server.services.secretScanningV2.updateSecretScanningFindingById(
findingUpdatePayload,
permission
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.SECRET_SCANNING_FINDING_UPDATE,
metadata: findingUpdatePayload
}
});
return finding;
});
const findings = await Promise.all(updatedFindingPromises);
return { findings };
}
});
server.route({
method: "GET",
url: "/configs",

View File

@ -1,15 +1,15 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TAccessApprovalPolicyApproverDALFactory = ReturnType<typeof accessApprovalPolicyApproverDALFactory>;
export type TAccessApprovalPolicyApproverDALFactory = TOrmify<TableName.AccessApprovalPolicyApprover>;
export const accessApprovalPolicyApproverDALFactory = (db: TDbClient) => {
const accessApprovalPolicyApproverOrm = ormify(db, TableName.AccessApprovalPolicyApprover);
return { ...accessApprovalPolicyApproverOrm };
};
export type TAccessApprovalPolicyBypasserDALFactory = ReturnType<typeof accessApprovalPolicyBypasserDALFactory>;
export type TAccessApprovalPolicyBypasserDALFactory = TOrmify<TableName.AccessApprovalPolicyBypasser>;
export const accessApprovalPolicyBypasserDALFactory = (db: TDbClient) => {
const accessApprovalPolicyBypasserOrm = ormify(db, TableName.AccessApprovalPolicyBypasser);

View File

@ -3,13 +3,363 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies, TUsers } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter, TOrmify } from "@app/lib/knex";
import { ApproverType, BypasserType } from "./access-approval-policy-types";
import {
ApproverType,
BypasserType,
TCreateAccessApprovalPolicy,
TDeleteAccessApprovalPolicy,
TGetAccessApprovalPolicyByIdDTO,
TGetAccessPolicyCountByEnvironmentDTO,
TListAccessApprovalPoliciesDTO,
TUpdateAccessApprovalPolicy
} from "./access-approval-policy-types";
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
export interface TAccessApprovalPolicyDALFactory
extends Omit<TOrmify<TableName.AccessApprovalPolicy>, "findById" | "find"> {
find: (
filter: TFindFilter<
TAccessApprovalPolicies & {
projectId: string;
}
>,
customFilter?: {
policyId?: string;
},
tx?: Knex
) => Promise<
{
approvers: (
| {
id: string | null | undefined;
type: ApproverType.User;
name: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
| {
id: string | null | undefined;
type: ApproverType.Group;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
)[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
bypassers: (
| {
id: string | null | undefined;
type: BypasserType.User;
name: string;
}
| {
id: string | null | undefined;
type: BypasserType.Group;
}
)[];
}[]
>;
findById: (
policyId: string,
tx?: Knex
) => Promise<
| {
approvers: {
id: string | null | undefined;
type: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
}
| undefined
>;
softDeleteById: (
policyId: string,
tx?: Knex
) => Promise<{
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
}>;
findLastValidPolicy: (
{
envId,
secretPath
}: {
envId: string;
secretPath: string;
},
tx?: Knex
) => Promise<
| {
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
}
| undefined
>;
}
export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
export interface TAccessApprovalPolicyServiceFactory {
getAccessPolicyCountByEnvSlug: ({
actor,
actorOrgId,
actorAuthMethod,
projectSlug,
actorId,
envSlug
}: TGetAccessPolicyCountByEnvironmentDTO) => Promise<{
count: number;
}>;
createAccessApprovalPolicy: ({
name,
actor,
actorId,
actorOrgId,
secretPath,
actorAuthMethod,
approvals,
approvers,
bypassers,
projectSlug,
environment,
enforcementLevel,
allowedSelfApprovals,
approvalsRequired
}: TCreateAccessApprovalPolicy) => Promise<{
environment: {
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
projectId: string;
slug: string;
position: number;
};
projectId: string;
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
}>;
deleteAccessApprovalPolicy: ({
policyId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TDeleteAccessApprovalPolicy) => Promise<{
approvers: {
id: string | null | undefined;
type: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
}>;
updateAccessApprovalPolicy: ({
policyId,
approvers,
bypassers,
secretPath,
name,
actorId,
actor,
actorOrgId,
actorAuthMethod,
approvals,
enforcementLevel,
allowedSelfApprovals,
approvalsRequired
}: TUpdateAccessApprovalPolicy) => Promise<{
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
}>;
getAccessApprovalPolicyByProjectSlug: ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
projectSlug
}: TListAccessApprovalPoliciesDTO) => Promise<
{
approvers: (
| {
id: string | null | undefined;
type: ApproverType;
name: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
| {
id: string | null | undefined;
type: ApproverType;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
)[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
bypassers: (
| {
id: string | null | undefined;
type: BypasserType;
name: string;
}
| {
id: string | null | undefined;
type: BypasserType;
}
)[];
}[]
>;
getAccessApprovalPolicyById: ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
policyId
}: TGetAccessApprovalPolicyByIdDTO) => Promise<{
approvers: (
| {
id: string | null | undefined;
type: ApproverType.User;
name: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
| {
id: string | null | undefined;
type: ApproverType.Group;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
)[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
bypassers: (
| {
id: string | null | undefined;
type: BypasserType.User;
name: string;
}
| {
id: string | null | undefined;
type: BypasserType.Group;
}
)[];
}>;
}
export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPolicyDALFactory => {
const accessApprovalPolicyOrm = ormify(db, TableName.AccessApprovalPolicy);
const accessApprovalPolicyFindQuery = async (
@ -48,6 +398,8 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
.select(tx.ref("username").withSchema("bypasserUsers").as("bypasserUsername"))
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"))
.select(tx.ref("approvalsRequired").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
@ -59,7 +411,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
return result;
};
const findById = async (policyId: string, tx?: Knex) => {
const findById: TAccessApprovalPolicyDALFactory["findById"] = async (policyId, tx) => {
try {
const doc = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), {
[`${TableName.AccessApprovalPolicy}.id` as "id"]: policyId
@ -80,35 +432,37 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
{
key: "approverUserId",
label: "approvers" as const,
mapper: ({ approverUserId: id }) => ({
mapper: ({ approverUserId: id, approverSequence, approvalsRequired }) => ({
id,
type: "user"
type: "user",
sequence: approverSequence,
approvalsRequired
})
},
{
key: "approverGroupId",
label: "approvers" as const,
mapper: ({ approverGroupId: id }) => ({
mapper: ({ approverGroupId: id, approverSequence, approvalsRequired }) => ({
id,
type: "group"
type: "group",
sequence: approverSequence,
approvalsRequired
})
}
]
});
if (!formattedDoc?.[0]) return;
return formattedDoc?.[0];
return {
...formattedDoc?.[0],
approvers: formattedDoc?.[0]?.approvers.sort((a, b) => (a.sequence || 1) - (b.sequence || 1))
};
} catch (error) {
throw new DatabaseError({ error, name: "FindById" });
}
};
const find = async (
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
customFilter?: {
policyId?: string;
},
tx?: Knex
) => {
const find: TAccessApprovalPolicyDALFactory["find"] = async (filter, customFilter, tx) => {
try {
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter, customFilter);
@ -129,18 +483,22 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
{
key: "approverUserId",
label: "approvers" as const,
mapper: ({ approverUserId: id, approverUsername }) => ({
mapper: ({ approverUserId: id, approverUsername, approverSequence, approvalsRequired }) => ({
id,
type: ApproverType.User,
name: approverUsername
type: ApproverType.User as const,
name: approverUsername,
sequence: approverSequence,
approvalsRequired
})
},
{
key: "approverGroupId",
label: "approvers" as const,
mapper: ({ approverGroupId: id }) => ({
mapper: ({ approverGroupId: id, approverSequence, approvalsRequired }) => ({
id,
type: ApproverType.Group
type: ApproverType.Group as const,
sequence: approverSequence,
approvalsRequired
})
},
{
@ -148,7 +506,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
label: "bypassers" as const,
mapper: ({ bypasserUserId: id, bypasserUsername }) => ({
id,
type: BypasserType.User,
type: BypasserType.User as const,
name: bypasserUsername
})
},
@ -157,24 +515,30 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
label: "bypassers" as const,
mapper: ({ bypasserGroupId: id }) => ({
id,
type: BypasserType.Group
type: BypasserType.Group as const
})
}
]
});
return formattedDocs;
return formattedDocs.map((el) => ({
...el,
approvers: el?.approvers.sort((a, b) => (a.sequence || 1) - (b.sequence || 1))
}));
} catch (error) {
throw new DatabaseError({ error, name: "Find" });
}
};
const softDeleteById = async (policyId: string, tx?: Knex) => {
const softDeleteById: TAccessApprovalPolicyDALFactory["softDeleteById"] = async (policyId, tx) => {
const softDeletedPolicy = await accessApprovalPolicyOrm.updateById(policyId, { deletedAt: new Date() }, tx);
return softDeletedPolicy;
};
const findLastValidPolicy = async ({ envId, secretPath }: { envId: string; secretPath: string }, tx?: Knex) => {
const findLastValidPolicy: TAccessApprovalPolicyDALFactory["findLastValidPolicy"] = async (
{ envId, secretPath },
tx
) => {
try {
const result = await (tx || db.replicaNode())(TableName.AccessApprovalPolicy)
.where(

View File

@ -1,9 +1,10 @@
import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
@ -23,9 +24,8 @@ import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
import {
ApproverType,
BypasserType,
TCreateAccessApprovalPolicy,
TAccessApprovalPolicyServiceFactory,
TDeleteAccessApprovalPolicy,
TGetAccessApprovalPolicyByIdDTO,
TGetAccessPolicyCountByEnvironmentDTO,
TListAccessApprovalPoliciesDTO,
TUpdateAccessApprovalPolicy
@ -41,14 +41,12 @@ type TAccessApprovalPolicyServiceFactoryDep = {
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
groupDAL: TGroupDALFactory;
userDAL: Pick<TUserDALFactory, "find">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find" | "resetReviewByPolicyId">;
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update">;
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
};
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
export const accessApprovalPolicyServiceFactory = ({
accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL,
@ -62,8 +60,8 @@ export const accessApprovalPolicyServiceFactory = ({
additionalPrivilegeDAL,
accessApprovalRequestReviewerDAL,
orgMembershipDAL
}: TAccessApprovalPolicyServiceFactoryDep) => {
const createAccessApprovalPolicy = async ({
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({
name,
actor,
actorId,
@ -76,27 +74,23 @@ export const accessApprovalPolicyServiceFactory = ({
projectSlug,
environment,
enforcementLevel,
allowedSelfApprovals
}: TCreateAccessApprovalPolicy) => {
allowedSelfApprovals,
approvalsRequired
}) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// If there is a group approver people might be added to the group later to meet the approvers quota
const groupApprovers = approvers
.filter((approver) => approver.type === ApproverType.Group)
.map((approver) => approver.id) as string[];
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
const userApprovers = approvers
.filter((approver) => approver.type === ApproverType.User)
.map((approver) => approver.id)
.filter(Boolean) as string[];
const userApprovers = approvers.filter((approver) => approver.type === ApproverType.User && approver.id) as {
id: string;
sequence?: number;
}[];
const userApproverNames = approvers
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
.filter(Boolean) as string[];
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const userApproverNames = approvers.filter(
(approver) => approver.type === ApproverType.User && approver.username
) as { username: string; sequence?: number }[];
const { permission } = await permissionService.getProjectPermission({
actor,
@ -116,14 +110,13 @@ export const accessApprovalPolicyServiceFactory = ({
let approverUserIds = userApprovers;
if (userApproverNames.length) {
const approverUsers = await userDAL.find({
const approverUsersInDB = await userDAL.find({
$in: {
username: userApproverNames
username: userApproverNames.map((el) => el.username)
}
});
const approverNamesFromDb = approverUsers.map((user) => user.username);
const invalidUsernames = userApproverNames.filter((username) => !approverNamesFromDb.includes(username));
const approverUsersInDBGroupByUsername = groupBy(approverUsersInDB, (i) => i.username);
const invalidUsernames = userApproverNames.filter((el) => !approverUsersInDBGroupByUsername?.[el.username]?.[0]);
if (invalidUsernames.length) {
throw new BadRequestError({
@ -131,32 +124,13 @@ export const accessApprovalPolicyServiceFactory = ({
});
}
approverUserIds = approverUserIds.concat(approverUsers.map((user) => user.id));
}
const usersPromises: Promise<
{
id: string;
email: string | null | undefined;
username: string;
firstName: string | null | undefined;
lastName: string | null | undefined;
isPartOfGroup: boolean;
}[]
>[] = [];
const verifyAllApprovers = [...approverUserIds];
for (const groupId of groupApprovers) {
usersPromises.push(
groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }).then((group) => group.members)
approverUserIds = approverUserIds.concat(
userApproverNames.map((el) => ({
id: approverUsersInDBGroupByUsername[el.username]?.[0].id,
sequence: el.sequence
}))
);
}
const verifyGroupApprovers = (await Promise.all(usersPromises))
.flat()
.filter((user) => user.isPartOfGroup)
.map((user) => user.id);
verifyAllApprovers.push(...verifyGroupApprovers);
let groupBypassers: string[] = [];
let bypasserUserIds: string[] = [];
@ -195,6 +169,7 @@ export const accessApprovalPolicyServiceFactory = ({
}
}
const approvalsRequiredGroupByStepNumber = groupBy(approvalsRequired || [], (i) => i.stepNumber);
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
const doc = await accessApprovalPolicyDAL.create(
{
@ -210,9 +185,13 @@ export const accessApprovalPolicyServiceFactory = ({
if (approverUserIds.length) {
await accessApprovalPolicyApproverDAL.insertMany(
approverUserIds.map((userId) => ({
approverUserId: userId,
policyId: doc.id
approverUserIds.map((el) => ({
approverUserId: el.id,
policyId: doc.id,
sequence: el.sequence,
approvalsRequired: el.sequence
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
: approvals
})),
tx
);
@ -220,9 +199,13 @@ export const accessApprovalPolicyServiceFactory = ({
if (groupApprovers) {
await accessApprovalPolicyApproverDAL.insertMany(
groupApprovers.map((groupId) => ({
approverGroupId: groupId,
policyId: doc.id
groupApprovers.map((el) => ({
approverGroupId: el.id,
policyId: doc.id,
sequence: el.sequence,
approvalsRequired: el.sequence
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
: approvals
})),
tx
);
@ -254,31 +237,26 @@ export const accessApprovalPolicyServiceFactory = ({
return { ...accessApproval, environment: env, projectId: project.id };
};
const getAccessApprovalPolicyByProjectSlug = async ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
projectSlug
}: TListAccessApprovalPoliciesDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] =
async ({ actorId, actor, actorOrgId, actorAuthMethod, projectSlug }: TListAccessApprovalPoliciesDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
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,
projectId: project.id,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.SecretManager
});
// Anyone in the project should be able to get the policies.
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;
};
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
return accessApprovalPolicies;
};
const updateAccessApprovalPolicy = async ({
const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({
policyId,
approvers,
bypassers,
@ -290,22 +268,22 @@ export const accessApprovalPolicyServiceFactory = ({
actorAuthMethod,
approvals,
enforcementLevel,
allowedSelfApprovals
allowedSelfApprovals,
approvalsRequired
}: TUpdateAccessApprovalPolicy) => {
const groupApprovers = approvers
.filter((approver) => approver.type === ApproverType.Group)
.map((approver) => approver.id) as string[];
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
const userApprovers = approvers
.filter((approver) => approver.type === ApproverType.User)
.map((approver) => approver.id)
.filter(Boolean) as string[];
const userApproverNames = approvers
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
.filter(Boolean) as string[];
const userApprovers = approvers.filter((approver) => approver.type === ApproverType.User && approver.id) as {
id: string;
sequence?: number;
}[];
const userApproverNames = approvers.filter(
(approver) => approver.type === ApproverType.User && approver.username
) as { username: string; sequence?: number }[];
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Approval policy not found" });
const currentApprovals = approvals || accessApprovalPolicy.approvals;
if (
groupApprovers?.length === 0 &&
@ -401,6 +379,7 @@ export const accessApprovalPolicyServiceFactory = ({
}
}
const approvalsRequiredGroupByStepNumber = groupBy(approvalsRequired || [], (i) => i.stepNumber);
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
const doc = await accessApprovalPolicyDAL.updateById(
accessApprovalPolicy.id,
@ -417,16 +396,18 @@ export const accessApprovalPolicyServiceFactory = ({
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
if (userApprovers.length || userApproverNames.length) {
let userApproverIds = userApprovers;
let approverUserIds = userApprovers;
if (userApproverNames.length) {
const approverUsers = await userDAL.find({
const approverUsersInDB = await userDAL.find({
$in: {
username: userApproverNames
username: userApproverNames.map((el) => el.username)
}
});
const approverUsersInDBGroupByUsername = groupBy(approverUsersInDB, (i) => i.username);
const approverNamesFromDb = approverUsers.map((user) => user.username);
const invalidUsernames = userApproverNames.filter((username) => !approverNamesFromDb.includes(username));
const invalidUsernames = userApproverNames.filter(
(el) => !approverUsersInDBGroupByUsername?.[el.username]?.[0]
);
if (invalidUsernames.length) {
throw new BadRequestError({
@ -434,13 +415,21 @@ export const accessApprovalPolicyServiceFactory = ({
});
}
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
approverUserIds = approverUserIds.concat(
userApproverNames.map((el) => ({
id: approverUsersInDBGroupByUsername[el.username]?.[0].id,
sequence: el.sequence
}))
);
}
await accessApprovalPolicyApproverDAL.insertMany(
userApproverIds.map((userId) => ({
approverUserId: userId,
policyId: doc.id
approverUserIds.map((el) => ({
approverUserId: el.id,
policyId: doc.id,
sequence: el.sequence,
approvalsRequired: el.sequence
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
: approvals
})),
tx
);
@ -448,9 +437,13 @@ export const accessApprovalPolicyServiceFactory = ({
if (groupApprovers) {
await accessApprovalPolicyApproverDAL.insertMany(
groupApprovers.map((groupId) => ({
approverGroupId: groupId,
policyId: doc.id
groupApprovers.map((el) => ({
approverGroupId: el.id,
policyId: doc.id,
sequence: el.sequence,
approvalsRequired: el.sequence
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
: approvals
})),
tx
);
@ -478,8 +471,11 @@ export const accessApprovalPolicyServiceFactory = ({
);
}
await accessApprovalRequestDAL.resetReviewByPolicyId(doc.id, tx);
return doc;
});
return {
...updatedPolicy,
environment: accessApprovalPolicy.environment,
@ -487,7 +483,7 @@ export const accessApprovalPolicyServiceFactory = ({
};
};
const deleteAccessApprovalPolicy = async ({
const deleteAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["deleteAccessApprovalPolicy"] = async ({
policyId,
actor,
actorId,
@ -536,7 +532,7 @@ export const accessApprovalPolicyServiceFactory = ({
return policy;
};
const getAccessPolicyCountByEnvSlug = async ({
const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({
actor,
actorOrgId,
actorAuthMethod,
@ -573,13 +569,13 @@ export const accessApprovalPolicyServiceFactory = ({
return { count: policies.length };
};
const getAccessApprovalPolicyById = async ({
const getAccessApprovalPolicyById: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyById"] = async ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
policyId
}: TGetAccessApprovalPolicyByIdDTO) => {
}) => {
const [policy] = await accessApprovalPolicyDAL.find({}, { policyId });
if (!policy) {

View File

@ -1,7 +1,7 @@
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
import { ActorAuthMethod } from "@app/services/auth/auth-type";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
export type TIsApproversValid = {
userIds: string[];
@ -27,7 +27,10 @@ export type TCreateAccessApprovalPolicy = {
approvals: number;
secretPath: string;
environment: string;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
approvers: (
| { type: ApproverType.Group; id: string; sequence?: number }
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
)[];
bypassers?: (
| { type: BypasserType.Group; id: string }
| { type: BypasserType.User; id?: string; username?: string }
@ -36,12 +39,16 @@ export type TCreateAccessApprovalPolicy = {
name: string;
enforcementLevel: EnforcementLevel;
allowedSelfApprovals: boolean;
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
} & Omit<TProjectPermission, "projectId">;
export type TUpdateAccessApprovalPolicy = {
policyId: string;
approvals?: number;
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
approvers: (
| { type: ApproverType.Group; id: string; sequence?: number }
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
)[];
bypassers?: (
| { type: BypasserType.Group; id: string }
| { type: BypasserType.User; id?: string; username?: string }
@ -50,6 +57,7 @@ export type TUpdateAccessApprovalPolicy = {
name?: string;
enforcementLevel?: EnforcementLevel;
allowedSelfApprovals: boolean;
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
} & Omit<TProjectPermission, "projectId">;
export type TDeleteAccessApprovalPolicy = {
@ -68,3 +76,217 @@ export type TGetAccessApprovalPolicyByIdDTO = {
export type TListAccessApprovalPoliciesDTO = {
projectSlug: string;
} & Omit<TProjectPermission, "projectId">;
export interface TAccessApprovalPolicyServiceFactory {
getAccessPolicyCountByEnvSlug: ({
actor,
actorOrgId,
actorAuthMethod,
projectSlug,
actorId,
envSlug
}: TGetAccessPolicyCountByEnvironmentDTO) => Promise<{
count: number;
}>;
createAccessApprovalPolicy: ({
name,
actor,
actorId,
actorOrgId,
secretPath,
actorAuthMethod,
approvals,
approvers,
bypassers,
projectSlug,
environment,
enforcementLevel,
allowedSelfApprovals,
approvalsRequired
}: TCreateAccessApprovalPolicy) => Promise<{
environment: {
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
projectId: string;
slug: string;
position: number;
};
projectId: string;
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
}>;
deleteAccessApprovalPolicy: ({
policyId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TDeleteAccessApprovalPolicy) => Promise<{
approvers: {
id: string | null | undefined;
type: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
}>;
updateAccessApprovalPolicy: ({
policyId,
approvers,
bypassers,
secretPath,
name,
actorId,
actor,
actorOrgId,
actorAuthMethod,
approvals,
enforcementLevel,
allowedSelfApprovals,
approvalsRequired
}: TUpdateAccessApprovalPolicy) => Promise<{
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
}>;
getAccessApprovalPolicyByProjectSlug: ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
projectSlug
}: TListAccessApprovalPoliciesDTO) => Promise<
{
approvers: (
| {
id: string | null | undefined;
type: ApproverType;
name: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
| {
id: string | null | undefined;
type: ApproverType;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
)[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
bypassers: (
| {
id: string | null | undefined;
type: BypasserType;
name: string;
}
| {
id: string | null | undefined;
type: BypasserType;
}
)[];
}[]
>;
getAccessApprovalPolicyById: ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
policyId
}: TGetAccessApprovalPolicyByIdDTO) => Promise<{
approvers: (
| {
id: string | null | undefined;
type: ApproverType.User;
name: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
| {
id: string | null | undefined;
type: ApproverType.Group;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
)[];
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
envId: string;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath?: string | null | undefined;
deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string;
bypassers: (
| {
id: string | null | undefined;
type: BypasserType.User;
name: string;
}
| {
id: string | null | undefined;
type: BypasserType.Group;
}
)[];
}>;
}

View File

@ -9,195 +9,442 @@ import {
TUsers
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter, TOrmify } from "@app/lib/knex";
import { ApprovalStatus } from "./access-approval-request-types";
export type TAccessApprovalRequestDALFactory = ReturnType<typeof accessApprovalRequestDALFactory>;
export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName.AccessApprovalRequest>, "findById"> {
findById: (
id: string,
tx?: Knex
) => Promise<
| {
policy: {
approvers: (
| {
userId: string | null | undefined;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
| {
userId: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
)[];
bypassers: (
| {
userId: string | null | undefined;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
}
| {
userId: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
}
)[];
id: string;
name: string;
approvals: number;
secretPath: string | null | undefined;
enforcementLevel: string;
allowedSelfApprovals: boolean;
deletedAt: Date | null | undefined;
};
projectId: string;
environment: string;
requestedByUser: {
userId: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
};
status: string;
id: string;
createdAt: Date;
updatedAt: Date;
policyId: string;
isTemporary: boolean;
requestedByUserId: string;
privilegeId?: string | null | undefined;
requestedBy?: string | null | undefined;
temporaryRange?: string | null | undefined;
permissions?: unknown;
note?: string | null | undefined;
privilegeDeletedAt?: Date | null | undefined;
reviewers: {
userId: string;
status: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
}[];
approvers: (
| {
userId: string | null | undefined;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
| {
userId: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
}
)[];
bypassers: (
| {
userId: string | null | undefined;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
}
| {
userId: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
}
)[];
}
| undefined
>;
findRequestsWithPrivilegeByPolicyIds: (policyIds: string[]) => Promise<
{
policy: {
approvers: (
| {
userId: string | null | undefined;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
| {
userId: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
)[];
bypassers: string[];
id: string;
name: string;
approvals: number;
secretPath: string | null | undefined;
enforcementLevel: string;
allowedSelfApprovals: boolean;
envId: string;
deletedAt: Date | null | undefined;
};
projectId: string;
environment: string;
environmentName: string;
requestedByUser: {
userId: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
};
privilege: {
membershipId: string;
userId: string;
projectId: string;
isTemporary: boolean;
temporaryMode: string | null | undefined;
temporaryRange: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
permissions: unknown;
} | null;
isApproved: boolean;
status: string;
id: string;
createdAt: Date;
updatedAt: Date;
policyId: string;
isTemporary: boolean;
requestedByUserId: string;
privilegeId?: string | null | undefined;
requestedBy?: string | null | undefined;
temporaryRange?: string | null | undefined;
permissions?: unknown;
note?: string | null | undefined;
privilegeDeletedAt?: Date | null | undefined;
reviewers: {
userId: string;
status: string;
}[];
approvers: (
| {
userId: string | null | undefined;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
| {
userId: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
)[];
bypassers: string[];
}[]
>;
getCount: ({ projectId }: { projectId: string }) => Promise<{
pendingCount: number;
finalizedCount: number;
}>;
resetReviewByPolicyId: (policyId: string, tx?: Knex) => Promise<void>;
}
export const accessApprovalRequestDALFactory = (db: TDbClient) => {
export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalRequestDALFactory => {
const accessApprovalRequestOrm = ormify(db, TableName.AccessApprovalRequest);
const findRequestsWithPrivilegeByPolicyIds = async (policyIds: string[]) => {
try {
const docs = await db
.replicaNode()(TableName.AccessApprovalRequest)
.whereIn(`${TableName.AccessApprovalRequest}.policyId`, policyIds)
const findRequestsWithPrivilegeByPolicyIds: TAccessApprovalRequestDALFactory["findRequestsWithPrivilegeByPolicyIds"] =
async (policyIds) => {
try {
const docs = await db
.replicaNode()(TableName.AccessApprovalRequest)
.whereIn(`${TableName.AccessApprovalRequest}.policyId`, policyIds)
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
`${TableName.AccessApprovalRequest}.privilegeId`,
`${TableName.ProjectUserAdditionalPrivilege}.id`
)
.leftJoin(
TableName.AccessApprovalPolicy,
`${TableName.AccessApprovalRequest}.policyId`,
`${TableName.AccessApprovalPolicy}.id`
)
.leftJoin(
TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId`
)
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
`${TableName.AccessApprovalRequest}.privilegeId`,
`${TableName.ProjectUserAdditionalPrivilege}.id`
)
.leftJoin(
TableName.AccessApprovalPolicy,
`${TableName.AccessApprovalRequest}.policyId`,
`${TableName.AccessApprovalPolicy}.id`
)
.leftJoin(
TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId`
)
.leftJoin(
TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
"accessApprovalPolicyApproverUser.id"
)
.leftJoin(
TableName.UserGroupMembership,
`${TableName.AccessApprovalPolicyApprover}.approverGroupId`,
`${TableName.UserGroupMembership}.groupId`
)
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(
TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.leftJoin(
TableName.UserGroupMembership,
`${TableName.AccessApprovalPolicyApprover}.approverGroupId`,
`${TableName.UserGroupMembership}.groupId`
)
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.leftJoin(
TableName.AccessApprovalPolicyBypasser,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.leftJoin(
TableName.AccessApprovalPolicyBypasser,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyBypasser}.policyId`
)
.leftJoin<TUserGroupMembership>(
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
`bypasserUserGroupMembership.groupId`
)
.join<TUsers>(
db(TableName.Users).as("requestedByUser"),
`${TableName.AccessApprovalRequest}.requestedByUserId`,
`requestedByUser.id`
)
.join<TUsers>(
db(TableName.Users).as("requestedByUser"),
`${TableName.AccessApprovalRequest}.requestedByUserId`,
`requestedByUser.id`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(
db.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
db.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
)
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(db.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"))
.select(db.ref("approvalsRequired").withSchema(TableName.AccessApprovalPolicyApprover))
.select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"))
.select(db.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"))
.select(
db.ref("email").withSchema("accessApprovalPolicyApproverUser").as("approverEmail"),
db.ref("email").withSchema(TableName.Users).as("approverGroupEmail"),
db.ref("username").withSchema("accessApprovalPolicyApproverUser").as("approverUsername"),
db.ref("username").withSchema(TableName.Users).as("approverGroupUsername")
)
.select(
db.ref("projectId").withSchema(TableName.Environment),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName")
)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(
db.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
db.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
)
.select(
db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
)
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"))
// TODO: ADD SUPPORT FOR GROUPS!!!!
.select(
db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
db.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
.select(db.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"))
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeUserId"),
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeMembershipId"),
.select(
db.ref("projectId").withSchema(TableName.Environment),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName")
)
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeTemporaryAccessEndTime"),
.select(
db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
)
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegePermissions")
)
.orderBy(`${TableName.AccessApprovalRequest}.createdAt`, "desc");
// TODO: ADD SUPPORT FOR GROUPS!!!!
.select(
db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
db.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
const formattedDocs = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (doc) => ({
...AccessApprovalRequestsSchema.parse(doc),
projectId: doc.projectId,
environment: doc.envSlug,
environmentName: doc.envName,
policy: {
id: doc.policyId,
name: doc.policyName,
approvals: doc.policyApprovals,
secretPath: doc.policySecretPath,
enforcementLevel: doc.policyEnforcementLevel,
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
envId: doc.policyEnvId,
deletedAt: doc.policyDeletedAt
},
requestedByUser: {
userId: doc.requestedByUserId,
email: doc.requestedByUserEmail,
firstName: doc.requestedByUserFirstName,
lastName: doc.requestedByUserLastName,
username: doc.requestedByUserUsername
},
privilege: doc.privilegeId
? {
membershipId: doc.privilegeMembershipId,
userId: doc.privilegeUserId,
projectId: doc.projectId,
isTemporary: doc.privilegeIsTemporary,
temporaryMode: doc.privilegeTemporaryMode,
temporaryRange: doc.privilegeTemporaryRange,
temporaryAccessStartTime: doc.privilegeTemporaryAccessStartTime,
temporaryAccessEndTime: doc.privilegeTemporaryAccessEndTime,
permissions: doc.privilegePermissions
}
: null,
isApproved: doc.status === ApprovalStatus.APPROVED
}),
childrenMapper: [
{
key: "reviewerUserId",
label: "reviewers" as const,
mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined)
},
{
key: "approverUserId",
label: "approvers" as const,
mapper: ({ approverUserId, approverSequence, approvalsRequired, approverUsername, approverEmail }) => ({
userId: approverUserId,
sequence: approverSequence,
approvalsRequired,
email: approverEmail,
username: approverUsername
})
},
{
key: "approverGroupUserId",
label: "approvers" as const,
mapper: ({
approverGroupUserId,
approverSequence,
approvalsRequired,
approverGroupEmail,
approverGroupUsername
}) => ({
userId: approverGroupUserId,
sequence: approverSequence,
approvalsRequired,
email: approverGroupEmail,
username: approverGroupUsername
})
},
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
{
key: "bypasserGroupUserId",
label: "bypassers" as const,
mapper: ({ bypasserGroupUserId }) => bypasserGroupUserId
}
]
});
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeUserId"),
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeMembershipId"),
if (!formattedDocs) return [];
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeTemporaryAccessEndTime"),
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegePermissions")
)
.orderBy(`${TableName.AccessApprovalRequest}.createdAt`, "desc");
const formattedDocs = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (doc) => ({
...AccessApprovalRequestsSchema.parse(doc),
projectId: doc.projectId,
environment: doc.envSlug,
environmentName: doc.envName,
return formattedDocs.map((doc) => ({
...doc,
policy: {
id: doc.policyId,
name: doc.policyName,
approvals: doc.policyApprovals,
secretPath: doc.policySecretPath,
enforcementLevel: doc.policyEnforcementLevel,
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
envId: doc.policyEnvId,
deletedAt: doc.policyDeletedAt
},
requestedByUser: {
userId: doc.requestedByUserId,
email: doc.requestedByUserEmail,
firstName: doc.requestedByUserFirstName,
lastName: doc.requestedByUserLastName,
username: doc.requestedByUserUsername
},
privilege: doc.privilegeId
? {
membershipId: doc.privilegeMembershipId,
userId: doc.privilegeUserId,
projectId: doc.projectId,
isTemporary: doc.privilegeIsTemporary,
temporaryMode: doc.privilegeTemporaryMode,
temporaryRange: doc.privilegeTemporaryRange,
temporaryAccessStartTime: doc.privilegeTemporaryAccessStartTime,
temporaryAccessEndTime: doc.privilegeTemporaryAccessEndTime,
permissions: doc.privilegePermissions
}
: null,
isApproved: !!doc.policyDeletedAt || !!doc.privilegeId || doc.status !== ApprovalStatus.PENDING
}),
childrenMapper: [
{
key: "reviewerUserId",
label: "reviewers" as const,
mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined)
},
{ key: "approverUserId", label: "approvers" as const, mapper: ({ approverUserId }) => approverUserId },
{
key: "approverGroupUserId",
label: "approvers" as const,
mapper: ({ approverGroupUserId }) => approverGroupUserId
},
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
{
key: "bypasserGroupUserId",
label: "bypassers" as const,
mapper: ({ bypasserGroupUserId }) => bypasserGroupUserId
...doc.policy,
approvers: doc.approvers.filter((el) => el.userId).sort((a, b) => (a.sequence || 0) - (b.sequence || 0)),
bypassers: doc.bypassers
}
]
});
if (!formattedDocs) return [];
return formattedDocs.map((doc) => ({
...doc,
policy: { ...doc.policy, approvers: doc.approvers, bypassers: doc.bypassers }
}));
} catch (error) {
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
}
};
}));
} catch (error) {
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
}
};
const findQuery = (filter: TFindFilter<TAccessApprovalRequests>, tx: Knex) =>
tx(TableName.AccessApprovalRequest)
@ -272,6 +519,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
tx.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"),
tx.ref("approvalsRequired").withSchema(TableName.AccessApprovalPolicyApprover),
tx.ref("userId").withSchema(TableName.UserGroupMembership),
tx.ref("email").withSchema("accessApprovalPolicyApproverUser").as("approverEmail"),
tx.ref("email").withSchema("accessApprovalPolicyGroupApproverUser").as("approverGroupEmail"),
@ -318,7 +567,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
);
const findById = async (id: string, tx?: Knex) => {
const findById: TAccessApprovalRequestDALFactory["findById"] = async (id, tx) => {
try {
const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
const docs = await sql;
@ -367,13 +616,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
approverEmail: email,
approverUsername: username,
approverLastName: lastName,
approverFirstName: firstName
approverFirstName: firstName,
approverSequence,
approvalsRequired
}) => ({
userId: approverUserId,
email,
firstName,
lastName,
username
username,
sequence: approverSequence,
approvalsRequired
})
},
{
@ -384,13 +637,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
approverGroupEmail: email,
approverGroupUsername: username,
approverGroupLastName: lastName,
approverFirstName: firstName
approverFirstName: firstName,
approverSequence,
approvalsRequired
}) => ({
userId,
email,
firstName,
lastName,
username
username,
sequence: approverSequence,
approvalsRequired
})
},
{
@ -434,7 +691,9 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
...formattedDoc[0],
policy: {
...formattedDoc[0].policy,
approvers: formattedDoc[0].approvers,
approvers: formattedDoc[0].approvers
.filter((el) => el.userId)
.sort((a, b) => (a.sequence || 0) - (b.sequence || 0)),
bypassers: formattedDoc[0].bypassers
}
};
@ -443,7 +702,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}
};
const getCount = async ({ projectId }: { projectId: string }) => {
const getCount: TAccessApprovalRequestDALFactory["getCount"] = async ({ projectId }) => {
try {
const accessRequests = await db
.replicaNode()(TableName.AccessApprovalRequest)
@ -495,7 +754,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
req.status === ApprovalStatus.PENDING
);
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required.
const finalizedApprovals = formattedRequests.filter(
(req) =>
req.privilegeId ||
@ -509,5 +768,27 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}
};
return { ...accessApprovalRequestOrm, findById, findRequestsWithPrivilegeByPolicyIds, getCount };
const resetReviewByPolicyId: TAccessApprovalRequestDALFactory["resetReviewByPolicyId"] = async (policyId, tx) => {
try {
await (tx || db)(TableName.AccessApprovalRequestReviewer)
.leftJoin(
TableName.AccessApprovalRequest,
`${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId`
)
.where(`${TableName.AccessApprovalRequest}.status` as "status", ApprovalStatus.PENDING)
.where(`${TableName.AccessApprovalRequest}.policyId` as "policyId", policyId)
.del();
} catch (error) {
throw new DatabaseError({ error, name: "ResetReviewByPolicyId" });
}
};
return {
...accessApprovalRequestOrm,
findById,
findRequestsWithPrivilegeByPolicyIds,
getCount,
resetReviewByPolicyId
};
};

View File

@ -1,10 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TAccessApprovalRequestReviewerDALFactory = ReturnType<typeof accessApprovalRequestReviewerDALFactory>;
export type TAccessApprovalRequestReviewerDALFactory = TOrmify<TableName.AccessApprovalRequestReviewer>;
export const accessApprovalRequestReviewerDALFactory = (db: TDbClient) => {
export const accessApprovalRequestReviewerDALFactory = (db: TDbClient): TAccessApprovalRequestReviewerDALFactory => {
const secretApprovalRequestReviewerOrm = ormify(db, TableName.AccessApprovalRequestReviewer);
return secretApprovalRequestReviewerOrm;
};

View File

@ -4,6 +4,7 @@ import msFn from "ms";
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn";
import { ms } from "@app/lib/ms";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { EnforcementLevel } from "@app/lib/types";
@ -22,19 +23,13 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal";
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
import { TGroupDALFactory } from "../group/group-dal";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
import { verifyRequestedPermissions } from "./access-approval-request-fns";
import { TAccessApprovalRequestReviewerDALFactory } from "./access-approval-request-reviewer-dal";
import {
ApprovalStatus,
TCreateAccessApprovalRequestDTO,
TGetAccessRequestCountDTO,
TListApprovalRequestsDTO,
TReviewAccessRequestDTO
} from "./access-approval-request-types";
import { ApprovalStatus, TAccessApprovalRequestServiceFactory } from "./access-approval-request-types";
type TSecretApprovalRequestServiceFactoryDep = {
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "create" | "findById">;
@ -74,8 +69,6 @@ type TSecretApprovalRequestServiceFactoryDep = {
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
};
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
export const accessApprovalRequestServiceFactory = ({
groupDAL,
projectDAL,
@ -92,8 +85,8 @@ export const accessApprovalRequestServiceFactory = ({
microsoftTeamsService,
projectMicrosoftTeamsConfigDAL,
projectSlackConfigDAL
}: TSecretApprovalRequestServiceFactoryDep) => {
const createAccessApprovalRequest = async ({
}: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => {
const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({
isTemporary,
temporaryRange,
actorId,
@ -103,7 +96,7 @@ export const accessApprovalRequestServiceFactory = ({
actorAuthMethod,
projectSlug,
note
}: TCreateAccessApprovalRequestDTO) => {
}) => {
const cfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@ -280,7 +273,7 @@ export const accessApprovalRequestServiceFactory = ({
return { request: approval };
};
const listApprovalRequests = async ({
const listApprovalRequests: TAccessApprovalRequestServiceFactory["listApprovalRequests"] = async ({
projectSlug,
authorProjectMembershipId,
envSlug,
@ -288,7 +281,7 @@ export const accessApprovalRequestServiceFactory = ({
actorOrgId,
actorId,
actorAuthMethod
}: TListApprovalRequestsDTO) => {
}) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
@ -318,7 +311,7 @@ export const accessApprovalRequestServiceFactory = ({
return { requests };
};
const reviewAccessRequest = async ({
const reviewAccessRequest: TAccessApprovalRequestServiceFactory["reviewAccessRequest"] = async ({
requestId,
actor,
status,
@ -326,7 +319,7 @@ export const accessApprovalRequestServiceFactory = ({
actorAuthMethod,
actorOrgId,
bypassReason
}: TReviewAccessRequestDTO) => {
}) => {
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
if (!accessApprovalRequest) {
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
@ -358,7 +351,6 @@ export const accessApprovalRequestServiceFactory = ({
const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypass);
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
// If user is (not an approver OR cant self approve) AND can't bypass policy
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
throw new BadRequestError({
@ -380,8 +372,44 @@ export const accessApprovalRequestServiceFactory = ({
}
const existingReviews = await accessApprovalRequestReviewerDAL.find({ requestId: accessApprovalRequest.id });
if (existingReviews.some((review) => review.status === ApprovalStatus.REJECTED)) {
throw new BadRequestError({ message: "The request has already been rejected by another reviewer" });
if (accessApprovalRequest.status !== ApprovalStatus.PENDING) {
throw new BadRequestError({ message: "The request has been closed" });
}
const reviewsGroupById = groupBy(
existingReviews.filter((review) => review.status === ApprovalStatus.APPROVED),
(i) => i.reviewerUserId
);
const approvedSequences = policy.approvers.reduce(
(acc, curr) => {
const hasApproved = reviewsGroupById?.[curr.userId as string]?.[0];
if (acc?.[acc.length - 1]?.step === curr.sequence) {
if (hasApproved) {
acc[acc.length - 1].approvals += 1;
}
return acc;
}
acc.push({
step: curr.sequence || 1,
approvals: hasApproved ? 1 : 0,
requiredApprovals: curr.approvalsRequired || 1
});
return acc;
},
[] as { step: number; approvals: number; requiredApprovals: number }[]
);
const presentSequence = approvedSequences.find((el) => el.approvals < el.requiredApprovals) || {
step: 1,
approvals: 0,
requiredApprovals: 1
};
if (presentSequence) {
const isApproverOfTheSequence = policy.approvers.find(
(el) => el.sequence === presentSequence.step && el.userId === actorId
);
if (!isApproverOfTheSequence) throw new BadRequestError({ message: "You are not reviewer in this step" });
}
const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => {
@ -426,11 +454,14 @@ export const accessApprovalRequestServiceFactory = ({
);
}
const otherReviews = existingReviews.filter((er) => er.reviewerUserId !== actorId);
const allUniqueReviews = [...otherReviews, reviewForThisActorProcessing];
if (status === ApprovalStatus.REJECTED) {
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { status: ApprovalStatus.REJECTED }, tx);
return reviewForThisActorProcessing;
}
const approvedReviews = allUniqueReviews.filter((r) => r.status === ApprovalStatus.APPROVED);
const meetsStandardApprovalThreshold = approvedReviews.length >= policy.approvals;
const meetsStandardApprovalThreshold =
(presentSequence?.approvals || 0) + 1 >= presentSequence.requiredApprovals &&
approvedSequences.at(-1)?.step === presentSequence?.step;
if (
reviewForThisActorProcessing.status === ApprovalStatus.APPROVED &&
@ -527,7 +558,13 @@ export const accessApprovalRequestServiceFactory = ({
return reviewStatus;
};
const getCount = async ({ projectSlug, actor, actorAuthMethod, actorId, actorOrgId }: TGetAccessRequestCountDTO) => {
const getCount: TAccessApprovalRequestServiceFactory["getCount"] = async ({
projectSlug,
actor,
actorAuthMethod,
actorId,
actorOrgId
}) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });

View File

@ -34,3 +34,124 @@ export type TListApprovalRequestsDTO = {
authorProjectMembershipId?: string;
envSlug?: string;
} & Omit<TProjectPermission, "projectId">;
export interface TAccessApprovalRequestServiceFactory {
createAccessApprovalRequest: (arg: TCreateAccessApprovalRequestDTO) => Promise<{
request: {
status: string;
id: string;
createdAt: Date;
updatedAt: Date;
policyId: string;
isTemporary: boolean;
requestedByUserId: string;
privilegeId?: string | null | undefined;
requestedBy?: string | null | undefined;
temporaryRange?: string | null | undefined;
permissions?: unknown;
note?: string | null | undefined;
privilegeDeletedAt?: Date | null | undefined;
};
}>;
listApprovalRequests: (arg: TListApprovalRequestsDTO) => Promise<{
requests: {
policy: {
approvers: (
| {
userId: string | null | undefined;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
| {
userId: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
)[];
bypassers: string[];
id: string;
name: string;
approvals: number;
secretPath: string | null | undefined;
enforcementLevel: string;
allowedSelfApprovals: boolean;
envId: string;
deletedAt: Date | null | undefined;
};
projectId: string;
environment: string;
environmentName: string;
requestedByUser: {
userId: string;
email: string | null | undefined;
firstName: string | null | undefined;
lastName: string | null | undefined;
username: string;
};
privilege: {
membershipId: string;
userId: string;
projectId: string;
isTemporary: boolean;
temporaryMode: string | null | undefined;
temporaryRange: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
permissions: unknown;
} | null;
isApproved: boolean;
status: string;
id: string;
createdAt: Date;
updatedAt: Date;
policyId: string;
isTemporary: boolean;
requestedByUserId: string;
privilegeId?: string | null | undefined;
requestedBy?: string | null | undefined;
temporaryRange?: string | null | undefined;
permissions?: unknown;
note?: string | null | undefined;
privilegeDeletedAt?: Date | null | undefined;
reviewers: {
userId: string;
status: string;
}[];
approvers: (
| {
userId: string | null | undefined;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
| {
userId: string;
sequence: number | null | undefined;
approvalsRequired: number | null | undefined;
email: string | null | undefined;
username: string;
}
)[];
bypassers: string[];
}[];
}>;
reviewAccessRequest: (arg: TReviewAccessRequestDTO) => Promise<{
id: string;
requestId: string;
reviewerUserId: string;
status: string;
createdAt: Date;
updatedAt: Date;
}>;
getCount: (arg: TGetAccessRequestCountDTO) => Promise<{
count: {
pendingCount: number;
finalizedCount: number;
};
}>;
}

View File

@ -0,0 +1,4 @@
export * from "./oracledb-connection-enums";
export * from "./oracledb-connection-fns";
export * from "./oracledb-connection-schemas";
export * from "./oracledb-connection-types";

View File

@ -0,0 +1,3 @@
export enum OracleDBConnectionMethod {
UsernameAndPassword = "username-and-password"
}

View File

@ -0,0 +1,12 @@
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { OracleDBConnectionMethod } from "./oracledb-connection-enums";
export const getOracleDBConnectionListItem = () => {
return {
name: "OracleDB" as const,
app: AppConnection.OracleDB as const,
methods: Object.values(OracleDBConnectionMethod) as [OracleDBConnectionMethod.UsernameAndPassword],
supportsPlatformManagement: true as const
};
};

View File

@ -0,0 +1,64 @@
import z from "zod";
import { AppConnections } from "@app/lib/api-docs";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
BaseAppConnectionSchema,
GenericCreateAppConnectionFieldsSchema,
GenericUpdateAppConnectionFieldsSchema
} from "@app/services/app-connection/app-connection-schemas";
import { BaseSqlUsernameAndPasswordConnectionSchema } from "@app/services/app-connection/shared/sql";
import { OracleDBConnectionMethod } from "./oracledb-connection-enums";
export const OracleDBConnectionCredentialsSchema = BaseSqlUsernameAndPasswordConnectionSchema;
const BaseOracleDBConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.OracleDB) });
export const OracleDBConnectionSchema = BaseOracleDBConnectionSchema.extend({
method: z.literal(OracleDBConnectionMethod.UsernameAndPassword),
credentials: OracleDBConnectionCredentialsSchema
});
export const SanitizedOracleDBConnectionSchema = z.discriminatedUnion("method", [
BaseOracleDBConnectionSchema.extend({
method: z.literal(OracleDBConnectionMethod.UsernameAndPassword),
credentials: OracleDBConnectionCredentialsSchema.pick({
host: true,
database: true,
port: true,
username: true,
sslEnabled: true,
sslRejectUnauthorized: true,
sslCertificate: true
})
})
]);
export const ValidateOracleDBConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({
method: z
.literal(OracleDBConnectionMethod.UsernameAndPassword)
.describe(AppConnections.CREATE(AppConnection.OracleDB).method),
credentials: OracleDBConnectionCredentialsSchema.describe(AppConnections.CREATE(AppConnection.OracleDB).credentials)
})
]);
export const CreateOracleDBConnectionSchema = ValidateOracleDBConnectionCredentialsSchema.and(
GenericCreateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true })
);
export const UpdateOracleDBConnectionSchema = z
.object({
credentials: OracleDBConnectionCredentialsSchema.optional().describe(
AppConnections.UPDATE(AppConnection.OracleDB).credentials
)
})
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true }));
export const OracleDBConnectionListItemSchema = z.object({
name: z.literal("OracleDB"),
app: z.literal(AppConnection.OracleDB),
methods: z.nativeEnum(OracleDBConnectionMethod).array(),
supportsPlatformManagement: z.literal(true)
});

View File

@ -0,0 +1,17 @@
import z from "zod";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateOracleDBConnectionSchema,
OracleDBConnectionSchema,
ValidateOracleDBConnectionCredentialsSchema
} from "./oracledb-connection-schemas";
export type TOracleDBConnection = z.infer<typeof OracleDBConnectionSchema>;
export type TOracleDBConnectionInput = z.infer<typeof CreateOracleDBConnectionSchema> & {
app: AppConnection.OracleDB;
};
export type TValidateOracleDBConnectionCredentialsSchema = typeof ValidateOracleDBConnectionCredentialsSchema;

View File

@ -7,29 +7,30 @@ import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import {
ProjectPermissionIdentityActions,
ProjectPermissionMemberActions,
ProjectPermissionSub
} from "../permission/project-permission";
import { TAssumeProjectPrivilegeDTO } from "./assume-privilege-types";
import { TAssumePrivilegeServiceFactory } from "./assume-privilege-types";
type TAssumePrivilegeServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TAssumePrivilegeServiceFactory = ReturnType<typeof assumePrivilegeServiceFactory>;
export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }: TAssumePrivilegeServiceFactoryDep) => {
const assumeProjectPrivileges = async ({
export const assumePrivilegeServiceFactory = ({
projectDAL,
permissionService
}: TAssumePrivilegeServiceFactoryDep): TAssumePrivilegeServiceFactory => {
const assumeProjectPrivileges: TAssumePrivilegeServiceFactory["assumeProjectPrivileges"] = async ({
targetActorType,
targetActorId,
projectId,
actorPermissionDetails,
tokenVersionId
}: TAssumeProjectPrivilegeDTO) => {
}) => {
const project = await projectDAL.findById(projectId);
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
const { permission } = await permissionService.getProjectPermission({
@ -79,7 +80,10 @@ export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }:
return { actorType: targetActorType, actorId: targetActorId, projectId, assumePrivilegesToken };
};
const verifyAssumePrivilegeToken = (token: string, tokenVersionId: string) => {
const verifyAssumePrivilegeToken: TAssumePrivilegeServiceFactory["verifyAssumePrivilegeToken"] = (
token,
tokenVersionId
) => {
const appCfg = getConfig();
const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as {
tokenVersionId: string;

View File

@ -8,3 +8,28 @@ export type TAssumeProjectPrivilegeDTO = {
tokenVersionId: string;
actorPermissionDetails: OrgServiceActor;
};
export interface TAssumePrivilegeServiceFactory {
assumeProjectPrivileges: ({
targetActorType,
targetActorId,
projectId,
actorPermissionDetails,
tokenVersionId
}: TAssumeProjectPrivilegeDTO) => Promise<{
actorType: ActorType.USER | ActorType.IDENTITY;
actorId: string;
projectId: string;
assumePrivilegesToken: string;
}>;
verifyAssumePrivilegeToken: (
token: string,
tokenVersionId: string
) => {
tokenVersionId: string;
projectId: string;
requesterId: string;
actorType: ActorType;
actorId: string;
};
}

View File

@ -1,10 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TAuditLogStreamDALFactory = ReturnType<typeof auditLogStreamDALFactory>;
export type TAuditLogStreamDALFactory = TOrmify<TableName.AuditLogStream>;
export const auditLogStreamDALFactory = (db: TDbClient) => {
export const auditLogStreamDALFactory = (db: TDbClient): TAuditLogStreamDALFactory => {
const orm = ormify(db, TableName.AuditLogStream);
return orm;

View File

@ -11,16 +11,9 @@ import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
import { AUDIT_LOG_STREAM_TIMEOUT } from "../audit-log/audit-log-queue";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TAuditLogStreamDALFactory } from "./audit-log-stream-dal";
import {
LogStreamHeaders,
TCreateAuditLogStreamDTO,
TDeleteAuditLogStreamDTO,
TGetDetailsAuditLogStreamDTO,
TListAuditLogStreamDTO,
TUpdateAuditLogStreamDTO
} from "./audit-log-stream-types";
import { LogStreamHeaders, TAuditLogStreamServiceFactory } from "./audit-log-stream-types";
type TAuditLogStreamServiceFactoryDep = {
auditLogStreamDAL: TAuditLogStreamDALFactory;
@ -28,21 +21,19 @@ type TAuditLogStreamServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TAuditLogStreamServiceFactory = ReturnType<typeof auditLogStreamServiceFactory>;
export const auditLogStreamServiceFactory = ({
auditLogStreamDAL,
permissionService,
licenseService
}: TAuditLogStreamServiceFactoryDep) => {
const create = async ({
}: TAuditLogStreamServiceFactoryDep): TAuditLogStreamServiceFactory => {
const create: TAuditLogStreamServiceFactory["create"] = async ({
url,
actor,
headers = [],
actorId,
actorOrgId,
actorAuthMethod
}: TCreateAuditLogStreamDTO) => {
}) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
const plan = await licenseService.getPlan(actorOrgId);
@ -110,7 +101,7 @@ export const auditLogStreamServiceFactory = ({
return logStream;
};
const updateById = async ({
const updateById: TAuditLogStreamServiceFactory["updateById"] = async ({
id,
url,
actor,
@ -118,7 +109,7 @@ export const auditLogStreamServiceFactory = ({
actorId,
actorOrgId,
actorAuthMethod
}: TUpdateAuditLogStreamDTO) => {
}) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
const plan = await licenseService.getPlan(actorOrgId);
@ -175,7 +166,13 @@ export const auditLogStreamServiceFactory = ({
return updatedLogStream;
};
const deleteById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TDeleteAuditLogStreamDTO) => {
const deleteById: TAuditLogStreamServiceFactory["deleteById"] = async ({
id,
actor,
actorId,
actorOrgId,
actorAuthMethod
}) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
const logStream = await auditLogStreamDAL.findById(id);
@ -189,7 +186,13 @@ export const auditLogStreamServiceFactory = ({
return deletedLogStream;
};
const getById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetDetailsAuditLogStreamDTO) => {
const getById: TAuditLogStreamServiceFactory["getById"] = async ({
id,
actor,
actorId,
actorOrgId,
actorAuthMethod
}) => {
const logStream = await auditLogStreamDAL.findById(id);
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
@ -212,7 +215,7 @@ export const auditLogStreamServiceFactory = ({
return { ...logStream, headers };
};
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListAuditLogStreamDTO) => {
const list: TAuditLogStreamServiceFactory["list"] = async ({ actor, actorId, actorOrgId, actorAuthMethod }) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,

View File

@ -1,3 +1,4 @@
import { TAuditLogStreams } from "@app/db/schemas";
import { TOrgPermission } from "@app/lib/types";
export type LogStreamHeaders = {
@ -25,3 +26,23 @@ export type TListAuditLogStreamDTO = Omit<TOrgPermission, "orgId">;
export type TGetDetailsAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
id: string;
};
export type TAuditLogStreamServiceFactory = {
create: (arg: TCreateAuditLogStreamDTO) => Promise<TAuditLogStreams>;
updateById: (arg: TUpdateAuditLogStreamDTO) => Promise<TAuditLogStreams>;
deleteById: (arg: TDeleteAuditLogStreamDTO) => Promise<TAuditLogStreams>;
getById: (arg: TGetDetailsAuditLogStreamDTO) => Promise<{
headers: LogStreamHeaders[] | undefined;
orgId: string;
url: string;
id: string;
createdAt: Date;
updatedAt: Date;
encryptedHeadersCiphertext?: string | null | undefined;
encryptedHeadersIV?: string | null | undefined;
encryptedHeadersTag?: string | null | undefined;
encryptedHeadersAlgorithm?: string | null | undefined;
encryptedHeadersKeyEncoding?: string | null | undefined;
}>;
list: (arg: TListAuditLogStreamDTO) => Promise<TAuditLogStreams[]>;
};

View File

@ -2,16 +2,29 @@
import knex from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { TableName, TAuditLogs } from "@app/db/schemas";
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { ormify, selectAllTableCols, TOrmify } from "@app/lib/knex";
import { logger } from "@app/lib/logger";
import { QueueName } from "@app/queue";
import { ActorType } from "@app/services/auth/auth-type";
import { EventType, filterableSecretEvents } from "./audit-log-types";
export type TAuditLogDALFactory = ReturnType<typeof auditLogDALFactory>;
export interface TAuditLogDALFactory extends Omit<TOrmify<TableName.AuditLog>, "find"> {
pruneAuditLog: (tx?: knex.Knex) => Promise<void>;
find: (
arg: Omit<TFindQuery, "actor" | "eventType"> & {
actorId?: string | undefined;
actorType?: ActorType | undefined;
secretPath?: string | undefined;
secretKey?: string | undefined;
eventType?: EventType[] | undefined;
eventMetadata?: Record<string, string> | undefined;
},
tx?: knex.Knex
) => Promise<TAuditLogs[]>;
}
type TFindQuery = {
actor?: string;
@ -29,7 +42,7 @@ type TFindQuery = {
export const auditLogDALFactory = (db: TDbClient) => {
const auditLogOrm = ormify(db, TableName.AuditLog);
const find = async (
const find: TAuditLogDALFactory["find"] = async (
{
orgId,
projectId,
@ -45,15 +58,8 @@ export const auditLogDALFactory = (db: TDbClient) => {
secretKey,
eventType,
eventMetadata
}: Omit<TFindQuery, "actor" | "eventType"> & {
actorId?: string;
actorType?: ActorType;
secretPath?: string;
secretKey?: string;
eventType?: EventType[];
eventMetadata?: Record<string, string>;
},
tx?: knex.Knex
tx
) => {
if (!orgId && !projectId) {
throw new Error("Either orgId or projectId must be provided");
@ -154,7 +160,7 @@ export const auditLogDALFactory = (db: TDbClient) => {
};
// delete all audit log that have expired
const pruneAuditLog = async (tx?: knex.Knex) => {
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => {
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;

View File

@ -21,7 +21,9 @@ type TAuditLogQueueServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TAuditLogQueueServiceFactory = Awaited<ReturnType<typeof auditLogQueueServiceFactory>>;
export type TAuditLogQueueServiceFactory = {
pushToLog: (data: TCreateAuditLogDTO) => Promise<void>;
};
// keep this timeout 5s it must be fast because else the queue will take time to finish
// audit log is a crowded queue thus needs to be fast
@ -33,7 +35,7 @@ export const auditLogQueueServiceFactory = async ({
projectDAL,
licenseService,
auditLogStreamDAL
}: TAuditLogQueueServiceFactoryDep) => {
}: TAuditLogQueueServiceFactoryDep): Promise<TAuditLogQueueServiceFactory> => {
const appCfg = getConfig();
const pushToLog = async (data: TCreateAuditLogDTO) => {

View File

@ -7,11 +7,11 @@ import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TAuditLogDALFactory } from "./audit-log-dal";
import { TAuditLogQueueServiceFactory } from "./audit-log-queue";
import { EventType, TCreateAuditLogDTO, TListProjectAuditLogDTO } from "./audit-log-types";
import { EventType, TAuditLogServiceFactory } from "./audit-log-types";
type TAuditLogServiceFactoryDep = {
auditLogDAL: TAuditLogDALFactory;
@ -19,14 +19,18 @@ type TAuditLogServiceFactoryDep = {
auditLogQueue: TAuditLogQueueServiceFactory;
};
export type TAuditLogServiceFactory = ReturnType<typeof auditLogServiceFactory>;
export const auditLogServiceFactory = ({
auditLogDAL,
auditLogQueue,
permissionService
}: TAuditLogServiceFactoryDep) => {
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
}: TAuditLogServiceFactoryDep): TAuditLogServiceFactory => {
const listAuditLogs: TAuditLogServiceFactory["listAuditLogs"] = async ({
actorAuthMethod,
actorId,
actorOrgId,
actor,
filter
}) => {
// Filter logs for specific project
if (filter.projectId) {
const { permission } = await permissionService.getProjectPermission({
@ -75,7 +79,7 @@ export const auditLogServiceFactory = ({
}));
};
const createAuditLog = async (data: TCreateAuditLogDTO) => {
const createAuditLog: TAuditLogServiceFactory["createAuditLog"] = async (data) => {
const appCfg = getConfig();
if (appCfg.DISABLE_AUDIT_LOG_GENERATION) {
return;

View File

@ -82,6 +82,32 @@ export type TCreateAuditLogDTO = {
projectId?: string;
} & BaseAuthData;
export type TAuditLogServiceFactory = {
createAuditLog: (data: TCreateAuditLogDTO) => Promise<void>;
listAuditLogs: (arg: TListProjectAuditLogDTO) => Promise<
{
event: {
type: string;
metadata: unknown;
};
actor: {
type: string;
metadata: unknown;
};
id: string;
createdAt: Date;
updatedAt: Date;
orgId?: string | null | undefined;
userAgent?: string | null | undefined;
expiresAt?: Date | null | undefined;
ipAddress?: string | null | undefined;
userAgentType?: string | null | undefined;
projectId?: string | null | undefined;
projectName?: string | null | undefined;
}[]
>;
};
export type AuditLogInfo = Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
interface BaseAuthData {
@ -170,6 +196,12 @@ export enum EventType {
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
LOGIN_IDENTITY_ALICLOUD_AUTH = "login-identity-alicloud-auth",
ADD_IDENTITY_ALICLOUD_AUTH = "add-identity-alicloud-auth",
UPDATE_IDENTITY_ALICLOUD_AUTH = "update-identity-alicloud-auth",
REVOKE_IDENTITY_ALICLOUD_AUTH = "revoke-identity-alicloud-auth",
GET_IDENTITY_ALICLOUD_AUTH = "get-identity-alicloud-auth",
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
@ -748,6 +780,7 @@ interface CreateIdentityEvent {
metadata: {
identityId: string;
name: string;
hasDeleteProtection: boolean;
};
}
@ -756,6 +789,7 @@ interface UpdateIdentityEvent {
metadata: {
identityId: string;
name?: string;
hasDeleteProtection?: boolean;
};
}
@ -1060,6 +1094,53 @@ interface GetIdentityAwsAuthEvent {
};
}
interface LoginIdentityAliCloudAuthEvent {
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH;
metadata: {
identityId: string;
identityAliCloudAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityAliCloudAuthEvent {
type: EventType.ADD_IDENTITY_ALICLOUD_AUTH;
metadata: {
identityId: string;
allowedArns: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface DeleteIdentityAliCloudAuthEvent {
type: EventType.REVOKE_IDENTITY_ALICLOUD_AUTH;
metadata: {
identityId: string;
};
}
interface UpdateIdentityAliCloudAuthEvent {
type: EventType.UPDATE_IDENTITY_ALICLOUD_AUTH;
metadata: {
identityId: string;
allowedArns: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityAliCloudAuthEvent {
type: EventType.GET_IDENTITY_ALICLOUD_AUTH;
metadata: {
identityId: string;
};
}
interface LoginIdentityOciAuthEvent {
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
metadata: {
@ -3272,6 +3353,11 @@ export type Event =
| UpdateIdentityAwsAuthEvent
| GetIdentityAwsAuthEvent
| DeleteIdentityAwsAuthEvent
| LoginIdentityAliCloudAuthEvent
| AddIdentityAliCloudAuthEvent
| UpdateIdentityAliCloudAuthEvent
| GetIdentityAliCloudAuthEvent
| DeleteIdentityAliCloudAuthEvent
| LoginIdentityOciAuthEvent
| AddIdentityOciAuthEvent
| UpdateIdentityOciAuthEvent

View File

@ -1,10 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TCertificateAuthorityCrlDALFactory = ReturnType<typeof certificateAuthorityCrlDALFactory>;
export type TCertificateAuthorityCrlDALFactory = TOrmify<TableName.CertificateAuthorityCrl>;
export const certificateAuthorityCrlDALFactory = (db: TDbClient) => {
export const certificateAuthorityCrlDALFactory = (db: TDbClient): TCertificateAuthorityCrlDALFactory => {
const caCrlOrm = ormify(db, TableName.CertificateAuthorityCrl);
return caCrlOrm;
};

View File

@ -3,7 +3,7 @@ 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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { NotFoundError } from "@app/lib/errors";
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
@ -12,7 +12,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
import { TGetCaCrlsDTO, TGetCrlById } from "./certificate-authority-crl-types";
import { TCertificateAuthorityCrlServiceFactory } from "./certificate-authority-crl-types";
type TCertificateAuthorityCrlServiceFactoryDep = {
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
@ -22,19 +22,17 @@ type TCertificateAuthorityCrlServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TCertificateAuthorityCrlServiceFactory = ReturnType<typeof certificateAuthorityCrlServiceFactory>;
export const certificateAuthorityCrlServiceFactory = ({
certificateAuthorityDAL,
certificateAuthorityCrlDAL,
projectDAL,
kmsService,
permissionService // licenseService
}: TCertificateAuthorityCrlServiceFactoryDep) => {
}: TCertificateAuthorityCrlServiceFactoryDep): TCertificateAuthorityCrlServiceFactory => {
/**
* Return CRL with id [crlId]
*/
const getCrlById = async (crlId: TGetCrlById) => {
const getCrlById: TCertificateAuthorityCrlServiceFactory["getCrlById"] = async (crlId) => {
const caCrl = await certificateAuthorityCrlDAL.findById(crlId);
if (!caCrl) throw new NotFoundError({ message: `CRL with ID '${crlId}' not found` });
@ -65,7 +63,13 @@ export const certificateAuthorityCrlServiceFactory = ({
/**
* Returns a list of CRL ids for CA with id [caId]
*/
const getCaCrls = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => {
const getCaCrls: TCertificateAuthorityCrlServiceFactory["getCaCrls"] = async ({
caId,
actorId,
actorAuthMethod,
actor,
actorOrgId
}) => {
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
if (!ca?.internalCa?.id) throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` });

View File

@ -5,3 +5,137 @@ export type TGetCrlById = string;
export type TGetCaCrlsDTO = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TCertificateAuthorityCrlServiceFactory = {
getCrlById: (crlId: TGetCrlById) => Promise<{
ca: {
readonly requireTemplateForIssuance: boolean;
readonly internalCa:
| {
id: string;
parentCaId: string | null | undefined;
type: string;
friendlyName: string;
organization: string;
ou: string;
country: string;
province: string;
locality: string;
commonName: string;
dn: string;
serialNumber: string | null | undefined;
maxPathLength: number | null | undefined;
keyAlgorithm: string;
notBefore: string | undefined;
notAfter: string | undefined;
activeCaCertId: string | null | undefined;
}
| undefined;
readonly externalCa:
| {
id: string;
type: string;
configuration: unknown;
dnsAppConnectionId: string | null | undefined;
appConnectionId: string | null | undefined;
credentials: Buffer | null | undefined;
}
| undefined;
readonly name: string;
readonly status: string;
readonly id: string;
readonly createdAt: Date;
readonly updatedAt: Date;
readonly projectId: string;
readonly enableDirectIssuance: boolean;
readonly parentCaId: string | null | undefined;
readonly type: string;
readonly friendlyName: string;
readonly organization: string;
readonly ou: string;
readonly country: string;
readonly province: string;
readonly locality: string;
readonly commonName: string;
readonly dn: string;
readonly serialNumber: string | null | undefined;
readonly maxPathLength: number | null | undefined;
readonly keyAlgorithm: string;
readonly notBefore: string | undefined;
readonly notAfter: string | undefined;
readonly activeCaCertId: string | null | undefined;
};
caCrl: {
id: string;
createdAt: Date;
updatedAt: Date;
caId: string;
caSecretId: string;
encryptedCrl: Buffer;
};
crl: ArrayBuffer;
}>;
getCaCrls: ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => Promise<{
ca: {
readonly requireTemplateForIssuance: boolean;
readonly internalCa:
| {
id: string;
parentCaId: string | null | undefined;
type: string;
friendlyName: string;
organization: string;
ou: string;
country: string;
province: string;
locality: string;
commonName: string;
dn: string;
serialNumber: string | null | undefined;
maxPathLength: number | null | undefined;
keyAlgorithm: string;
notBefore: string | undefined;
notAfter: string | undefined;
activeCaCertId: string | null | undefined;
}
| undefined;
readonly externalCa:
| {
id: string;
type: string;
configuration: unknown;
dnsAppConnectionId: string | null | undefined;
appConnectionId: string | null | undefined;
credentials: Buffer | null | undefined;
}
| undefined;
readonly name: string;
readonly status: string;
readonly id: string;
readonly createdAt: Date;
readonly updatedAt: Date;
readonly projectId: string;
readonly enableDirectIssuance: boolean;
readonly parentCaId: string | null | undefined;
readonly type: string;
readonly friendlyName: string;
readonly organization: string;
readonly ou: string;
readonly country: string;
readonly province: string;
readonly locality: string;
readonly commonName: string;
readonly dn: string;
readonly serialNumber: string | null | undefined;
readonly maxPathLength: number | null | undefined;
readonly keyAlgorithm: string;
readonly notBefore: string | undefined;
readonly notAfter: string | undefined;
readonly activeCaCertId: string | null | undefined;
};
crls: {
id: string;
crl: string;
}[];
}>;
};

View File

@ -3,7 +3,7 @@ import RE2 from "re2";
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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub
@ -264,7 +264,10 @@ export const dynamicSecretLeaseServiceFactory = ({
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
if (maxTTL) {
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max ttl" });
if (expireAt > maxExpiryDate)
throw new BadRequestError({
message: "The requested renewal would exceed the maximum allowed lease duration. Please choose a shorter TTL"
});
}
const { entityId } = await selectedProvider.renew(

View File

@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub

View File

@ -128,11 +128,21 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
const username = generateUsername(usernameTemplate, identity);
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }];
if (providerInputs.tags && Array.isArray(providerInputs.tags)) {
const additionalTags = providerInputs.tags.map((tag) => ({
Key: tag.key,
Value: tag.value
}));
awsTags.push(...additionalTags);
}
const createUserRes = await client.send(
new CreateUserCommand({
Path: awsPath,
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }],
Tags: awsTags,
UserName: username
})
);

View File

@ -0,0 +1,105 @@
import { gaxios, Impersonated, JWT } from "google-auth-library";
import { GetAccessTokenResponse } from "google-auth-library/build/src/auth/oauth2client";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretGcpIamSchema, TDynamicProviderFns } from "./models";
export const GcpIamProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretGcpIamSchema.parseAsync(inputs);
return providerInputs;
};
const $getToken = async (serviceAccountEmail: string, ttl: number): Promise<string> => {
const appCfg = getConfig();
if (!appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) {
throw new InternalServerError({
message: "Environment variable has not been configured: INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL"
});
}
const credJson = JSON.parse(appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) as {
client_email: string;
private_key: string;
};
const sourceClient = new JWT({
email: credJson.client_email,
key: credJson.private_key,
scopes: ["https://www.googleapis.com/auth/cloud-platform"]
});
const impersonatedCredentials = new Impersonated({
sourceClient,
targetPrincipal: serviceAccountEmail,
lifetime: ttl,
delegates: [],
targetScopes: ["https://www.googleapis.com/auth/iam", "https://www.googleapis.com/auth/cloud-platform"]
});
let tokenResponse: GetAccessTokenResponse | undefined;
try {
tokenResponse = await impersonatedCredentials.getAccessToken();
} catch (error) {
let message = "Unable to validate connection";
if (error instanceof gaxios.GaxiosError) {
message = error.message;
}
throw new BadRequestError({
message
});
}
if (!tokenResponse || !tokenResponse.token) {
throw new BadRequestError({
message: "Unable to validate connection"
});
}
return tokenResponse.token;
};
const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs);
await $getToken(providerInputs.serviceAccountEmail, 10);
return true;
};
const create = async (data: { inputs: unknown; expireAt: number }) => {
const { inputs, expireAt } = data;
const providerInputs = await validateProviderInputs(inputs);
const now = Math.floor(Date.now() / 1000);
const ttl = Math.max(Math.floor(expireAt / 1000) - now, 0);
const token = await $getToken(providerInputs.serviceAccountEmail, ttl);
const entityId = alphaNumericNanoId(32);
return { entityId, data: { SERVICE_ACCOUNT_EMAIL: providerInputs.serviceAccountEmail, TOKEN: token } };
};
const revoke = async (_inputs: unknown, entityId: string) => {
// There's no way to revoke GCP IAM access tokens
return { entityId };
};
const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
// To renew a token it must be re-created
const data = await create({ inputs, expireAt });
return { ...data, entityId };
};
return {
validateProviderInputs,
validateConnection,
create,
revoke,
renew
};
};

View File

@ -0,0 +1,133 @@
import axios from "axios";
import * as jwt from "jsonwebtoken";
import { BadRequestError, InternalServerError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
import { DynamicSecretGithubSchema, TDynamicProviderFns } from "./models";
interface GitHubInstallationTokenResponse {
token: string;
expires_at: string; // ISO 8601 timestamp e.g., "2024-01-15T12:00:00Z"
permissions?: Record<string, string>;
repository_selection?: string;
}
interface TGithubProviderInputs {
appId: number;
installationId: number;
privateKey: string;
}
export const GithubProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretGithubSchema.parseAsync(inputs);
return providerInputs;
};
const $generateGitHubInstallationAccessToken = async (
credentials: TGithubProviderInputs
): Promise<GitHubInstallationTokenResponse> => {
const { appId, installationId, privateKey } = credentials;
const nowInSeconds = Math.floor(Date.now() / 1000);
const jwtPayload = {
iat: nowInSeconds - 5,
exp: nowInSeconds + 60,
iss: String(appId)
};
let appJwt: string;
try {
appJwt = jwt.sign(jwtPayload, privateKey, { algorithm: "RS256" });
} catch (error) {
let message = "Failed to sign JWT.";
if (error instanceof jwt.JsonWebTokenError) {
message += ` JsonWebTokenError: ${error.message}`;
}
throw new InternalServerError({
message
});
}
const tokenUrl = `${IntegrationUrls.GITHUB_API_URL}/app/installations/${String(installationId)}/access_tokens`;
try {
const response = await axios.post<GitHubInstallationTokenResponse>(tokenUrl, undefined, {
headers: {
Authorization: `Bearer ${appJwt}`,
Accept: "application/vnd.github.v3+json",
"X-GitHub-Api-Version": "2022-11-28"
}
});
if (response.status === 201 && response.data.token) {
return response.data; // Includes token, expires_at, permissions, repository_selection
}
throw new InternalServerError({
message: `GitHub API responded with unexpected status ${response.status}: ${JSON.stringify(response.data)}`
});
} catch (error) {
let message = "Failed to fetch GitHub installation access token.";
if (axios.isAxiosError(error) && error.response) {
const githubErrorMsg =
(error.response.data as { message?: string })?.message || JSON.stringify(error.response.data);
message += ` GitHub API Error: ${error.response.status} - ${githubErrorMsg}`;
// Classify as BadRequestError for auth-related issues (401, 403, 404) which might be due to user input
if ([401, 403, 404].includes(error.response.status)) {
throw new BadRequestError({ message });
}
}
throw new InternalServerError({ message });
}
};
const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs);
await $generateGitHubInstallationAccessToken(providerInputs);
return true;
};
const create = async (data: { inputs: unknown }) => {
const { inputs } = data;
const providerInputs = await validateProviderInputs(inputs);
const ghTokenData = await $generateGitHubInstallationAccessToken(providerInputs);
const entityId = alphaNumericNanoId(32);
return {
entityId,
data: {
TOKEN: ghTokenData.token,
EXPIRES_AT: ghTokenData.expires_at,
PERMISSIONS: ghTokenData.permissions,
REPOSITORY_SELECTION: ghTokenData.repository_selection
}
};
};
const revoke = async () => {
// GitHub installation tokens cannot be revoked.
throw new BadRequestError({
message:
"Github dynamic secret does not support revocation because GitHub itself cannot revoke installation tokens"
});
};
const renew = async () => {
// No renewal
throw new BadRequestError({ message: "Github dynamic secret does not support renewal" });
};
return {
validateProviderInputs,
validateConnection,
create,
revoke,
renew
};
};

View File

@ -6,6 +6,8 @@ import { AwsIamProvider } from "./aws-iam";
import { AzureEntraIDProvider } from "./azure-entra-id";
import { CassandraProvider } from "./cassandra";
import { ElasticSearchProvider } from "./elastic-search";
import { GcpIamProvider } from "./gcp-iam";
import { GithubProvider } from "./github";
import { KubernetesProvider } from "./kubernetes";
import { LdapProvider } from "./ldap";
import { DynamicSecretProviders, TDynamicProviderFns } from "./models";
@ -42,5 +44,7 @@ export const buildDynamicSecretProviders = ({
[DynamicSecretProviders.Totp]: TotpProvider(),
[DynamicSecretProviders.SapAse]: SapAseProvider(),
[DynamicSecretProviders.Kubernetes]: KubernetesProvider({ gatewayService }),
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService })
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService }),
[DynamicSecretProviders.GcpIam]: GcpIamProvider(),
[DynamicSecretProviders.Github]: GithubProvider()
});

View File

@ -2,6 +2,7 @@ import RE2 from "re2";
import { z } from "zod";
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
@ -207,7 +208,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: z.string().trim().optional(),
policyArns: z.string().trim().optional()
policyArns: z.string().trim().optional(),
tags: ResourceMetadataSchema.optional()
}),
z.object({
method: z.literal(AwsIamAuthType.AssumeRole),
@ -217,7 +219,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: z.string().trim().optional(),
policyArns: z.string().trim().optional()
policyArns: z.string().trim().optional(),
tags: ResourceMetadataSchema.optional()
})
])
);
@ -470,6 +473,27 @@ export const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [
})
]);
export const DynamicSecretGcpIamSchema = z.object({
serviceAccountEmail: z.string().email().trim().min(1, "Service account email required").max(128)
});
export const DynamicSecretGithubSchema = z.object({
appId: z.number().min(1).describe("The ID of your GitHub App."),
installationId: z.number().min(1).describe("The ID of the GitHub App installation."),
privateKey: z
.string()
.trim()
.min(1)
.refine(
(val) =>
new RE2(
/^-----BEGIN(?:(?: RSA| PGP| ENCRYPTED)? PRIVATE KEY)-----\s*[\s\S]*?-----END(?:(?: RSA| PGP| ENCRYPTED)? PRIVATE KEY)-----$/
).test(val),
"Invalid PEM format for private key"
)
.describe("The private key generated for your GitHub App.")
});
export enum DynamicSecretProviders {
SqlDatabase = "sql-database",
Cassandra = "cassandra",
@ -487,7 +511,9 @@ export enum DynamicSecretProviders {
Totp = "totp",
SapAse = "sap-ase",
Kubernetes = "kubernetes",
Vertica = "vertica"
Vertica = "vertica",
GcpIam = "gcp-iam",
Github = "github"
}
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
@ -507,7 +533,9 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Kubernetes), inputs: DynamicSecretKubernetesSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Vertica), inputs: DynamicSecretVerticaSchema })
z.object({ type: z.literal(DynamicSecretProviders.Vertica), inputs: DynamicSecretVerticaSchema }),
z.object({ type: z.literal(DynamicSecretProviders.GcpIam), inputs: DynamicSecretGcpIamSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Github), inputs: DynamicSecretGithubSchema })
]);
export type TDynamicProviderFns = {

View File

@ -11,7 +11,7 @@ import { KmsDataKey, KmsKeyUsage } from "@app/services/kms/kms-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TExternalKmsDALFactory } from "./external-kms-dal";
import {
TCreateExternalKmsDTO,

View File

@ -21,7 +21,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TGatewayDALFactory } from "./gateway-dal";
import {
TExchangeAllocatedRelayAddressDTO,

View File

@ -14,7 +14,7 @@ import { TGroupDALFactory } from "../group/group-dal";
import { TUserGroupMembershipDALFactory } from "../group/user-group-membership-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal";
import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types";

View File

@ -169,11 +169,29 @@ export const groupDALFactory = (db: TDbClient) => {
}
};
const findById = async (id: string, tx?: Knex) => {
try {
const doc = await (tx || db.replicaNode())(TableName.Groups)
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
.where(`${TableName.Groups}.id`, id)
.select(
selectAllTableCols(TableName.Groups),
db.ref("slug").as("customRoleSlug").withSchema(TableName.OrgRoles)
)
.first();
return doc;
} catch (error) {
throw new DatabaseError({ error, name: "Find by id" });
}
};
return {
...groupOrm,
findGroups,
findByOrgId,
findAllGroupPossibleMembers,
findGroupsByProjectId,
...groupOrm
findById
};
};

View File

@ -15,7 +15,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionGroupActions, OrgPermissionSubjects } from "../permission/org-permission";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TGroupDALFactory } from "./group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "./group-fns";
import {

View File

@ -11,7 +11,7 @@ import { TIdentityProjectDALFactory } from "@app/services/identity-project/ident
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "../permission/project-permission";
import { TIdentityProjectAdditionalPrivilegeV2DALFactory } from "./identity-project-additional-privilege-v2-dal";
import {

View File

@ -11,7 +11,7 @@ import { TIdentityProjectDALFactory } from "@app/services/identity-project/ident
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import {
ProjectPermissionIdentityActions,
ProjectPermissionSet,

View File

@ -7,7 +7,7 @@ import { KmsKeyUsage } from "@app/services/kms/kms-types";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TKmipClientDALFactory } from "./kmip-client-dal";
import { KmipPermission } from "./kmip-enum";
import {

View File

@ -18,7 +18,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "../permission/project-permission";
import { TKmipClientCertificateDALFactory } from "./kmip-client-certificate-dal";
import { TKmipClientDALFactory } from "./kmip-client-dal";

View File

@ -29,7 +29,7 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TLdapConfigDALFactory } from "./ldap-config-dal";
import {
TCreateLdapCfgDTO,

View File

@ -18,7 +18,7 @@ import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { OrgPermissionBillingActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
import { TLicenseDALFactory } from "./license-dal";
import { getDefaultOnPremFeatures, setupLicenseRequestWithStore } from "./license-fns";

View File

@ -5,14 +5,13 @@ import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet }
import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
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";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
@ -699,9 +698,9 @@ export const oidcConfigServiceFactory = ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(_req: any, tokenSet: TokenSet, cb: any) => {
const claims = tokenSet.claims();
if (!claims.email || !claims.given_name) {
if (!claims.email) {
throw new BadRequestError({
message: "Invalid request. Missing email or first name"
message: "Invalid request. Missing email claim."
});
}
@ -714,12 +713,19 @@ export const oidcConfigServiceFactory = ({
}
}
const name = claims?.given_name || claims?.name;
if (!name) {
throw new BadRequestError({
message: "Invalid request. Missing name claim."
});
}
const groups = typeof claims.groups === "string" ? [claims.groups] : (claims.groups as string[] | undefined);
oidcLogin({
email: claims.email.toLowerCase(),
externalId: claims.sub,
firstName: claims.given_name ?? "",
firstName: name,
lastName: claims.family_name ?? "",
orgId: org.id,
groups,

View File

@ -6,16 +6,312 @@ import {
OrgMembershipRole,
OrgMembershipsSchema,
TableName,
TIdentityOrgMemberships,
TProjectRoles,
TProjects
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
export type TPermissionDALFactory = ReturnType<typeof permissionDALFactory>;
export interface TPermissionDALFactory {
getOrgPermission: (
userId: string,
orgId: string
) => Promise<
{
status: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
role: string;
isActive: boolean;
shouldUseNewPrivilegeSystem: boolean;
bypassOrgAuthEnabled: boolean;
permissions?: unknown;
userId?: string | null | undefined;
roleId?: string | null | undefined;
inviteEmail?: string | null | undefined;
projectFavorites?: string[] | null | undefined;
customRoleSlug?: string | null | undefined;
orgAuthEnforced?: boolean | null | undefined;
} & {
groups: {
id: string;
updatedAt: Date;
createdAt: Date;
role: string;
roleId: string | null | undefined;
customRolePermission: unknown;
name: string;
slug: string;
orgId: string;
}[];
}
>;
getOrgIdentityPermission: (
identityId: string,
orgId: string
) => Promise<
| (TIdentityOrgMemberships & {
orgAuthEnforced: boolean | null | undefined;
shouldUseNewPrivilegeSystem: boolean;
permissions?: unknown;
})
| undefined
>;
getProjectPermission: (
userId: string,
projectId: string
) => Promise<
| {
roles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
additionalPrivileges: {
id: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
orgId: string;
orgAuthEnforced: boolean | null | undefined;
orgRole: OrgMembershipRole;
userId: string;
projectId: string;
username: string;
projectType: string;
id: string;
createdAt: Date;
updatedAt: Date;
shouldUseNewPrivilegeSystem: boolean;
bypassOrgAuthEnabled: boolean;
metadata: {
id: string;
key: string;
value: string;
}[];
userGroupRoles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
projecMembershiptRoles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
}
| undefined
>;
getProjectIdentityPermission: (
identityId: string,
projectId: string
) => Promise<
| {
roles: {
id: string;
createdAt: Date;
updatedAt: Date;
isTemporary: boolean;
role: string;
projectMembershipId: string;
temporaryRange?: string | null | undefined;
permissions?: unknown;
customRoleId?: string | null | undefined;
temporaryMode?: string | null | undefined;
temporaryAccessStartTime?: Date | null | undefined;
temporaryAccessEndTime?: Date | null | undefined;
customRoleSlug?: string | null | undefined;
}[];
additionalPrivileges: {
id: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
isTemporary: boolean;
}[];
id: string;
identityId: string;
username: string;
projectId: string;
createdAt: Date;
updatedAt: Date;
orgId: string;
projectType: string;
shouldUseNewPrivilegeSystem: boolean;
orgAuthEnforced: boolean;
metadata: {
id: string;
key: string;
value: string;
}[];
}
| undefined
>;
getProjectUserPermissions: (projectId: string) => Promise<
{
roles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
additionalPrivileges: {
id: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
orgId: string;
orgAuthEnforced: boolean | null | undefined;
userId: string;
projectId: string;
username: string;
projectType: string;
id: string;
createdAt: Date;
updatedAt: Date;
metadata: {
id: string;
key: string;
value: string;
}[];
userGroupRoles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
projectMembershipRoles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
}[]
>;
getProjectIdentityPermissions: (projectId: string) => Promise<
{
roles: {
id: string;
createdAt: Date;
updatedAt: Date;
isTemporary: boolean;
role: string;
projectMembershipId: string;
temporaryRange?: string | null | undefined;
permissions?: unknown;
customRoleId?: string | null | undefined;
temporaryMode?: string | null | undefined;
temporaryAccessStartTime?: Date | null | undefined;
temporaryAccessEndTime?: Date | null | undefined;
customRoleSlug?: string | null | undefined;
}[];
additionalPrivileges: {
id: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
isTemporary: boolean;
}[];
id: string;
identityId: string;
username: string;
projectId: string;
createdAt: Date;
updatedAt: Date;
orgId: string;
projectType: string;
orgAuthEnforced: boolean;
metadata: {
id: string;
key: string;
value: string;
}[];
}[]
>;
getProjectGroupPermissions: (
projectId: string,
filterGroupId?: string
) => Promise<
{
roles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
groupId: string;
username: string;
id: string;
groupRoles: {
id: string;
role: string;
customRoleSlug: string;
permissions: unknown;
temporaryRange: string | null | undefined;
temporaryMode: string | null | undefined;
temporaryAccessStartTime: Date | null | undefined;
temporaryAccessEndTime: Date | null | undefined;
isTemporary: boolean;
}[];
}[]
>;
}
export const permissionDALFactory = (db: TDbClient) => {
const getOrgPermission = async (userId: string, orgId: string) => {
export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
const getOrgPermission: TPermissionDALFactory["getOrgPermission"] = async (userId: string, orgId: string) => {
try {
const groupSubQuery = db(TableName.Groups)
.where(`${TableName.Groups}.orgId`, orgId)
@ -112,7 +408,10 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getOrgIdentityPermission = async (identityId: string, orgId: string) => {
const getOrgIdentityPermission: TPermissionDALFactory["getOrgIdentityPermission"] = async (
identityId: string,
orgId: string
) => {
try {
const membership = await db
.replicaNode()(TableName.IdentityOrgMembership)
@ -132,7 +431,10 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectGroupPermissions = async (projectId: string, filterGroupId?: string) => {
const getProjectGroupPermissions: TPermissionDALFactory["getProjectGroupPermissions"] = async (
projectId: string,
filterGroupId?: string
) => {
try {
const docs = await db
.replicaNode()(TableName.GroupProjectMembership)
@ -245,7 +547,7 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectUserPermissions = async (projectId: string) => {
const getProjectUserPermissions: TPermissionDALFactory["getProjectUserPermissions"] = async (projectId: string) => {
try {
const docs = await db
.replicaNode()(TableName.Users)
@ -535,7 +837,10 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectPermission = async (userId: string, projectId: string) => {
const getProjectPermission: TPermissionDALFactory["getProjectPermission"] = async (
userId: string,
projectId: string
) => {
try {
const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId");
const docs = await db
@ -838,7 +1143,9 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectIdentityPermissions = async (projectId: string) => {
const getProjectIdentityPermissions: TPermissionDALFactory["getProjectIdentityPermissions"] = async (
projectId: string
) => {
try {
const docs = await db
.replicaNode()(TableName.IdentityProjectMembership)
@ -995,7 +1302,10 @@ export const permissionDALFactory = (db: TDbClient) => {
}
};
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
const getProjectIdentityPermission: TPermissionDALFactory["getProjectIdentityPermission"] = async (
identityId,
projectId
) => {
try {
const docs = await db
.replicaNode()(TableName.IdentityProjectMembership)

View File

@ -1,6 +1,12 @@
import { MongoAbility, RawRuleOf } from "@casl/ability";
import { MongoQuery } from "@ucast/mongo2js";
import { ActionProjectType } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { OrgPermissionSet } from "./org-permission";
import { ProjectPermissionSet } from "./project-permission";
export type TBuildProjectPermissionDTO = {
permissions?: unknown;
role: string;
@ -41,3 +47,240 @@ export type TGetProjectPermissionArg = {
actorOrgId?: string;
actionProjectType: ActionProjectType;
};
export type TPermissionServiceFactory = {
getUserOrgPermission: (
userId: string,
orgId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
) => Promise<{
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
membership: {
status: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
role: string;
isActive: boolean;
shouldUseNewPrivilegeSystem: boolean;
bypassOrgAuthEnabled: boolean;
permissions?: unknown;
userId?: string | null | undefined;
roleId?: string | null | undefined;
inviteEmail?: string | null | undefined;
projectFavorites?: string[] | null | undefined;
customRoleSlug?: string | null | undefined;
orgAuthEnforced?: boolean | null | undefined;
} & {
groups: {
id: string;
updatedAt: Date;
createdAt: Date;
role: string;
roleId: string | null | undefined;
customRolePermission: unknown;
name: string;
slug: string;
orgId: string;
}[];
};
}>;
getOrgPermission: (
type: ActorType,
id: string,
orgId: string,
authMethod: ActorAuthMethod,
actorOrgId: string | undefined
) => Promise<
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
membership: {
status: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
role: string;
isActive: boolean;
shouldUseNewPrivilegeSystem: boolean;
bypassOrgAuthEnabled: boolean;
permissions?: unknown;
userId?: string | null | undefined;
roleId?: string | null | undefined;
inviteEmail?: string | null | undefined;
projectFavorites?: string[] | null | undefined;
customRoleSlug?: string | null | undefined;
orgAuthEnforced?: boolean | null | undefined;
} & {
groups: {
id: string;
updatedAt: Date;
createdAt: Date;
role: string;
roleId: string | null | undefined;
customRolePermission: unknown;
name: string;
slug: string;
orgId: string;
}[];
};
}
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
membership: {
id: string;
role: string;
createdAt: Date;
updatedAt: Date;
orgId: string;
roleId?: string | null | undefined;
permissions?: unknown;
identityId: string;
orgAuthEnforced: boolean | null | undefined;
shouldUseNewPrivilegeSystem: boolean;
};
}
>;
getUserProjectPermission: ({
userId,
projectId,
authMethod,
userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg) => Promise<{
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
projectId: string;
} & {
orgAuthEnforced: boolean | null | undefined;
orgId: string;
roles: Array<{
role: string;
}>;
shouldUseNewPrivilegeSystem: boolean;
};
hasRole: (role: string) => boolean;
}>;
getProjectPermission: <T extends ActorType>(
arg: TGetProjectPermissionArg
) => Promise<
T extends ActorType.SERVICE
? {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: {
shouldUseNewPrivilegeSystem: boolean;
};
hasRole: (arg: string) => boolean;
}
: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: (T extends ActorType.USER
? {
id: string;
createdAt: Date;
updatedAt: Date;
userId: string;
projectId: string;
}
: {
id: string;
createdAt: Date;
updatedAt: Date;
projectId: string;
identityId: string;
}) & {
orgAuthEnforced: boolean | null | undefined;
orgId: string;
roles: Array<{
role: string;
}>;
shouldUseNewPrivilegeSystem: boolean;
};
hasRole: (role: string) => boolean;
}
>;
getProjectPermissions: (projectId: string) => Promise<{
userPermissions: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
id: string;
name: string;
membershipId: string;
}[];
identityPermissions: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
id: string;
name: string;
membershipId: string;
}[];
groupPermissions: {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
id: string;
name: string;
membershipId: string;
}[];
}>;
getOrgPermissionByRole: (
role: string,
orgId: string
) => Promise<
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
role: {
name: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
slug: string;
permissions?: unknown;
description?: string | null | undefined;
};
}
| {
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
role?: undefined;
}
>;
getProjectPermissionByRole: (
role: string,
projectId: string
) => Promise<
| {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
role: {
name: string;
version: number;
id: string;
createdAt: Date;
updatedAt: Date;
projectId: string;
slug: string;
permissions?: unknown;
description?: string | null | undefined;
};
}
| {
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
role?: undefined;
}
>;
buildOrgPermission: (orgUserRoles: TBuildOrgPermissionDTO) => MongoAbility<OrgPermissionSet, MongoQuery>;
buildProjectPermissionRules: (
projectUserRoles: TBuildProjectPermissionDTO
) => RawRuleOf<MongoAbility<ProjectPermissionSet>>[];
checkGroupProjectPermission: ({
groupId,
projectId,
checkPermissions
}: {
groupId: string;
projectId: string;
checkPermissions: ProjectPermissionSet;
}) => Promise<boolean>;
};

View File

@ -23,7 +23,7 @@ import {
import { conditionsMatcher } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { objectify } from "@app/lib/fn";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { ActorType } from "@app/services/auth/auth-type";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
@ -38,7 +38,8 @@ import {
TGetIdentityProjectPermissionArg,
TGetProjectPermissionArg,
TGetServiceTokenProjectPermissionArg,
TGetUserProjectPermissionArg
TGetUserProjectPermissionArg,
TPermissionServiceFactory
} from "./permission-service-types";
import { buildServiceTokenProjectPermission, ProjectPermissionSet } from "./project-permission";
@ -50,15 +51,13 @@ type TPermissionServiceFactoryDep = {
permissionDAL: TPermissionDALFactory;
};
export type TPermissionServiceFactory = ReturnType<typeof permissionServiceFactory>;
export const permissionServiceFactory = ({
permissionDAL,
orgRoleDAL,
projectRoleDAL,
serviceTokenDAL,
projectDAL
}: TPermissionServiceFactoryDep) => {
}: TPermissionServiceFactoryDep): TPermissionServiceFactory => {
const buildOrgPermission = (orgUserRoles: TBuildOrgPermissionDTO) => {
const rules = orgUserRoles
.map(({ role, permissions }) => {
@ -120,11 +119,11 @@ export const permissionServiceFactory = ({
/*
* Get user permission in an organization
*/
const getUserOrgPermission = async (
userId: string,
orgId: string,
authMethod: ActorAuthMethod,
userOrgId?: string
const getUserOrgPermission: TPermissionServiceFactory["getUserOrgPermission"] = async (
userId,
orgId,
authMethod,
userOrgId
) => {
// when token is scoped, ensure the passed org id is same as user org id
if (userOrgId && userOrgId !== orgId)
@ -172,12 +171,12 @@ export const permissionServiceFactory = ({
};
};
const getOrgPermission = async (
type: ActorType,
id: string,
orgId: string,
authMethod: ActorAuthMethod,
actorOrgId: string | undefined
const getOrgPermission: TPermissionServiceFactory["getOrgPermission"] = async (
type,
id,
orgId,
authMethod,
actorOrgId
) => {
switch (type) {
case ActorType.USER:
@ -194,7 +193,7 @@ export const permissionServiceFactory = ({
// instead of actor type this will fetch by role slug. meaning it can be the pre defined slugs like
// admin member or user defined ones like biller etc
const getOrgPermissionByRole = async (role: string, orgId: string) => {
const getOrgPermissionByRole: TPermissionServiceFactory["getOrgPermissionByRole"] = async (role, orgId) => {
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
if (isCustomRole) {
const orgRole = await orgRoleDAL.findOne({ slug: role, orgId });
@ -437,7 +436,7 @@ export const permissionServiceFactory = ({
hasRole: (role: string) => boolean;
};
const getProjectPermissions = async (projectId: string) => {
const getProjectPermissions: TPermissionServiceFactory["getProjectPermissions"] = async (projectId) => {
// fetch user permissions
const rawUserProjectPermissions = await permissionDAL.getProjectUserPermissions(projectId);
const userPermissions = rawUserProjectPermissions.map((userProjectPermission) => {
@ -607,7 +606,10 @@ export const permissionServiceFactory = ({
}
};
const getProjectPermissionByRole = async (role: string, projectId: string) => {
const getProjectPermissionByRole: TPermissionServiceFactory["getProjectPermissionByRole"] = async (
role,
projectId
) => {
const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole);
if (isCustomRole) {
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
@ -630,14 +632,10 @@ export const permissionServiceFactory = ({
return { permission };
};
const checkGroupProjectPermission = async ({
const checkGroupProjectPermission: TPermissionServiceFactory["checkGroupProjectPermission"] = async ({
groupId,
projectId,
checkPermissions
}: {
groupId: string;
projectId: string;
checkPermissions: ProjectPermissionSet;
}) => {
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId, groupId);
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {

View File

@ -211,6 +211,11 @@ export type SecretFolderSubjectFields = {
secretPath: string;
};
export type SecretSyncSubjectFields = {
environment: string;
secretPath: string;
};
export type DynamicSecretSubjectFields = {
environment: string;
secretPath: string;
@ -267,6 +272,10 @@ export type ProjectPermissionSet =
| (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields)
)
]
| [
ProjectPermissionSecretSyncActions,
ProjectPermissionSub.SecretSyncs | (ForcedSubject<ProjectPermissionSub.SecretSyncs> & SecretSyncSubjectFields)
]
| [
ProjectPermissionActions,
(
@ -323,7 +332,6 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
| [ProjectPermissionKmipActions, ProjectPermissionSub.Kmip]
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
@ -412,6 +420,23 @@ const DynamicSecretConditionV2Schema = z
})
.partial();
const SecretSyncConditionV2Schema = z
.object({
environment: z.union([
z.string(),
z
.object({
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
})
.partial()
]),
secretPath: SECRET_PATH_PERMISSION_OPERATOR_SCHEMA
})
.partial();
const SecretImportConditionSchema = z
.object({
environment: z.union([
@ -671,12 +696,6 @@ const GeneralPermissionSchema = [
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.SecretSyncs).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretSyncActions).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.Kmip).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionKmipActions).describe(
@ -836,6 +855,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
z.object({
subject: z.literal(ProjectPermissionSub.SecretSyncs).describe("The entity this permission pertains to."),
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretSyncActions).describe(
"Describe what action an entity can take."
),
conditions: SecretSyncConditionV2Schema.describe(
"When specified, only matching conditions will be allowed to access given resource."
).optional()
}),
...GeneralPermissionSchema
]);

View File

@ -16,7 +16,7 @@ import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
type TPitServiceFactoryDep = {
folderCommitService: TFolderCommitServiceFactory;

View File

@ -1,7 +1,8 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TProjectTemplateDALFactory = ReturnType<typeof projectTemplateDALFactory>;
export type TProjectTemplateDALFactory = TOrmify<TableName.ProjectTemplates>;
export const projectTemplateDALFactory = (db: TDbClient) => ormify(db, TableName.ProjectTemplates);
export const projectTemplateDALFactory = (db: TDbClient): TProjectTemplateDALFactory =>
ormify(db, TableName.ProjectTemplates);

View File

@ -4,18 +4,16 @@ import { packRules } from "@casl/ability/extra";
import { ProjectType, TProjectTemplates } 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";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
import {
TCreateProjectTemplateDTO,
TProjectTemplateEnvironment,
TProjectTemplateRole,
TUnpackedPermission,
TUpdateProjectTemplateDTO
TProjectTemplateServiceFactory,
TUnpackedPermission
} from "@app/ee/services/project-template/project-template-types";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { OrgServiceActor } from "@app/lib/types";
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
@ -27,8 +25,6 @@ type TProjectTemplatesServiceFactoryDep = {
projectTemplateDAL: TProjectTemplateDALFactory;
};
export type TProjectTemplateServiceFactory = ReturnType<typeof projectTemplateServiceFactory>;
const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTemplates) => ({
...rest,
environments: environments as TProjectTemplateEnvironment[],
@ -51,8 +47,11 @@ export const projectTemplateServiceFactory = ({
licenseService,
permissionService,
projectTemplateDAL
}: TProjectTemplatesServiceFactoryDep) => {
const listProjectTemplatesByOrg = async (actor: OrgServiceActor, type?: ProjectType) => {
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (
actor,
type
) => {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
@ -83,7 +82,10 @@ export const projectTemplateServiceFactory = ({
];
};
const findProjectTemplateByName = async (name: string, actor: OrgServiceActor) => {
const findProjectTemplateByName: TProjectTemplateServiceFactory["findProjectTemplateByName"] = async (
name,
actor
) => {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
@ -111,7 +113,7 @@ export const projectTemplateServiceFactory = ({
};
};
const findProjectTemplateById = async (id: string, actor: OrgServiceActor) => {
const findProjectTemplateById: TProjectTemplateServiceFactory["findProjectTemplateById"] = async (id, actor) => {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)
@ -139,9 +141,9 @@ export const projectTemplateServiceFactory = ({
};
};
const createProjectTemplate = async (
{ roles, environments, type, ...params }: TCreateProjectTemplateDTO,
actor: OrgServiceActor
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
{ roles, environments, type, ...params },
actor
) => {
const plan = await licenseService.getPlan(actor.orgId);
@ -195,10 +197,10 @@ export const projectTemplateServiceFactory = ({
return $unpackProjectTemplate(projectTemplate);
};
const updateProjectTemplateById = async (
id: string,
{ roles, environments, ...params }: TUpdateProjectTemplateDTO,
actor: OrgServiceActor
const updateProjectTemplateById: TProjectTemplateServiceFactory["updateProjectTemplateById"] = async (
id,
{ roles, environments, ...params },
actor
) => {
const plan = await licenseService.getPlan(actor.orgId);
@ -259,7 +261,7 @@ export const projectTemplateServiceFactory = ({
return $unpackProjectTemplate(updatedProjectTemplate);
};
const deleteProjectTemplateById = async (id: string, actor: OrgServiceActor) => {
const deleteProjectTemplateById: TProjectTemplateServiceFactory["deleteProjectTemplateById"] = async (id, actor) => {
const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates)

View File

@ -1,7 +1,8 @@
import { z } from "zod";
import { ProjectType, TProjectEnvironments } from "@app/db/schemas";
import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { OrgServiceActor } from "@app/lib/types";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
export type TProjectTemplateEnvironment = Pick<TProjectEnvironments, "name" | "slug" | "position">;
@ -27,3 +28,177 @@ export type TUnpackedPermission = z.infer<typeof UnpackedPermissionSchema>;
export enum InfisicalProjectTemplate {
Default = "default"
}
export type TProjectTemplateServiceFactory = {
listProjectTemplatesByOrg: (
actor: OrgServiceActor,
type?: ProjectType
) => Promise<
(
| {
id: string;
type: ProjectType;
name: InfisicalProjectTemplate;
createdAt: Date;
updatedAt: Date;
description: string;
environments:
| {
name: string;
slug: string;
position: number;
}[]
| null;
roles: {
name: string;
slug: ProjectMembershipRole;
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
}[];
orgId: string;
}
| {
environments: TProjectTemplateEnvironment[];
roles: {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
slug: string;
name: string;
}[];
name: string;
type: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
description?: string | null | undefined;
}
)[]
>;
createProjectTemplate: (
arg: TCreateProjectTemplateDTO,
actor: OrgServiceActor
) => Promise<{
environments: TProjectTemplateEnvironment[];
roles: {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
slug: string;
name: string;
}[];
name: string;
type: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
description?: string | null | undefined;
}>;
updateProjectTemplateById: (
id: string,
{ roles, environments, ...params }: TUpdateProjectTemplateDTO,
actor: OrgServiceActor
) => Promise<{
environments: TProjectTemplateEnvironment[];
roles: {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
slug: string;
name: string;
}[];
name: string;
type: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
description?: string | null | undefined;
}>;
deleteProjectTemplateById: (
id: string,
actor: OrgServiceActor
) => Promise<{
environments: TProjectTemplateEnvironment[];
roles: {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
slug: string;
name: string;
}[];
name: string;
type: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
description?: string | null | undefined;
}>;
findProjectTemplateById: (
id: string,
actor: OrgServiceActor
) => Promise<{
packedRoles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[];
roles: {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
slug: string;
name: string;
}[];
name: string;
type: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
description?: string | null | undefined;
}>;
findProjectTemplateByName: (
name: string,
actor: OrgServiceActor
) => Promise<{
packedRoles: TProjectTemplateRole[];
environments: TProjectTemplateEnvironment[];
roles: {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
slug: string;
name: string;
}[];
name: string;
type: string;
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
description?: string | null | undefined;
}>;
};

View File

@ -1,10 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TProjectUserAdditionalPrivilegeDALFactory = ReturnType<typeof projectUserAdditionalPrivilegeDALFactory>;
export type TProjectUserAdditionalPrivilegeDALFactory = TOrmify<TableName.ProjectUserAdditionalPrivilege>;
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient) => {
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient): TProjectUserAdditionalPrivilegeDALFactory => {
const orm = ormify(db, TableName.ProjectUserAdditionalPrivilege);
return orm;
};

View File

@ -11,7 +11,7 @@ import { TProjectMembershipDALFactory } from "@app/services/project-membership/p
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import {
ProjectPermissionMemberActions,
ProjectPermissionSet,
@ -21,11 +21,7 @@ import { ApprovalStatus } from "../secret-approval-request/secret-approval-reque
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
import {
ProjectUserAdditionalPrivilegeTemporaryMode,
TCreateUserPrivilegeDTO,
TDeleteUserPrivilegeDTO,
TGetUserPrivilegeDetailsDTO,
TListUserPrivilegesDTO,
TUpdateUserPrivilegeDTO
TProjectUserAdditionalPrivilegeServiceFactory
} from "./project-user-additional-privilege-types";
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
@ -35,10 +31,6 @@ type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
};
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
typeof projectUserAdditionalPrivilegeServiceFactory
>;
const unpackPermissions = (permissions: unknown) =>
UnpackedPermissionSchema.array().parse(
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
@ -49,8 +41,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
projectMembershipDAL,
permissionService,
accessApprovalRequestDAL
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
const create = async ({
}: TProjectUserAdditionalPrivilegeServiceFactoryDep): TProjectUserAdditionalPrivilegeServiceFactory => {
const create: TProjectUserAdditionalPrivilegeServiceFactory["create"] = async ({
slug,
actor,
actorId,
@ -59,7 +51,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorAuthMethod,
projectMembershipId,
...dto
}: TCreateUserPrivilegeDTO) => {
}) => {
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
@ -147,14 +139,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
};
};
const updateById = async ({
const updateById: TProjectUserAdditionalPrivilegeServiceFactory["updateById"] = async ({
privilegeId,
actorOrgId,
actor,
actorId,
actorAuthMethod,
...dto
}: TUpdateUserPrivilegeDTO) => {
}) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
@ -259,7 +251,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
};
};
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
const deleteById: TProjectUserAdditionalPrivilegeServiceFactory["deleteById"] = async ({
actorId,
actor,
actorOrgId,
actorAuthMethod,
privilegeId
}) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
@ -299,13 +297,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
};
};
const getPrivilegeDetailsById = async ({
const getPrivilegeDetailsById: TProjectUserAdditionalPrivilegeServiceFactory["getPrivilegeDetailsById"] = async ({
privilegeId,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TGetUserPrivilegeDetailsDTO) => {
}) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
@ -335,13 +333,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
};
};
const listPrivileges = async ({
const listPrivileges: TProjectUserAdditionalPrivilegeServiceFactory["listPrivileges"] = async ({
projectMembershipId,
actorOrgId,
actor,
actorId,
actorAuthMethod
}: TListUserPrivilegesDTO) => {
}) => {
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });

View File

@ -1,3 +1,4 @@
import { TProjectUserAdditionalPrivilege } from "@app/db/schemas";
import { TProjectPermission } from "@app/lib/types";
import { TProjectPermissionV2Schema } from "../permission/project-permission";
@ -40,3 +41,20 @@ export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
interface TAdditionalPrivilege extends TProjectUserAdditionalPrivilege {
permissions: {
action: string[];
subject?: string | undefined;
conditions?: unknown;
inverted?: boolean | undefined;
}[];
}
export type TProjectUserAdditionalPrivilegeServiceFactory = {
create: (arg: TCreateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
updateById: (arg: TUpdateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
deleteById: (arg: TDeleteUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
getPrivilegeDetailsById: (arg: TGetUserPrivilegeDetailsDTO) => Promise<TAdditionalPrivilege>;
listPrivileges: (arg: TListUserPrivilegesDTO) => Promise<TProjectUserAdditionalPrivilege[]>;
};

View File

@ -1,7 +1,7 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TRateLimitDALFactory = ReturnType<typeof rateLimitDALFactory>;
export type TRateLimitDALFactory = TOrmify<TableName.RateLimit>;
export const rateLimitDALFactory = (db: TDbClient) => ormify(db, TableName.RateLimit, {});
export const rateLimitDALFactory = (db: TDbClient): TRateLimitDALFactory => ormify(db, TableName.RateLimit, {});

View File

@ -4,7 +4,7 @@ import { logger } from "@app/lib/logger";
import { TLicenseServiceFactory } from "../license/license-service";
import { TRateLimitDALFactory } from "./rate-limit-dal";
import { RateLimitConfiguration, TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types";
import { RateLimitConfiguration, TRateLimit, TRateLimitServiceFactory } from "./rate-limit-types";
let rateLimitMaxConfiguration: RateLimitConfiguration = {
readLimit: 60,
@ -27,12 +27,13 @@ type TRateLimitServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
};
export type TRateLimitServiceFactory = ReturnType<typeof rateLimitServiceFactory>;
export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateLimitServiceFactoryDep) => {
export const rateLimitServiceFactory = ({
rateLimitDAL,
licenseService
}: TRateLimitServiceFactoryDep): TRateLimitServiceFactory => {
const DEFAULT_RATE_LIMIT_CONFIG_ID = "00000000-0000-0000-0000-000000000000";
const getRateLimits = async (): Promise<TRateLimit | undefined> => {
const getRateLimits: TRateLimitServiceFactory["getRateLimits"] = async () => {
let rateLimit: TRateLimit;
try {
@ -51,11 +52,11 @@ export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateL
}
};
const updateRateLimit = async (updates: TRateLimitUpdateDTO): Promise<TRateLimit> => {
const updateRateLimit: TRateLimitServiceFactory["updateRateLimit"] = async (updates) => {
return rateLimitDAL.updateById(DEFAULT_RATE_LIMIT_CONFIG_ID, updates);
};
const syncRateLimitConfiguration = async () => {
const syncRateLimitConfiguration: TRateLimitServiceFactory["syncRateLimitConfiguration"] = async () => {
try {
const rateLimit = await getRateLimits();
if (rateLimit) {
@ -78,7 +79,7 @@ export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateL
}
};
const initializeBackgroundSync = async () => {
const initializeBackgroundSync: TRateLimitServiceFactory["initializeBackgroundSync"] = async () => {
if (!licenseService.onPremFeatures.customRateLimits) {
logger.info("Current license does not support custom rate limit configuration");
return;

View File

@ -1,3 +1,5 @@
import { CronJob } from "cron";
export type TRateLimitUpdateDTO = {
readRateLimit: number;
writeRateLimit: number;
@ -23,3 +25,10 @@ export type RateLimitConfiguration = {
inviteUserRateLimit: number;
mfaRateLimit: number;
};
export type TRateLimitServiceFactory = {
getRateLimits: () => Promise<TRateLimit | undefined>;
updateRateLimit: (updates: TRateLimitUpdateDTO) => Promise<TRateLimit>;
initializeBackgroundSync: () => Promise<CronJob<null, null> | undefined>;
syncRateLimitConfiguration: () => Promise<void>;
};

View File

@ -1,10 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TSamlConfigDALFactory = ReturnType<typeof samlConfigDALFactory>;
export type TSamlConfigDALFactory = TOrmify<TableName.SamlConfig>;
export const samlConfigDALFactory = (db: TDbClient) => {
export const samlConfigDALFactory = (db: TDbClient): TSamlConfigDALFactory => {
const samlCfgOrm = ormify(db, TableName.SamlConfig);
return samlCfgOrm;

View File

@ -23,9 +23,9 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TSamlConfigDALFactory } from "./saml-config-dal";
import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } from "./saml-config-types";
import { TSamlConfigServiceFactory } from "./saml-config-types";
type TSamlConfigServiceFactoryDep = {
samlConfigDAL: Pick<TSamlConfigDALFactory, "create" | "findOne" | "update" | "findById">;
@ -47,8 +47,6 @@ type TSamlConfigServiceFactoryDep = {
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
};
export type TSamlConfigServiceFactory = ReturnType<typeof samlConfigServiceFactory>;
export const samlConfigServiceFactory = ({
samlConfigDAL,
orgDAL,
@ -61,8 +59,8 @@ export const samlConfigServiceFactory = ({
smtpService,
identityMetadataDAL,
kmsService
}: TSamlConfigServiceFactoryDep) => {
const createSamlCfg = async ({
}: TSamlConfigServiceFactoryDep): TSamlConfigServiceFactory => {
const createSamlCfg: TSamlConfigServiceFactory["createSamlCfg"] = async ({
idpCert,
actor,
actorAuthMethod,
@ -73,7 +71,7 @@ export const samlConfigServiceFactory = ({
isActive,
entryPoint,
authProvider
}: TCreateSamlCfgDTO) => {
}) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Sso);
@ -101,7 +99,7 @@ export const samlConfigServiceFactory = ({
return samlConfig;
};
const updateSamlCfg = async ({
const updateSamlCfg: TSamlConfigServiceFactory["updateSamlCfg"] = async ({
orgId,
actor,
actorOrgId,
@ -112,7 +110,7 @@ export const samlConfigServiceFactory = ({
isActive,
entryPoint,
authProvider
}: TUpdateSamlCfgDTO) => {
}) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
const plan = await licenseService.getPlan(orgId);
@ -146,7 +144,7 @@ export const samlConfigServiceFactory = ({
return ssoConfig;
};
const getSaml = async (dto: TGetSamlCfgDTO) => {
const getSaml: TSamlConfigServiceFactory["getSaml"] = async (dto) => {
let samlConfig: TSamlConfigs | undefined;
if (dto.type === "org") {
samlConfig = await samlConfigDAL.findOne({ orgId: dto.orgId });
@ -221,7 +219,7 @@ export const samlConfigServiceFactory = ({
};
};
const samlLogin = async ({
const samlLogin: TSamlConfigServiceFactory["samlLogin"] = async ({
externalId,
email,
firstName,
@ -230,7 +228,7 @@ export const samlConfigServiceFactory = ({
orgId,
relayState,
metadata
}: TSamlLoginDTO) => {
}) => {
const appCfg = getConfig();
const serverCfg = await getServerCfg();

View File

@ -1,3 +1,4 @@
import { TSamlConfigs } from "@app/db/schemas";
import { TOrgPermission } from "@app/lib/types";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
@ -56,3 +57,26 @@ export type TSamlLoginDTO = {
relayState?: string;
metadata?: { key: string; value: string }[];
};
export type TSamlConfigServiceFactory = {
createSamlCfg: (arg: TCreateSamlCfgDTO) => Promise<TSamlConfigs>;
updateSamlCfg: (arg: TUpdateSamlCfgDTO) => Promise<TSamlConfigs>;
getSaml: (arg: TGetSamlCfgDTO) => Promise<
| {
id: string;
organization: string;
orgId: string;
authProvider: string;
isActive: boolean;
entryPoint: string;
issuer: string;
cert: string;
lastUsed: Date | null | undefined;
}
| undefined
>;
samlLogin: (arg: TSamlLoginDTO) => Promise<{
isUserCompleted: boolean;
providerAuthToken: string;
}>;
};

View File

@ -1,10 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
import { ormify, TOrmify } from "@app/lib/knex";
export type TScimDALFactory = ReturnType<typeof scimDALFactory>;
export type TScimDALFactory = TOrmify<TableName.ScimToken>;
export const scimDALFactory = (db: TDbClient) => {
export const scimDALFactory = (db: TDbClient): TScimDALFactory => {
const scimTokenOrm = ormify(db, TableName.ScimToken);
return scimTokenOrm;
};

View File

@ -11,7 +11,6 @@ import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TOrgPermission } from "@app/lib/types";
import { AuthTokenType } from "@app/services/auth/auth-type";
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
@ -33,28 +32,10 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import { buildScimGroup, buildScimGroupList, buildScimUser, buildScimUserList, parseScimFilter } from "./scim-fns";
import {
TCreateScimGroupDTO,
TCreateScimTokenDTO,
TCreateScimUserDTO,
TDeleteScimGroupDTO,
TDeleteScimTokenDTO,
TDeleteScimUserDTO,
TGetScimGroupDTO,
TGetScimUserDTO,
TListScimGroupsDTO,
TListScimUsers,
TListScimUsersDTO,
TReplaceScimUserDTO,
TScimGroup,
TScimTokenJwtPayload,
TUpdateScimGroupNamePatchDTO,
TUpdateScimGroupNamePutDTO,
TUpdateScimUserDTO
} from "./scim-types";
import { TScimGroup, TScimServiceFactory } from "./scim-types";
type TScimServiceFactoryDep = {
scimDAL: Pick<TScimDALFactory, "create" | "find" | "findById" | "deleteById">;
@ -111,8 +92,6 @@ type TScimServiceFactoryDep = {
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
};
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
export const scimServiceFactory = ({
licenseService,
scimDAL,
@ -131,8 +110,8 @@ export const scimServiceFactory = ({
projectUserAdditionalPrivilegeDAL,
smtpService,
externalGroupOrgRoleMappingDAL
}: TScimServiceFactoryDep) => {
const createScimToken = async ({
}: TScimServiceFactoryDep): TScimServiceFactory => {
const createScimToken: TScimServiceFactory["createScimToken"] = async ({
actor,
actorId,
actorOrgId,
@ -140,7 +119,7 @@ export const scimServiceFactory = ({
orgId,
description,
ttlDays
}: TCreateScimTokenDTO) => {
}) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Scim);
@ -169,7 +148,13 @@ export const scimServiceFactory = ({
return { scimToken };
};
const listScimTokens = async ({ actor, actorId, actorOrgId, actorAuthMethod, orgId }: TOrgPermission) => {
const listScimTokens: TScimServiceFactory["listScimTokens"] = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
orgId
}) => {
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
@ -183,7 +168,13 @@ export const scimServiceFactory = ({
return scimTokens;
};
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => {
const deleteScimToken: TScimServiceFactory["deleteScimToken"] = async ({
scimTokenId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}) => {
let scimToken = await scimDAL.findById(scimTokenId);
if (!scimToken) throw new NotFoundError({ message: `SCIM token with ID '${scimTokenId}' not found` });
@ -208,12 +199,12 @@ export const scimServiceFactory = ({
};
// SCIM server endpoints
const listScimUsers = async ({
const listScimUsers: TScimServiceFactory["listScimUsers"] = async ({
startIndex = 0,
limit = 100,
filter,
orgId
}: TListScimUsersDTO): Promise<TListScimUsers> => {
}) => {
const org = await orgDAL.findById(orgId);
if (!org.scimEnabled)
@ -250,7 +241,7 @@ export const scimServiceFactory = ({
});
};
const getScimUser = async ({ orgMembershipId, orgId }: TGetScimUserDTO) => {
const getScimUser: TScimServiceFactory["getScimUser"] = async ({ orgMembershipId, orgId }) => {
const [membership] = await orgDAL
.findMembership({
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
@ -287,7 +278,13 @@ export const scimServiceFactory = ({
});
};
const createScimUser = async ({ externalId, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
const createScimUser: TScimServiceFactory["createScimUser"] = async ({
externalId,
email,
firstName,
lastName,
orgId
}) => {
if (!email) throw new ScimRequestError({ detail: "Invalid request. Missing email.", status: 400 });
const org = await orgDAL.findOrgById(orgId);
@ -467,7 +464,7 @@ export const scimServiceFactory = ({
};
// partial
const updateScimUser = async ({ orgMembershipId, orgId, operations }: TUpdateScimUserDTO) => {
const updateScimUser: TScimServiceFactory["updateScimUser"] = async ({ orgMembershipId, orgId, operations }) => {
const org = await orgDAL.findOrgById(orgId);
if (!org.orgAuthMethod) {
throw new ScimRequestError({
@ -540,7 +537,7 @@ export const scimServiceFactory = ({
return scimUser;
};
const replaceScimUser = async ({
const replaceScimUser: TScimServiceFactory["replaceScimUser"] = async ({
orgMembershipId,
active,
orgId,
@ -548,7 +545,7 @@ export const scimServiceFactory = ({
firstName,
email,
externalId
}: TReplaceScimUserDTO) => {
}) => {
const org = await orgDAL.findOrgById(orgId);
if (!org.orgAuthMethod) {
throw new ScimRequestError({
@ -627,7 +624,7 @@ export const scimServiceFactory = ({
});
};
const deleteScimUser = async ({ orgMembershipId, orgId }: TDeleteScimUserDTO) => {
const deleteScimUser: TScimServiceFactory["deleteScimUser"] = async ({ orgMembershipId, orgId }) => {
const [membership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
@ -660,7 +657,13 @@ export const scimServiceFactory = ({
return {}; // intentionally return empty object upon success
};
const listScimGroups = async ({ orgId, startIndex, limit, filter, isMembersExcluded }: TListScimGroupsDTO) => {
const listScimGroups: TScimServiceFactory["listScimGroups"] = async ({
orgId,
startIndex,
limit,
filter,
isMembersExcluded
}) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
throw new BadRequestError({
@ -768,7 +771,7 @@ export const scimServiceFactory = ({
);
};
const createScimGroup = async ({ displayName, orgId, members }: TCreateScimGroupDTO) => {
const createScimGroup: TScimServiceFactory["createScimGroup"] = async ({ displayName, orgId, members }) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
throw new BadRequestError({
@ -863,7 +866,7 @@ export const scimServiceFactory = ({
});
};
const getScimGroup = async ({ groupId, orgId }: TGetScimGroupDTO) => {
const getScimGroup: TScimServiceFactory["getScimGroup"] = async ({ groupId, orgId }) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
throw new BadRequestError({
@ -1011,7 +1014,12 @@ export const scimServiceFactory = ({
return updatedGroup;
};
const replaceScimGroup = async ({ groupId, orgId, displayName, members }: TUpdateScimGroupNamePutDTO) => {
const replaceScimGroup: TScimServiceFactory["replaceScimGroup"] = async ({
groupId,
orgId,
displayName,
members
}) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
throw new BadRequestError({
@ -1043,7 +1051,7 @@ export const scimServiceFactory = ({
});
};
const updateScimGroup = async ({ groupId, orgId, operations }: TUpdateScimGroupNamePatchDTO) => {
const updateScimGroup: TScimServiceFactory["updateScimGroup"] = async ({ groupId, orgId, operations }) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
throw new BadRequestError({
@ -1101,7 +1109,7 @@ export const scimServiceFactory = ({
};
};
const deleteScimGroup = async ({ groupId, orgId }: TDeleteScimGroupDTO) => {
const deleteScimGroup: TScimServiceFactory["deleteScimGroup"] = async ({ groupId, orgId }) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
throw new BadRequestError({
@ -1137,7 +1145,7 @@ export const scimServiceFactory = ({
return {}; // intentionally return empty object upon success
};
const fnValidateScimToken = async (token: TScimTokenJwtPayload) => {
const fnValidateScimToken: TScimServiceFactory["fnValidateScimToken"] = async (token) => {
const scimToken = await scimDAL.findById(token.scimTokenId);
if (!scimToken) throw new UnauthorizedError();

View File

@ -1,5 +1,6 @@
import { ScimPatchOperation } from "scim-patch";
import { TScimTokens } from "@app/db/schemas";
import { TOrgPermission } from "@app/lib/types";
export type TCreateScimTokenDTO = {
@ -156,3 +157,47 @@ export type TScimGroup = {
lastModified: Date;
};
};
export type TScimServiceFactory = {
createScimToken: (arg: TCreateScimTokenDTO) => Promise<{
scimToken: string;
}>;
listScimTokens: (arg: TOrgPermission) => Promise<TScimTokens[]>;
deleteScimToken: (arg: TDeleteScimTokenDTO) => Promise<{
orgId: string;
id: string;
createdAt: Date;
updatedAt: Date;
description: string;
ttlDays: number;
}>;
listScimUsers: (arg: TListScimUsersDTO) => Promise<TListScimUsers>;
getScimUser: (arg: TGetScimUserDTO) => Promise<TScimUser>;
createScimUser: (arg: TCreateScimUserDTO) => Promise<TScimUser>;
updateScimUser: (arg: TUpdateScimUserDTO) => Promise<TScimUser>;
replaceScimUser: (arg: TReplaceScimUserDTO) => Promise<TScimUser>;
deleteScimUser: (arg: TDeleteScimUserDTO) => Promise<object>;
listScimGroups: (arg: TListScimGroupsDTO) => Promise<TListScimGroups>;
createScimGroup: (arg: TCreateScimGroupDTO) => Promise<TScimGroup>;
getScimGroup: (arg: TGetScimGroupDTO) => Promise<TScimGroup>;
deleteScimGroup: (arg: TDeleteScimGroupDTO) => Promise<object>;
replaceScimGroup: (arg: TUpdateScimGroupNamePutDTO) => Promise<TScimGroup>;
updateScimGroup: (arg: TUpdateScimGroupNamePatchDTO) => Promise<{
members: {
value: string;
display: string;
}[];
schemas: string[];
id: string;
displayName: string;
meta: {
resourceType: string;
created: Date;
lastModified: Date;
};
}>;
fnValidateScimToken: (token: TScimTokenJwtPayload) => Promise<{
scimTokenId: string;
orgId: string;
}>;
};

View File

@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import picomatch from "picomatch";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { removeTrailingSlash } from "@app/lib/fn";

View File

@ -1,3 +1,4 @@
/* eslint-disable no-nested-ternary */
import { ForbiddenError, subject } from "@casl/ability";
import {
@ -38,7 +39,8 @@ import {
fnSecretBulkDelete,
fnSecretBulkInsert,
fnSecretBulkUpdate,
getAllNestedSecretReferences
getAllNestedSecretReferences,
INFISICAL_SECRET_VALUE_HIDDEN_MASK
} from "@app/services/secret/secret-fns";
import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
import { SecretOperations } from "@app/services/secret/secret-types";
@ -62,7 +64,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TPermissionServiceFactory } from "../permission/permission-service-types";
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
@ -246,7 +248,7 @@ export const secretApprovalRequestServiceFactory = ({
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
const { policy } = secretApprovalRequest;
const { hasRole } = await permissionService.getProjectPermission({
const { hasRole, permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId,
@ -262,6 +264,11 @@ export const secretApprovalRequestServiceFactory = ({
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
}
const hasSecretReadAccess = permission.can(
ProjectPermissionSecretActions.DescribeAndReadValue,
ProjectPermissionSub.Secrets
);
let secrets;
if (shouldUseSecretV2Bridge) {
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
@ -278,9 +285,10 @@ export const secretApprovalRequestServiceFactory = ({
version: el.version,
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
isRotatedSecret: el.secret?.isRotatedSecret ?? false,
secretValue:
// eslint-disable-next-line no-nested-ternary
el.secret && el.secret.isRotatedSecret
secretValueHidden: !hasSecretReadAccess,
secretValue: !hasSecretReadAccess
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secret && el.secret.isRotatedSecret
? undefined
: el.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
@ -293,9 +301,12 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.secret.key,
id: el.secret.id,
version: el.secret.version,
secretValue: el.secret.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
: "",
secretValueHidden: !hasSecretReadAccess,
secretValue: !hasSecretReadAccess
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secret.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
: "",
secretComment: el.secret.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedComment }).toString()
: ""
@ -306,9 +317,12 @@ export const secretApprovalRequestServiceFactory = ({
secretKey: el.secretVersion.key,
id: el.secretVersion.id,
version: el.secretVersion.version,
secretValue: el.secretVersion.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
: "",
secretValueHidden: !hasSecretReadAccess,
secretValue: !hasSecretReadAccess
? INFISICAL_SECRET_VALUE_HIDDEN_MASK
: el.secretVersion.encryptedValue
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
: "",
secretComment: el.secretVersion.encryptedComment
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
: "",
@ -322,11 +336,13 @@ export const secretApprovalRequestServiceFactory = ({
const encryptedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
secrets = encryptedSecrets.map((el) => ({
...el,
secretValueHidden: !hasSecretReadAccess,
...decryptSecretWithBot(el, botKey),
secret: el.secret
? {
id: el.secret.id,
version: el.secret.version,
secretValueHidden: false,
...decryptSecretWithBot(el.secret, botKey)
}
: undefined,
@ -334,6 +350,7 @@ export const secretApprovalRequestServiceFactory = ({
? {
id: el.secretVersion.id,
version: el.secretVersion.version,
secretValueHidden: false,
...decryptSecretWithBot(el.secretVersion, botKey)
}
: undefined
@ -342,6 +359,7 @@ export const secretApprovalRequestServiceFactory = ({
const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [
secretApprovalRequest.folderId
]);
return { ...secretApprovalRequest, secretPath: secretPath?.[0]?.path || "/", commits: secrets };
};

View File

@ -101,10 +101,56 @@ export const azureClientSecretRotationFactory: TRotationFactory<
}
};
/**
* Checks if a credential with the given keyId exists.
*/
const credentialExists = async (keyId: string): Promise<boolean> => {
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/passwordCredentials`;
try {
const { data } = await request.get<{ value: Array<{ keyId: string }> }>(endpoint, {
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json"
}
});
return data.value?.some((credential) => credential.keyId === keyId) || false;
} catch (error: unknown) {
if (error instanceof AxiosError) {
let message;
if (
error.response?.data &&
typeof error.response.data === "object" &&
"error" in error.response.data &&
typeof (error.response.data as AzureErrorResponse).error.message === "string"
) {
message = (error.response.data as AzureErrorResponse).error.message;
}
throw new BadRequestError({
message: `Failed to check credential existence for app ${objectId}: ${
message || error.message || "Unknown error"
}`
});
}
throw new BadRequestError({
message: "Unable to validate connection: verify credentials"
});
}
};
/**
* Revokes a client secret from the Azure app using its keyId.
* First checks if the credential exists before attempting revocation.
*/
const revokeCredential = async (keyId: string) => {
// Check if credential exists before attempting revocation
const exists = await credentialExists(keyId);
if (!exists) {
return; // Credential doesn't exist, nothing to revoke
}
const accessToken = await getAzureConnectionAccessToken(connection.id, appConnectionDAL, kmsService);
const endpoint = `${GRAPH_API_BASE}/applications/${objectId}/removePassword`;

View File

@ -0,0 +1,3 @@
export * from "./oracledb-credentials-rotation-constants";
export * from "./oracledb-credentials-rotation-schemas";
export * from "./oracledb-credentials-rotation-types";

View File

@ -0,0 +1,20 @@
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
import { TSecretRotationV2ListItem } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
export const ORACLEDB_CREDENTIALS_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = {
name: "OracleDB Credentials",
type: SecretRotation.OracleDBCredentials,
connection: AppConnection.OracleDB,
template: {
createUserStatement: `-- create user
CREATE USER INFISICAL_USER IDENTIFIED BY "temporary_password";
-- grant all privileges
GRANT ALL PRIVILEGES TO INFISICAL_USER;`,
secretsMapping: {
username: "ORACLEDB_USERNAME",
password: "ORACLEDB_PASSWORD"
}
}
};

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