Compare commits

..

469 Commits

Author SHA1 Message Date
3d65d121c0 docs: updated docs to reflect new SDK structure 2024-05-23 04:45:33 +02:00
663f8abc51 bring back last updated at for service token 2024-05-22 20:44:07 -04:00
941a71efaf add index for expire at needed for pruning 2024-05-22 20:38:04 -04:00
19bbc2ab26 add secrets index 2024-05-22 19:04:44 -04:00
f4de52e714 add index on secret snapshot folders 2024-05-22 18:15:04 -04:00
0b87121b67 add index to secret-snapshot-secret for field snapshotId 2024-05-22 17:46:16 -04:00
e649667da8 add indexs to secret versions and secret snapshot secrets 2024-05-22 16:52:18 -04:00
6af4b3f64c add index for audit logs 2024-05-22 15:48:24 -04:00
efcc248486 add tx to find ghost user 2024-05-22 14:54:20 -04:00
82eeae6030 track identity 2024-05-22 13:34:44 -04:00
440c77965c add logs to track permission inject 2024-05-21 22:35:13 -04:00
880289217e revert identity based rate limit 2024-05-21 22:24:53 -04:00
d0947f1040 update service tokens 2024-05-21 22:02:01 -04:00
303edadb1e Merge pull request #1848 from Infisical/feat/add-integration-sync-status
feat: added integration sync status
2024-05-22 01:19:36 +08:00
50155a610d Merge pull request #1858 from Infisical/misc/address-digital-ocean-env-encryption
misc: made digital ocean envs encrypted by default
2024-05-21 13:15:09 -04:00
c2830a56b6 misc: made digital ocean envs encrypted by default 2024-05-22 01:12:28 +08:00
b9a9b6b4d9 misc: applied ui/ux changes 2024-05-22 00:06:06 +08:00
e7f7f271c8 Merge pull request #1857 from Infisical/misc/added-pino-logger-redaction
misc: added logger redaction
2024-05-21 11:49:36 -04:00
b26e96c5a2 misc: added logger redaction 2024-05-21 23:04:11 +08:00
9b404c215b adjustment: ui changes to sync button 2024-05-21 16:04:36 +08:00
d6dae04959 misc: removed unnecessary notification 2024-05-21 14:01:15 +08:00
629bd9b7c6 added support for manual syncing of integrations 2024-05-21 13:56:44 +08:00
3d4aa0fdc9 Merge pull request #1853 from Infisical/daniel/jenkins-docs
Docs: Jenkins Plugin
2024-05-20 20:13:42 -07:00
711e30a6be Docs: Plugin installation 2024-05-21 04:58:24 +02:00
7b1462fdee Docs: Updated Jenkins docs to reflect new plugin 2024-05-21 04:56:26 +02:00
50915833ff Images 2024-05-21 04:56:15 +02:00
44e37fd531 update distinct id for service tokens 2024-05-20 19:50:52 -04:00
fa3f957738 count for null actor 2024-05-20 19:26:03 -04:00
224b26ced6 Merge pull request #1852 from Infisical/rate-limit-based-on-identity
rate limit based on identity
2024-05-20 19:04:41 -04:00
e833d9e67c revert secret read limit 2024-05-20 19:01:01 -04:00
dc08edb7d2 rate limit based on identity 2024-05-20 18:52:23 -04:00
0b78e30848 Delete mongo infisical helm 2024-05-20 16:27:15 -04:00
9253c69325 misc: finalized ui design of integration sync status 2024-05-21 02:35:23 +08:00
7d3a62cc4c feat: added integration sync status 2024-05-20 20:56:29 +08:00
7e2147f14e Adjust aws iam auth docs 2024-05-19 22:05:38 -07:00
32f39c98a7 Merge pull request #1842 from akhilmhdh/feat/membership-by-id
Endpoints for retreiving membership details
2024-05-19 23:51:30 +05:30
ddf6db5a7e small rephrase 2024-05-19 14:19:42 -04:00
554dbf6c23 Merge pull request #1846 from Infisical/create-pull-request/patch-1716042374
GH Action: rename new migration file timestamp
2024-05-18 07:33:38 -07:00
d1997f04c0 chore: renamed new migration files to latest timestamp (gh-action) 2024-05-18 14:26:13 +00:00
deefaa0961 Merge pull request #1827 from Infisical/k8s-auth
Kubernetes Native Authentication Method
2024-05-18 07:25:52 -07:00
a392c9f022 Move k8s migration to front 2024-05-17 22:41:33 -07:00
34222b83ee review fixes for k8s auth 2024-05-17 21:44:02 -04:00
ef36852a47 Add access token trusted ip support to k8s auth 2024-05-17 15:41:32 -07:00
d79fd826a4 Merge remote-tracking branch 'origin' into k8s-auth 2024-05-17 15:39:52 -07:00
18aaa423a9 Merge pull request #1845 from Infisical/patch-gcp-id-token-auth
Patch Identity Access Token Trusted IPs validation for AWS/GCP Auth
2024-05-17 18:38:15 -04:00
32c33eaf6e Patch identity token trusted ips validation for aws/gcp auths 2024-05-17 11:58:08 -07:00
702699b4f0 Update faq.mdx 2024-05-17 12:13:11 -04:00
35ee03d347 Merge pull request #1843 from akhilmhdh/fix/validation-permission
feat: added validation for project permission body in identity specific privilege
2024-05-17 11:50:35 -04:00
=
9c5deee688 feat: added validation for project permission body in identity specific privilege 2024-05-17 21:09:50 +05:30
=
ce4cb39a2d docs: added doc for new endpoints of getting membership and some title change 2024-05-17 20:49:58 +05:30
=
84724e5f65 feat: added endpoints to fetch a particule project user membership and identity 2024-05-17 20:45:31 +05:30
=
56c2e12760 feat: added create identity project membership to api reference and support for roles 2024-05-17 17:09:35 +05:30
=
21656a7ab6 docs: seperate project user and identities api into seperate 2024-05-17 16:15:52 +05:30
=
2ccc77ef40 feat: split project api description for identities and users into seperate 2024-05-17 16:15:05 +05:30
1438415d0c Merge pull request #1450 from Cristobal-M/feat-support-imports-in-cli-export
feat(cli): support of include-imports in export command
2024-05-17 14:21:34 +05:30
eca0e62764 Merge pull request #1829 from akhilmhdh/feat/revoke-access-token
Revoke access token endpoint
2024-05-16 23:41:38 +05:30
e4186f0317 Merge pull request #1838 from akhilmhdh/fix/aws-parameter-stoer
fix: get all secrets from aws ssm
2024-05-16 12:27:20 -04:00
=
704c630797 feat: added rate limit for sync secrets 2024-05-16 21:34:31 +05:30
f398fee2b8 make var readable 2024-05-16 11:43:32 -04:00
=
7fce51e8c1 fix: get all secrets from aws ssm 2024-05-16 20:51:07 +05:30
a6fe233122 Feat: missing documentation for include-imports in export and run command 2024-05-16 11:44:29 +02:00
5e678b1ad2 Merge pull request #1836 from akhilmhdh/fix/create-secret-fail-reference
fix: resolved create secret failing for reference
2024-05-15 22:34:37 -04:00
cf453e87d8 Merge pull request #1835 from Infisical/daniel/fix-expansion
Fix: Fix secret expansion II
2024-05-16 08:02:41 +05:30
=
4af703df5b fix: resolved create secret failing for reference 2024-05-16 07:35:05 +05:30
75b8b521b3 Update secret-service.ts 2024-05-16 03:31:01 +02:00
58c1d3b0ac Merge pull request #1832 from Infisical/daniel/fix-secret-expand-with-recursive
Fix: Secret expansion with recursive mode enabled
2024-05-16 02:33:28 +02:00
6b5cafa631 Merge pull request #1834 from Infisical/patch-update-project-identity
patch project identity update
2024-05-15 20:23:09 -04:00
4a35623956 remove for of with for await 2024-05-15 20:19:10 -04:00
74fe673724 patch project identity update 2024-05-15 20:12:45 -04:00
2f92719771 Fix: Secret expansion with recursive mode 2024-05-16 00:29:07 +02:00
399ca7a221 Merge pull request #1826 from justin1121/patch-1
Update secret-versioning.mdx
2024-05-15 15:34:03 +05:30
=
29f37295e1 docs: added revoke token api to api-reference 2024-05-15 15:27:26 +05:30
=
e3184a5f40 feat(api): added revoke access token endpoint 2024-05-15 15:26:38 +05:30
ace008f44e Make rejectUnauthorized true if ca cert is passed for k8s auth method 2024-05-14 22:49:37 -07:00
4afd95fe1a Merge pull request #1825 from akhilmhdh/feat/sync-integration-inline
Secret reference and integration sync support
2024-05-15 01:36:19 -04:00
3cd719f6b0 update index secret references button 2024-05-15 09:57:24 +05:30
c6352cc970 updated texts and comments 2024-05-15 09:57:24 +05:30
=
d4555f9698 feat: ui for reindex secret reference 2024-05-15 09:57:24 +05:30
=
393964c4ae feat: implemented inline secret reference integration sync 2024-05-15 09:57:23 +05:30
e4afbe8662 Update k8s auth docs 2024-05-14 20:44:09 -07:00
0d89aa8607 Add docs for K8s auth method 2024-05-14 18:02:05 -07:00
2b91ec5ae9 Fix merge conflicts 2024-05-14 13:37:39 -07:00
c438479246 update prod pipeline names 2024-05-14 16:14:42 -04:00
9828cbbfbe Update secret-versioning.mdx 2024-05-14 16:28:43 -03:00
cd910a2fac Update k8s auth impl to be able to test ca, tokenReviewerjwt locally 2024-05-14 11:42:26 -07:00
fc1dffd7e2 Merge pull request #1823 from Infisical/snyk-fix-a2a4b055e42c14d5cbdb505e7670d300
[Snyk] Security upgrade bullmq from 5.3.3 to 5.4.2
2024-05-14 12:02:13 -04:00
55f8198a2d Merge pull request #1821 from matthewaerose/patch-1
Fix: Correct typo from 'Halm' to 'Helm'
2024-05-14 11:46:49 -04:00
4d166402df Merge pull request #1824 from Infisical/create-pull-request/patch-1715660210
GH Action: rename new migration file timestamp
2024-05-14 00:17:34 -04:00
19edf83dbc chore: renamed new migration files to latest timestamp (gh-action) 2024-05-14 04:16:49 +00:00
13f6b238e7 fix: backend/package.json & backend/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-BRACES-6838727
- https://snyk.io/vuln/SNYK-JS-MICROMATCH-6838728
2024-05-14 04:16:40 +00:00
8dee1f8fc7 Merge pull request #1800 from Infisical/gcp-iam-auth
GCP Native Authentication Method
2024-05-14 00:16:28 -04:00
3b23035dfb disable secret scanning 2024-05-13 23:12:36 -04:00
0c8ef13d8d Fix: Correct typo from 'Halm' to 'Helm' 2024-05-13 13:38:09 -05:00
389d51fa5c Merge pull request #1819 from akhilmhdh/feat/hide-secret-scanner
feat: added secret-scanning disable option
2024-05-13 13:53:35 -04:00
638208e9fa update secret scanning text 2024-05-13 13:48:23 -04:00
c176d1e4f7 Merge pull request #1818 from akhilmhdh/fix/patches-v2
Improvised secret input component and fontawesome performance improvment
2024-05-13 13:42:30 -04:00
=
91a23a608e feat: added secret-scanning disable option 2024-05-13 21:55:37 +05:30
=
c6a25271dd fix: changed cross key to check for submission for save secret changes 2024-05-13 19:50:38 +05:30
=
0f5c1340d3 feat: dashboard optimized on font awesome levels using symbols technique 2024-05-13 13:40:59 +05:30
=
ecbdae110d feat: simplified secret input with auto completion 2024-05-13 13:40:59 +05:30
=
8ef727b4ec fix: resolved typo in dashboard nav header redirection 2024-05-13 13:40:59 +05:30
=
c6f24dbb5e fix: resolved unique key error secret input rendering 2024-05-13 13:40:59 +05:30
c45dae4137 Merge remote-tracking branch 'origin' into k8s-auth 2024-05-12 16:16:44 -07:00
18c0d2fd6f Merge pull request #1814 from Infisical/aws-integration-patch
Allow updating tags in AWS Secret Manager integration
2024-05-12 15:03:19 -07:00
c1fb8f47bf Add UntagResource IAM policy requirement for AWS SM integration docs 2024-05-12 08:57:41 -07:00
bd57a068d1 Fix merge conflicts 2024-05-12 08:43:29 -07:00
990eddeb32 Merge pull request #1816 from akhilmhdh/fix/remove-migration-notice
fix: removed migration notice
2024-05-11 13:43:04 -04:00
=
ce01f8d099 fix: removed migration notice 2024-05-11 23:04:43 +05:30
faf6708b00 Merge pull request #1815 from akhilmhdh/fix/migration-mode-patch-v1
feat: maintaince mode enable machine identity login and renew
2024-05-11 11:26:21 -04:00
=
a58d6ebdac feat: maintaince mode enable machine identity login and renew 2024-05-11 20:54:00 +05:30
818b136836 Make app and appId optional in update integration endpoint 2024-05-10 19:17:40 -07:00
0cdade6a2d Update AWS SM integration to allow updating tags 2024-05-10 19:07:44 -07:00
bcf9b68e2b Update GCP auth method description 2024-05-10 10:28:29 -07:00
6aa9fb6ecd Updated docs 2024-05-10 10:24:29 -07:00
38e7382d85 Remove GCP audit log space 2024-05-10 10:15:43 -07:00
95e12287c2 Minor edits to renaming GCE -> ID Token 2024-05-10 10:14:13 -07:00
c6d14a4bea Update 2024-05-10 10:10:51 -07:00
0a91586904 Remove service account JSON requirement from GCP Auth 2024-05-10 09:56:35 -07:00
6561a9c7be Merge pull request #1804 from Infisical/feat/add-support-for-secret-folder-rename-overview
Feature: add support for secret folder rename in the overview page
2024-05-10 23:07:14 +08:00
86aaa486b4 Update secret-folder-service.ts 2024-05-10 17:00:30 +02:00
9880977098 misc: addressed naming suggestion 2024-05-10 22:52:08 +08:00
b93aaffe77 adjustment: updated to use project slug 2024-05-10 22:34:16 +08:00
1ea0d55dd1 Merge pull request #1813 from Infisical/misc/update-documentation-for-github-integration
misc: updated documentation for github integration to include official action
2024-05-10 09:14:14 -04:00
0866a90c8e misc: updated documentation for github integration 2024-05-10 16:29:12 +08:00
3fff272cb3 feat: added snapshot for batch 2024-05-10 15:46:31 +08:00
2559809eac misc: addressed formatting issues 2024-05-10 14:41:35 +08:00
f32abbdc25 feat: integrate overview folder rename with new batch endpoint 2024-05-10 14:00:49 +08:00
a6f750fafb feat: added batch update endpoint for folders 2024-05-10 13:57:00 +08:00
610f474ecc Rename migration file 2024-05-09 16:58:39 -07:00
03f4a699e6 Improve GCP docs 2024-05-09 16:53:08 -07:00
533d49304a Update GCP documentation 2024-05-09 15:35:50 -07:00
184b59ad1d Resolve merge conflicts 2024-05-09 12:51:24 -07:00
b4a2123fa3 Merge pull request #1812 from Infisical/delete-pg-migrator
Delete PG migrator folder
2024-05-09 15:19:04 -04:00
79cacfa89c Delete PG migrator folder 2024-05-09 12:16:13 -07:00
44531487d6 Merge pull request #1811 from Infisical/maidul-pacth233
revert schema name for memberships-unique-constraint
2024-05-09 13:46:32 -04:00
7c77a4f049 revert schema name 2024-05-09 13:42:23 -04:00
9dfb587032 Merge pull request #1810 from Infisical/check-saml-email-verification
Update isEmailVerified field upon invite signups
2024-05-09 13:03:52 -04:00
3952ad9a2e Update isEmailVerified field upon invite signups 2024-05-09 09:51:53 -07:00
9c15cb407d Merge pull request #1806 from Infisical/aws-non-delete
Add option to not delete secrets in parameter store
2024-05-09 21:56:48 +05:30
cb17efa10b Merge pull request #1809 from akhilmhdh/fix/patches-v2
Workspace slug support in secret v3 Get Key
2024-05-09 12:17:14 -04:00
4adc2c4927 update api descriptions 2024-05-09 12:11:46 -04:00
1a26b34ad8 Merge pull request #1805 from Infisical/revise-aws-auth
Reframe AWS IAM auth to AWS Auth with IAM type
2024-05-09 12:06:31 -04:00
=
21c339d27a fix: better error message on ua based login error 2024-05-09 21:32:09 +05:30
1da4cf85f8 rename schema file 2024-05-09 11:59:47 -04:00
=
20f29c752d fix: added workspaceSlug support get secret by key 2024-05-09 21:23:57 +05:30
29ea12f8b1 Merge pull request #1807 from Infisical/mermaid-universal-auth
Add mermaid diagram for Universal Auth
2024-05-08 22:05:12 -07:00
b4f1cce587 Add mermaid diagram for universal auth 2024-05-08 22:03:57 -07:00
5a92520ca3 Update build-staging-and-deploy-aws.yml 2024-05-09 00:53:42 -04:00
42471b22bb Finish AWS Auth mermaid diagram 2024-05-08 21:52:56 -07:00
79704e9c98 add option to not delete secrets in parameter store 2024-05-08 21:49:09 -07:00
1165d11816 Update build-staging-and-deploy-aws.yml 2024-05-09 00:27:21 -04:00
15ea96815c Rename AWS IAM auth to AWS Auth with IAM type 2024-05-08 21:22:23 -07:00
86d4d88b58 package json lock 2024-05-09 00:19:44 -04:00
a12ad91e59 Update build-staging-and-deploy-aws.yml 2024-05-09 00:15:42 -04:00
3113e40d0b Add mermaid diagrams to gcp auth docs 2024-05-08 20:09:08 -07:00
2406d3d904 Update GCP auth docs 2024-05-08 17:03:26 -07:00
e99182c141 Complete adding GCP GCE auth 2024-05-08 15:51:09 -07:00
522dd0836e feat: added validation for folder name duplicates 2024-05-08 23:25:33 +08:00
e461787c78 feat: added support for renaming folders in the overview page 2024-05-08 23:24:33 +08:00
f74993e850 Merge pull request #1803 from Infisical/misc/improved-select-path-component-ux-1
misc: added handling of input focus to select path component
2024-05-08 22:00:02 +08:00
d0036a5656 Merge remote-tracking branch 'origin/main' into misc/improved-select-path-component-ux-1 2024-05-08 17:28:31 +08:00
e7f19421ef misc: resolved auto-popup of suggestions 2024-05-08 17:24:06 +08:00
e18d830fe8 Merge pull request #1801 from Infisical/daniel/k8-recursive
Feat: Recursive support for K8 operaetor
2024-05-08 00:44:07 +02:00
be2fc4fec4 Update Chart.yaml 2024-05-08 00:42:38 +02:00
829dbb9970 Update values.yaml 2024-05-08 00:41:53 +02:00
0b012c5dfb Chore: Helm 2024-05-08 00:23:50 +02:00
b0421ccad0 Docs: Add recursive to example 2024-05-08 00:21:08 +02:00
6b83326d00 Feat: Recursive mode support 2024-05-08 00:18:53 +02:00
1f6abc7f27 Feat: Recursive mode and fix error formatting 2024-05-08 00:18:40 +02:00
4a02520147 Update sample 2024-05-08 00:18:26 +02:00
14f38eb961 Feat: Recursive mode types 2024-05-08 00:16:51 +02:00
ac469dbe4f Update GCP auth docs 2024-05-07 14:58:14 -07:00
d98430fe07 Merge remote-tracking branch 'origin' into gcp-iam-auth 2024-05-07 14:29:08 -07:00
82bafd02bb Fix merge conflicts 2024-05-07 14:28:41 -07:00
37a59b2576 Merge pull request #1799 from Infisical/create-pull-request/patch-1715116016
GH Action: rename new migration file timestamp
2024-05-07 14:27:45 -07:00
cebd22da8e chore: renamed new migration files to latest timestamp (gh-action) 2024-05-07 21:06:55 +00:00
d200405c6e Merge pull request #1778 from Infisical/aws-iam-auth
AWS IAM Authentication Method
2024-05-07 14:06:30 -07:00
3a1cdc4f44 Delete backend/src/db/migrations/20240507162149_test.ts 2024-05-07 15:41:09 -04:00
1d40d9e448 Begin frontend for GCP IAM Auth 2024-05-07 12:40:19 -07:00
e96ca8d355 Draft GCP IAM Auth docs 2024-05-07 12:15:18 -07:00
2929d94f0a Merge pull request #1797 from Infisical/maidul98-patch-10
test
2024-05-07 14:28:03 -04:00
0383ae9e8b Create 20240507162149_test.ts 2024-05-07 14:27:44 -04:00
00faa6257f Delete backend/src/db/migrations/20240507162149_test.ts 2024-05-07 14:27:33 -04:00
183bde55ca correctly fetch merged by user login 2024-05-07 14:26:56 -04:00
c96fc1f798 Merge pull request #1795 from Infisical/maidul98-patch-9
test
2024-05-07 14:09:49 -04:00
80f7ff1ea8 Create 20240507162149_test.ts 2024-05-07 14:09:38 -04:00
c87620109b Rename 20240507162141_access to 20240507162141_access.ts 2024-05-07 13:58:10 -04:00
02c158b4ed Delete backend/src/db/migrations/20240507162180_test 2024-05-07 13:47:25 -04:00
588f4bdb09 Fix merge conflict 2024-05-07 10:45:07 -07:00
4d74d264dd Finish preliminary backend endpoints for GCP IAM Auth method 2024-05-07 10:42:39 -07:00
ddfa64eb33 Merge pull request #1793 from Infisical/maidul98-patch-8
testing-ignore
2024-05-07 13:27:19 -04:00
7fdaa1543a Create 20240507162180_test 2024-05-07 13:26:52 -04:00
c8433f39ed Delete backend/src/db/migrations/20240507162180_test 2024-05-07 13:26:42 -04:00
ba238a8f3b get pr details by pr number 2024-05-07 13:25:35 -04:00
dd89a80449 Merge pull request #1788 from Infisical/feature/add-multi-select-deletion-overview
Feature: Add support for deleting secrets and folders in the Overview page
2024-05-08 01:25:21 +08:00
a1585db76a Merge pull request #1791 from Infisical/maidul98-patch-7
Create 20240507162180_test
2024-05-07 13:16:59 -04:00
f5f0bf3c83 Create 20240507162180_test 2024-05-07 13:16:42 -04:00
3638645b8a get closed by user 2024-05-07 13:15:15 -04:00
f957b9d970 misc: migrated to react-state 2024-05-08 01:03:41 +08:00
b461697fbf Merge pull request #1790 from Infisical/fix/api-doc-typo
doc: fixed typo in api privilege documentation
2024-05-07 12:56:34 -04:00
3ce91b8a20 doc: fixed typo in api privilege documentation 2024-05-07 22:25:36 +05:30
8bab14a672 misc: added handling of input focus 2024-05-08 00:43:14 +08:00
78922a80e2 Merge pull request #1716 from Infisical/snyk-fix-0eecde4245cc6ed2d19ec9aa18a14703
[Snyk] Security upgrade mysql2 from 3.9.4 to 3.9.7
2024-05-07 12:23:13 -04:00
0181007c66 Merge pull request #1789 from Infisical/create-pull-request/patch-1715098901
GH Action: rename new migration file timestamp
2024-05-07 12:22:42 -04:00
306cf8733e chore: renamed new migration files to latest timestamp (gh-action) 2024-05-07 16:21:40 +00:00
6e829516db Merge pull request #1652 from Infisical/daniel/request-access
Feat: Request Access
2024-05-07 12:21:17 -04:00
c08fcc6f5e adjustment: finalized notification text 2024-05-08 00:12:55 +08:00
9a585ad930 Fix: Rebase error 2024-05-07 17:30:36 +02:00
95c1fff7d3 Chore: Remove unused files 2024-05-07 17:30:36 +02:00
9c2591f3a6 Fix: Moved Divider to v2 2024-05-07 17:30:36 +02:00
a579598b6d Chore: Moved verifyApprovers 2024-05-07 17:30:36 +02:00
af0d31db2c Fix: Improved migrations 2024-05-07 17:30:36 +02:00
fb6c4acf31 Delete access-approval-request-secret-dal.ts 2024-05-07 17:30:36 +02:00
551ca0fa8c Migration improvements 2024-05-07 17:30:36 +02:00
4a0ccbe69e Fixed bugs 2024-05-07 17:30:36 +02:00
f5a463ddea Update SecretApprovalPage.tsx 2024-05-07 17:30:36 +02:00
ce1ad6f32e Fix: Rebase errors 2024-05-07 17:30:36 +02:00
56c8b4f5e5 Removed unnessecary types 2024-05-07 17:30:36 +02:00
29b26e3158 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
6e209bf099 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
949d210263 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
1a2d8e96f3 style changes 2024-05-07 17:30:36 +02:00
9198eb5fba Update licence-fns.ts 2024-05-07 17:30:36 +02:00
0580f37c5e Update generate-schema-types.ts 2024-05-07 17:30:36 +02:00
e53d40f0e5 Update SecretApprovalPage.tsx 2024-05-07 17:30:36 +02:00
801c0c5ada Fix: Remove redundant code 2024-05-07 17:30:36 +02:00
7b8af89bee Fix: Validate approvers access 2024-05-07 17:30:36 +02:00
ef7f5c9eac Feat: Request access (new routes) 2024-05-07 17:30:36 +02:00
db0b4a5ad1 Feat: Request access 2024-05-07 17:30:36 +02:00
cb505d1525 Draft 2024-05-07 17:30:36 +02:00
c66476e2b4 Fix: Multiple approvers acceptance bug 2024-05-07 17:30:36 +02:00
60a06edd9b Style: Fix styling 2024-05-07 17:30:36 +02:00
e8e1d46f0e Capitalization 2024-05-07 17:30:36 +02:00
038fe3508c Removed unnessecary types 2024-05-07 17:30:36 +02:00
7d1dff9e5a Fix: Security vulnurbility making it possible to spoof env & secret path requested. 2024-05-07 17:30:36 +02:00
5117f5d3c1 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
350dd97b98 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
121902e51f Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
923bf02046 style changes 2024-05-07 17:30:36 +02:00
27447ddc88 Update licence-fns.ts 2024-05-07 17:30:36 +02:00
a3b4b650d1 Removed unused parameter 2024-05-07 17:30:36 +02:00
3f0f45e853 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:36 +02:00
3bb50b235d Update generate-schema-types.ts 2024-05-07 17:30:36 +02:00
1afd120e8e Feat: Request access 2024-05-07 17:30:36 +02:00
ab3593af37 Feat: Request access 2024-05-07 17:30:36 +02:00
2c2afbea7a Fix: Move to project slug 2024-05-07 17:30:36 +02:00
4eabbb3ac5 Fix: Added support for request access 2024-05-07 17:30:36 +02:00
1ccd74e1a5 Fix: Remove redundant code 2024-05-07 17:30:35 +02:00
812cced9d5 Feat: Request access 2024-05-07 17:30:35 +02:00
cd6be68461 Fix: Validate approvers access 2024-05-07 17:30:35 +02:00
5c69bbf515 Feat: Request access (new routes) 2024-05-07 17:30:35 +02:00
448f89fd1c Feat: Request Access (migrations) 2024-05-07 17:30:35 +02:00
3331699f56 Feat: Request access 2024-05-07 17:30:35 +02:00
810f670e64 Feat: Request Access 2024-05-07 17:30:35 +02:00
5894df4370 Draft 2024-05-07 17:30:35 +02:00
2aacd54116 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:35 +02:00
73d9fcc0de Draft 2024-05-07 17:30:35 +02:00
7ac3bb20df Update instance recognition of offline license 2024-05-07 17:30:35 +02:00
d659b5a624 Fix: Duplicate access request check 2024-05-07 17:30:35 +02:00
0bbdf2a8f4 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
a8eba9cfbf Fix: Moved from email to username 2024-05-07 17:30:35 +02:00
a3d7c5f599 Cleanup 2024-05-07 17:30:35 +02:00
c325674da0 Fix: Move standalone components to individual files 2024-05-07 17:30:35 +02:00
3637152a6b Chore: Remove unused files 2024-05-07 17:30:35 +02:00
8ed3c0cd68 Fix: Use username instead of email 2024-05-07 17:30:35 +02:00
cdd836d58f Fix: Columns 2024-05-07 17:30:35 +02:00
3d3b1eb21a Fix: Use username instead of email 2024-05-07 17:30:35 +02:00
6aab28c4c7 Feat: Badge component 2024-05-07 17:30:35 +02:00
f038b28c1c Fix: Moved Divider to v2 2024-05-07 17:30:35 +02:00
24a286e898 Update index.ts 2024-05-07 17:30:35 +02:00
0c1103e778 Fix: Pick 2024-05-07 17:30:35 +02:00
2c1eecaf85 Chore: Moved verifyApprovers 2024-05-07 17:30:35 +02:00
5884565de7 Fix: Make verifyApprovers independent on memberships 2024-05-07 17:30:35 +02:00
dd43268506 Fix: Made API endpoints more REST compliant 2024-05-07 17:30:35 +02:00
9d362b8597 Chore: Cleaned up models 2024-05-07 17:30:35 +02:00
972ecc3e92 Fix: Improved migrations 2024-05-07 17:30:35 +02:00
dc3014409f Delete access-approval-request-secret-dal.ts 2024-05-07 17:30:35 +02:00
4e449f62c0 Fix: Don't display requested by when user has no access to read workspace members 2024-05-07 17:30:35 +02:00
c911a7cd81 Fix: Don't display requested by when user has no access to read workspace members 2024-05-07 17:30:35 +02:00
44370d49e3 Fix: Add tooltip for clarity and fix wording 2024-05-07 17:30:35 +02:00
c7d2dfd351 Fix: Requesting approvals on previously rejected resources 2024-05-07 17:30:35 +02:00
1785548a40 Fix: Sort by createdAt 2024-05-07 17:30:35 +02:00
2baf9e0739 Migration improvements 2024-05-07 17:30:35 +02:00
01e7ed23ba Fixed bugs 2024-05-07 17:30:35 +02:00
1f789110e3 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
c874c943c1 Fix: Rebase errors 2024-05-07 17:30:35 +02:00
dab69dcb51 Removed unnessecary types 2024-05-07 17:30:35 +02:00
8e82bfae86 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
bc810ea567 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
22470376d9 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
bb9503471f style changes 2024-05-07 17:30:35 +02:00
a687b1d0db Update licence-fns.ts 2024-05-07 17:30:35 +02:00
0aa77f90c8 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:35 +02:00
5a04371fb0 Update generate-schema-types.ts 2024-05-07 17:30:35 +02:00
70c06c91c8 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
926d324ae3 Fix: Added support for request access 2024-05-07 17:30:35 +02:00
e48377dea9 Fix: Remove redundant code 2024-05-07 17:30:35 +02:00
5e1484bd05 Fix: Validate approvers access 2024-05-07 17:30:35 +02:00
6d9de752d7 Feat: Request access (new routes) 2024-05-07 17:30:35 +02:00
f9a9b1222e Feat: Request Access (migrations) 2024-05-07 17:30:35 +02:00
4326ce970a Feat: Request access 2024-05-07 17:30:35 +02:00
7a3a9ca9ea Draft 2024-05-07 17:30:35 +02:00
32a110e0ca Fix: Multiple approvers acceptance bug 2024-05-07 17:30:35 +02:00
da5278f6bf Fix: Rename change -> secret 2024-05-07 17:30:35 +02:00
7e765681cb Style: Fix styling 2024-05-07 17:30:35 +02:00
0990ce1f92 Capitalization 2024-05-07 17:30:35 +02:00
2369ff6813 Removed unnessecary types 2024-05-07 17:30:35 +02:00
478520f090 Remove unnessecary types and projectMembershipid 2024-05-07 17:30:35 +02:00
54313f9c08 Renaming 2024-05-07 17:30:35 +02:00
cb8763bc9c Update smtp-service.ts 2024-05-07 17:30:35 +02:00
c5d11eee7f Feat: Find users by project membership ID's 2024-05-07 17:30:35 +02:00
8e1d19c041 Feat: access request emails 2024-05-07 17:30:35 +02:00
608c7a4dee Update index.ts 2024-05-07 17:30:35 +02:00
c7b60bcf0e Update access-approval-request-types.ts 2024-05-07 17:30:35 +02:00
6ae62675be Feat: Send emails for access requests 2024-05-07 17:30:35 +02:00
fb2ab200b9 Feat: Request access, extract permission details 2024-05-07 17:30:35 +02:00
f1428d72c2 Fix: Security vulnurbility making it possible to spoof env & secret path requested. 2024-05-07 17:30:35 +02:00
4cb51805f0 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
8c40918cef Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
3a002b921a Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
299653528c style changes 2024-05-07 17:30:35 +02:00
8c256bd9c8 Fix: Status filtering & query invalidation 2024-05-07 17:30:35 +02:00
f8e0e01bb8 Fix: Access request query invalidation 2024-05-07 17:30:35 +02:00
b59413ded0 fix privilegeId issue 2024-05-07 17:30:35 +02:00
15c747e8e8 Fix: Request access permissions 2024-05-07 17:30:35 +02:00
073a9ee6a4 Update licence-fns.ts 2024-05-07 17:30:35 +02:00
d371c568f1 Add count 2024-05-07 17:30:35 +02:00
e6c086ab09 Fix: Don't allow users to request access to the same resource with same permissions multiple times 2024-05-07 17:30:35 +02:00
890c8b89be Removed unused parameter 2024-05-07 17:30:35 +02:00
6f4b62cfbb Removed logs 2024-05-07 17:30:35 +02:00
076c70f6ff Removed logs 2024-05-07 17:30:35 +02:00
aedc1f2441 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:35 +02:00
352d363bd4 Update generate-schema-types.ts 2024-05-07 17:30:35 +02:00
ac92a916b4 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
17587ff1b8 Fix: Minor fixes 2024-05-07 17:30:35 +02:00
7f1c8d9ff6 Create index.tsx 2024-05-07 17:30:35 +02:00
ac24c0f760 Feat: Request access 2024-05-07 17:30:35 +02:00
0e95c1bcee Feat: Request access 2024-05-07 17:30:35 +02:00
447630135b Feat: Request access 2024-05-07 17:30:35 +02:00
ddd6adf804 Fix: Move to project slug 2024-05-07 17:30:35 +02:00
a4b6d2650a Fix: Move to project slug 2024-05-07 17:30:35 +02:00
2f5d6b11da Fix: Move to project slug 2024-05-07 17:30:35 +02:00
d380b7f788 Fix: Added support for request access 2024-05-07 17:30:35 +02:00
7aee4fdfcd Feat: Request access 2024-05-07 17:30:27 +02:00
83bd3a0bf4 Update index.tsx 2024-05-07 17:30:27 +02:00
1f68730aa3 Fix: Improve disabled Select 2024-05-07 17:30:27 +02:00
7fd1d72985 Fix: Access Request setup 2024-05-07 17:30:27 +02:00
b298eec9db Fix: Danger color not working on disabled buttons 2024-05-07 17:30:27 +02:00
696479a2ef Fix: Remove redundant code 2024-05-07 17:30:27 +02:00
ad6e2aeb9e Feat: Request Access 2024-05-07 17:30:27 +02:00
ad405109a0 Feat: Request access 2024-05-07 17:30:27 +02:00
992a82015a Feat: Request access 2024-05-07 17:30:27 +02:00
317956a038 Fix: Types mismatch 2024-05-07 17:30:27 +02:00
5255c4075a Fix: Validate approvers access 2024-05-07 17:30:27 +02:00
eca36f1993 Feat: Request access 2024-05-07 17:30:27 +02:00
7e29a6a656 Fix: Access Approval Policy DAL bugs 2024-05-07 17:30:27 +02:00
f458e34c37 Feat: Request access (new routes) 2024-05-07 17:30:27 +02:00
99f5ed1f4b Fix: Move to project slug 2024-05-07 17:30:27 +02:00
f981c59b5c Feat: Request access (models) 2024-05-07 17:30:27 +02:00
a528d011c0 Feat: Request Access (migrations) 2024-05-07 17:30:27 +02:00
d337118803 Feat: Request access 2024-05-07 17:30:27 +02:00
68a11db1c6 Feat: Request access 2024-05-07 17:30:27 +02:00
91bf6a6dad Fix: Remove logs 2024-05-07 17:30:13 +02:00
12c655a152 Feat: Request Access 2024-05-07 17:30:13 +02:00
1d2f10178f Draft 2024-05-07 17:30:13 +02:00
c5cd5047d7 Update trusted email migration file with backfill 2024-05-07 07:59:37 -07:00
06c103c10a misc: added handling for no changes made 2024-05-07 22:19:20 +08:00
b6a73459a8 misc: addressed rbac for bulk delete in overview 2024-05-07 16:37:10 +08:00
536f51f6ba misc: added descriptive error message 2024-05-07 15:21:17 +08:00
a9b72b2da3 feat: added handling of folder/secret deletion 2024-05-07 15:16:37 +08:00
e3c80309c3 Move aws auth migration file to front 2024-05-06 23:03:45 -07:00
ec3d6c20e8 Merge remote-tracking branch 'origin' into aws-iam-auth 2024-05-06 22:58:47 -07:00
5d7c0f30c8 Fix typo universal auth 2024-05-06 22:58:35 -07:00
a3552d00d1 feat: add multi-select in secret overview 2024-05-07 13:52:42 +08:00
c9f0ba08e1 Merge pull request #1787 from Infisical/create-pull-request/patch-1715052491
GH Action: rename new migration file timestamp
2024-05-07 01:17:35 -04:00
308e605b6c chore: renamed new migration files to latest timestamp (gh-action) 2024-05-07 03:28:10 +00:00
4d8965eb82 Merge pull request #1762 from Infisical/groups-phase-2c
Groups Phase 2B (Trust external SAML/LDAP email option, email verification, SCIM user ID ref update)
2024-05-06 23:27:50 -04:00
0357e7c80e Put email-confirmation migration into trusted-saml-ldap-emails file 2024-05-06 19:58:58 -07:00
ba1b223655 Patch migration file hasTable ref 2024-05-06 19:44:43 -07:00
0b089e6fa6 Update aws iam auth fns filename 2024-05-06 18:35:34 -07:00
3b88a2759b Patch unsynchronized username/email for saml/scim 2024-05-06 18:27:36 -07:00
42383d5643 Merge pull request #1782 from akhilmhdh/feat/privilege-identity-api-change
Privilege identity api change
2024-05-06 15:01:02 -04:00
d198ba1a79 feat: refactored the map unpack to a function 2024-05-06 23:27:51 +05:30
b3579cb271 rephrase text for permission schema zod 2024-05-06 13:44:39 -04:00
30ccb78c81 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-05-06 09:33:36 -07:00
fdd67c89b3 Merge pull request #1783 from akhilmhdh:feat/dashboard-slug-fix
feat: debounced main page search and rolled back to old input component
2024-05-06 12:31:57 -04:00
79e9b1b2ae feat: debounced main page search and rolled back to old input component 2024-05-06 20:43:23 +05:30
86fd4d5fba feat: added a fixed sorted order to avoid jumps 2024-05-06 14:26:46 +05:30
4692aa12bd feat: updated identity additional privilege permission object in api to have a proper body and explanation 2024-05-06 14:01:30 +05:30
61a0997adc fix(ui): secret path input showing / for a valid value that comes delayed 2024-05-06 14:00:32 +05:30
c276c44c08 Finish preliminary backend endpoints / db structure for k8s auth 2024-05-05 19:14:49 -07:00
b4f1bec1a9 Merge pull request #1781 from Infisical/feature/added-secret-expand-in-raw-secret-get
feat: added secret expand option in secrets get API
2024-05-04 22:09:12 -04:00
ab79342743 rename to expandSecretReferences 2024-05-04 22:05:57 -04:00
1957531ac4 Update docker-compose.mdx 2024-05-04 21:01:19 -04:00
61ae0e2fc7 feat: added secret expand option in secrets get API 2024-05-04 14:42:22 +08:00
cbf8e041e9 Finish docs for AWS IAM Auth, update ARN regex 2024-05-03 17:20:44 -07:00
87b571d6ff Merge remote-tracking branch 'origin' 2024-05-03 09:52:48 -07:00
1e6af8ad8f Update email in beginEmailSignupProcess 2024-05-03 09:49:10 -07:00
a771ddf859 Merge pull request #1721 from akhilmhdh/feat/audit-log-stream
Audit log streams
2024-05-03 12:48:55 -04:00
c4cd6909bb docs: improved datadog log stream doc 2024-05-03 20:09:57 +05:30
49642480d3 fix: resolved headers not working in queue 2024-05-03 20:06:24 +05:30
b667dccc0d docs: improved text audit log stream 2024-05-03 18:19:37 +05:30
fdda247120 feat: added a catch and override error message for ping check 2024-05-03 18:18:57 +05:30
ee8a88d062 Update docker-swarm.mdx 2024-05-03 08:44:43 -04:00
33349839cd Merge pull request #1780 from Infisical/maidul-1221
Make migration notice visible
2024-05-03 08:24:07 -04:00
8f3883c7d4 update date 2024-05-03 08:20:20 -04:00
38cfb7fd41 patch migration notice bug 2024-05-03 08:19:27 -04:00
a331eb8dc4 docs: updated docs with header inputs for audit log stream and datadog section added 2024-05-03 17:43:58 +05:30
2dcb409d3b feat: changed from token to headers for audit log streams api 2024-05-03 17:43:14 +05:30
39bcb73f3d Merge pull request #1779 from Infisical/adjustment/added-workspace-slug-to-api-projects-get
Added slug to API response from workspace get all
2024-05-03 15:38:34 +05:30
52189111d7 adjustment: added slug to response 2024-05-03 18:03:21 +08:00
5c4d35e30a Merge remote-tracking branch 'origin' into aws-iam-auth 2024-05-02 22:53:14 -07:00
d5c74d558a Start docs for AWS IAM auth 2024-05-02 22:52:37 -07:00
9c002ad645 Finish preliminary AWS IAM Auth method 2024-05-02 22:42:02 -07:00
f369761920 feat: rollback license-fns 2024-05-03 00:31:40 +05:30
8eb22630b6 docs: added docs for audit log stream 2024-05-03 00:23:59 +05:30
d650fd68c0 feat: improved api desc, added ping check before accepting stream 2024-05-03 00:23:59 +05:30
387c899193 add line breaks for readiblity 2024-05-03 00:23:59 +05:30
37882e6344 rephrase ui texts 2024-05-03 00:23:59 +05:30
68a1aa6f46 feat: switched audit log stream from project level to org level 2024-05-03 00:23:59 +05:30
fa18ca41ac feat(server): fixed if projectid is missing 2024-05-03 00:23:59 +05:30
8485fdc1cd feat(ui): audit log page completed 2024-05-03 00:23:59 +05:30
49ae2386c0 feat(ui): audit log api hooks 2024-05-03 00:23:59 +05:30
f2b1f3f0e7 feat(server): audit log streams services and api routes 2024-05-03 00:23:58 +05:30
69aa20e35c feat(server): audit log streams db schema changes 2024-05-03 00:23:58 +05:30
524c7ae78f Merge pull request #1776 from akhilmhdh/fix/bulk-op-sidebar
doc: resolved missing bulk secret api operations
2024-05-02 12:17:45 -04:00
e13f7a7486 doc: resolved missing bulk secret api operations 2024-05-02 21:40:52 +05:30
1867fb2fc4 Merge pull request #1769 from Infisical/fix/address-functional-issues-with-secret-input
fix: address functional issues with secret input
2024-05-02 11:26:06 -04:00
5dd144b97b update self host nav items 2024-05-01 22:06:26 -04:00
b1b430e003 add more steps and FAQ for docker swarm 2024-05-01 21:57:48 -04:00
fb09980413 Create .env.example 2024-05-01 21:42:45 -04:00
3b36cb8b3d rename_ha-proxy 2024-05-01 21:16:27 -04:00
be6a98d0bb update docker swarm stack 2024-05-01 19:24:05 -04:00
f8e1ed09d2 Merge pull request #1772 from Infisical/service-token-deprecation-notice
Add deprecation notice banner to service token section
2024-05-01 09:21:28 -07:00
5c71116be6 Add deprecation notice banner to service token section 2024-05-01 09:17:07 -07:00
07cc4fd1ab add company folder 2024-04-30 23:24:03 -07:00
ea4ef7f7ef Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-30 21:37:48 -07:00
0482424a1c Make merge user step automatic after email verification 2024-04-30 21:33:27 -07:00
74bdbc0724 Update mint.json 2024-04-30 23:30:58 -04:00
a0d5c67456 Merge pull request #1770 from Infisical/docker-swarm
add docker swarm guide
2024-04-30 22:10:56 -04:00
db4f4d8f28 add docker swarm guide 2024-04-30 22:10:11 -04:00
d6f6f51d16 Update stack.yaml 2024-04-30 21:45:00 -04:00
79a0f3d701 Merge pull request #1736 from Infisical/daniel/remove-service-tokens-docs
Feat: API Docs revamp (Service Token Deprecation)
2024-04-30 16:49:12 -07:00
46912c4c3c Update docs 2024-04-30 16:44:06 -07:00
6636377cb5 Merge remote-tracking branch 'origin' 2024-04-30 15:50:08 -07:00
26320ddce4 Temp increase secretsLimit 2024-04-30 15:49:42 -07:00
f5964040d7 Update CLI usage page 2024-04-30 15:47:24 -07:00
dcaa7f1fce fix: address functional issues with secret input 2024-05-01 03:03:40 +08:00
a4119ee1bb Merge pull request #1768 from Infisical/fix/address-infisical-secret-input-ux-issues
fix: address infisical secret input ux issue with enter and arrow keys
2024-04-30 14:33:33 -04:00
74f866715f fix: address infisical secret input ux issue with enter and arrow keys 2024-05-01 02:10:54 +08:00
667f696d26 Start updating docs 2024-04-30 08:59:02 -07:00
5f3938c33d Update overview.mdx 2024-04-29 23:20:48 -07:00
07845ad6af Merge pull request #1764 from Infisical/fix-integration-sync-import-priority
Update priority of integration sync secrets for imported secrets
2024-04-30 00:10:16 -04:00
ce2a9c8640 Rename migration file 2024-04-29 11:57:30 -07:00
ac97f273e3 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-29 11:55:53 -07:00
69c50af14e Move trust saml/ldap emails to server config 2024-04-29 11:53:28 -07:00
519403023a Pick 2024-04-28 22:04:22 -07:00
b2a976f3d4 Update groups CRUD SCIM to use orgMembershipId 2024-04-28 21:58:24 -07:00
a7af3a48d9 Continue moving SCIM userId refs to orgMembershipId 2024-04-28 19:09:12 -07:00
80da2a19aa Add TRUST_SAML_EMAILS and TRUST_LDAP_EMAILS opts 2024-04-26 22:30:07 -07:00
858a35812a Finish preliminary email validation, merge user flow w saml/ldap 2024-04-26 20:19:43 -07:00
d0cb06d875 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-26 09:08:30 -07:00
d42f620e1b Continue user aliases 2024-04-26 09:02:10 -07:00
5c0e5a8ae0 Feat: API Docs revamp (Service Token Deprecation) 2024-04-26 05:08:27 +02:00
71e309bbcb Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-25 17:03:23 -07:00
8ff407927c Continue merge user 2024-04-25 17:02:55 -07:00
d9005e8665 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-25 06:50:02 -07:00
c88923e0c6 fix: backend/package.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-MYSQL2-6670046
2024-04-22 17:59:21 +00:00
54fcc23a6c Begin groups phase 2b 2024-04-19 16:16:16 -07:00
552 changed files with 18910 additions and 17335 deletions

65
.env.example Normal file
View File

@ -0,0 +1,65 @@
# Keys
# Required key for platform encryption/decryption ops
# THIS IS A SAMPLE ENCRYPTION KEY AND SHOULD NEVER BE USED FOR PRODUCTION
ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218
# JWT
# Required secrets to sign JWT tokens
# THIS IS A SAMPLE AUTH_SECRET KEY AND SHOULD NEVER BE USED FOR PRODUCTION
AUTH_SECRET=5lrMXKKWCVocS/uerPsl7V+TX/aaUaI7iDkgl3tSmLE=
# Postgres creds
POSTGRES_PASSWORD=infisical
POSTGRES_USER=infisical
POSTGRES_DB=infisical
# Required
DB_CONNECTION_URI=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
# Redis
REDIS_URL=redis://redis:6379
# Website URL
# Required
SITE_URL=http://localhost:8080
# Mail/SMTP
SMTP_HOST=
SMTP_PORT=
SMTP_NAME=
SMTP_USERNAME=
SMTP_PASSWORD=
# Integration
# Optional only if integration is used
CLIENT_ID_HEROKU=
CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB=
CLIENT_ID_GITLAB=
CLIENT_ID_BITBUCKET=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB=
CLIENT_SECRET_GITLAB=
CLIENT_SECRET_BITBUCKET=
CLIENT_SLUG_VERCEL=
# Sentry (optional) for monitoring errors
SENTRY_DSN=
# Infisical Cloud-specific configs
# Ignore - Not applicable for self-hosted version
POSTHOG_HOST=
POSTHOG_PROJECT_API_KEY=
# SSO-specific variables
CLIENT_ID_GOOGLE_LOGIN=
CLIENT_SECRET_GOOGLE_LOGIN=
CLIENT_ID_GITHUB_LOGIN=
CLIENT_SECRET_GITHUB_LOGIN=
CLIENT_ID_GITLAB_LOGIN=
CLIENT_SECRET_GITLAB_LOGIN=

View File

@ -74,21 +74,21 @@ jobs:
uses: pr-mpt/actions-commit-hash@v2 uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition - name: Download task definition
run: | run: |
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition - name: Render Amazon ECS task definition
id: render-web-container id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1 uses: aws-actions/amazon-ecs-render-task-definition@v1
with: with:
task-definition: task-definition.json task-definition: task-definition.json
container-name: infisical-prod-platform container-name: infisical-core-platform
image: infisical/staging_infisical:${{ steps.commit.outputs.short }} image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info" environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service - name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with: with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }} task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-prod-platform service: infisical-core-platform
cluster: infisical-prod-platform cluster: infisical-core-platform
wait-for-service-stability: true wait-for-service-stability: true
production-postgres-deployment: production-postgres-deployment:
@ -122,19 +122,19 @@ jobs:
uses: pr-mpt/actions-commit-hash@v2 uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition - name: Download task definition
run: | run: |
aws ecs describe-task-definition --task-definition infisical-prod-platform --query taskDefinition > task-definition.json aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition - name: Render Amazon ECS task definition
id: render-web-container id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1 uses: aws-actions/amazon-ecs-render-task-definition@v1
with: with:
task-definition: task-definition.json task-definition: task-definition.json
container-name: infisical-prod-platform container-name: infisical-core-platform
image: infisical/staging_infisical:${{ steps.commit.outputs.short }} image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info" environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service - name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with: with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }} task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-prod-platform service: infisical-core-platform
cluster: infisical-prod-platform cluster: infisical-core-platform
wait-for-service-stability: true wait-for-service-stability: true

View File

@ -38,6 +38,16 @@ jobs:
rm added_files.txt rm added_files.txt
git commit -m "chore: renamed new migration files to latest timestamp (gh-action)" git commit -m "chore: renamed new migration files to latest timestamp (gh-action)"
- name: Get PR details
id: pr_details
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
PR_MERGER=$(curl -s "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER" | jq -r '.merged_by.login')
echo "PR Number: $PR_NUMBER"
echo "PR Merger: $PR_MERGER"
echo "pr_merger=$PR_MERGER" >> $GITHUB_OUTPUT
- name: Create Pull Request - name: Create Pull Request
if: env.SKIP_RENAME != 'true' if: env.SKIP_RENAME != 'true'
uses: peter-evans/create-pull-request@v6 uses: peter-evans/create-pull-request@v6
@ -46,3 +56,4 @@ jobs:
commit-message: 'chore: renamed new migration files to latest UTC (gh-action)' commit-message: 'chore: renamed new migration files to latest UTC (gh-action)'
title: 'GH Action: rename new migration file timestamp' title: 'GH Action: rename new migration file timestamp'
branch-suffix: timestamp branch-suffix: timestamp
reviewers: ${{ steps.pr_details.outputs.pr_merger }}

View File

@ -2,4 +2,6 @@
frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/IdentityRbacSection.tsx:generic-api-key:206 frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/IdentityRbacSection.tsx:generic-api-key:206
frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:304 frontend/src/views/Project/MembersPage/components/IdentityTab/components/IdentityRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:304
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/MemberRbacSection.tsx:generic-api-key:206 frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/MemberRbacSection.tsx:generic-api-key:206
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292 frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:292
docs/self-hosting/configuration/envars.mdx:generic-api-key:106
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451

File diff suppressed because it is too large Load Diff

View File

@ -95,11 +95,13 @@
"axios": "^1.6.7", "axios": "^1.6.7",
"axios-retry": "^4.0.0", "axios-retry": "^4.0.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"bullmq": "^5.3.3", "bullmq": "^5.4.2",
"cassandra-driver": "^4.7.2", "cassandra-driver": "^4.7.2",
"dotenv": "^16.4.1", "dotenv": "^16.4.1",
"fastify": "^4.26.0", "fastify": "^4.26.0",
"fastify-plugin": "^4.5.1", "fastify-plugin": "^4.5.1",
"google-auth-library": "^9.9.0",
"googleapis": "^137.1.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"jmespath": "^0.16.0", "jmespath": "^0.16.0",
@ -110,7 +112,7 @@
"libsodium-wrappers": "^0.7.13", "libsodium-wrappers": "^0.7.13",
"lodash.isequal": "^4.5.0", "lodash.isequal": "^4.5.0",
"ms": "^2.1.3", "ms": "^2.1.3",
"mysql2": "^3.9.4", "mysql2": "^3.9.7",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"nodemailer": "^6.9.9", "nodemailer": "^6.9.9",
"ora": "^7.0.1", "ora": "^7.0.1",

View File

@ -1,8 +1,11 @@
import "fastify"; import "fastify";
import { TUsers } from "@app/db/schemas"; 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 { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types"; 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 { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-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"; import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
import { TGroupServiceFactory } from "@app/ee/services/group/group-service"; import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
@ -29,6 +32,9 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service"; import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service"; import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { TIdentityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service"; import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service"; import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
@ -112,6 +118,11 @@ declare module "fastify" {
identityAccessToken: TIdentityAccessTokenServiceFactory; identityAccessToken: TIdentityAccessTokenServiceFactory;
identityProject: TIdentityProjectServiceFactory; identityProject: TIdentityProjectServiceFactory;
identityUa: TIdentityUaServiceFactory; identityUa: TIdentityUaServiceFactory;
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
identityGcpAuth: TIdentityGcpAuthServiceFactory;
identityAwsAuth: TIdentityAwsAuthServiceFactory;
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory; secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;
secretApprovalRequest: TSecretApprovalRequestServiceFactory; secretApprovalRequest: TSecretApprovalRequestServiceFactory;
secretRotation: TSecretRotationServiceFactory; secretRotation: TSecretRotationServiceFactory;
@ -120,6 +131,7 @@ declare module "fastify" {
scim: TScimServiceFactory; scim: TScimServiceFactory;
ldap: TLdapConfigServiceFactory; ldap: TLdapConfigServiceFactory;
auditLog: TAuditLogServiceFactory; auditLog: TAuditLogServiceFactory;
auditLogStream: TAuditLogStreamServiceFactory;
secretScanning: TSecretScanningServiceFactory; secretScanning: TSecretScanningServiceFactory;
license: TLicenseServiceFactory; license: TLicenseServiceFactory;
trustedIp: TTrustedIpServiceFactory; trustedIp: TTrustedIpServiceFactory;

View File

@ -2,11 +2,26 @@ import { Knex } from "knex";
import { import {
TableName, TableName,
TAccessApprovalPolicies,
TAccessApprovalPoliciesApprovers,
TAccessApprovalPoliciesApproversInsert,
TAccessApprovalPoliciesApproversUpdate,
TAccessApprovalPoliciesInsert,
TAccessApprovalPoliciesUpdate,
TAccessApprovalRequests,
TAccessApprovalRequestsInsert,
TAccessApprovalRequestsReviewers,
TAccessApprovalRequestsReviewersInsert,
TAccessApprovalRequestsReviewersUpdate,
TAccessApprovalRequestsUpdate,
TApiKeys, TApiKeys,
TApiKeysInsert, TApiKeysInsert,
TApiKeysUpdate, TApiKeysUpdate,
TAuditLogs, TAuditLogs,
TAuditLogsInsert, TAuditLogsInsert,
TAuditLogStreams,
TAuditLogStreamsInsert,
TAuditLogStreamsUpdate,
TAuditLogsUpdate, TAuditLogsUpdate,
TAuthTokens, TAuthTokens,
TAuthTokenSessions, TAuthTokenSessions,
@ -44,6 +59,15 @@ import {
TIdentityAccessTokens, TIdentityAccessTokens,
TIdentityAccessTokensInsert, TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate, TIdentityAccessTokensUpdate,
TIdentityAwsAuths,
TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate,
TIdentityGcpAuths,
TIdentityGcpAuthsInsert,
TIdentityGcpAuthsUpdate,
TIdentityKubernetesAuths,
TIdentityKubernetesAuthsInsert,
TIdentityKubernetesAuthsUpdate,
TIdentityOrgMemberships, TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert, TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate, TIdentityOrgMembershipsUpdate,
@ -210,6 +234,7 @@ import {
TWebhooksInsert, TWebhooksInsert,
TWebhooksUpdate TWebhooksUpdate
} from "@app/db/schemas"; } from "@app/db/schemas";
import { TSecretReferences, TSecretReferencesInsert, TSecretReferencesUpdate } from "@app/db/schemas/secret-references";
declare module "knex/types/tables" { declare module "knex/types/tables" {
interface Tables { interface Tables {
@ -283,6 +308,11 @@ declare module "knex/types/tables" {
>; >;
[TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>; [TableName.ProjectKeys]: Knex.CompositeTableType<TProjectKeys, TProjectKeysInsert, TProjectKeysUpdate>;
[TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>; [TableName.Secret]: Knex.CompositeTableType<TSecrets, TSecretsInsert, TSecretsUpdate>;
[TableName.SecretReference]: Knex.CompositeTableType<
TSecretReferences,
TSecretReferencesInsert,
TSecretReferencesUpdate
>;
[TableName.SecretBlindIndex]: Knex.CompositeTableType< [TableName.SecretBlindIndex]: Knex.CompositeTableType<
TSecretBlindIndexes, TSecretBlindIndexes,
TSecretBlindIndexesInsert, TSecretBlindIndexesInsert,
@ -311,6 +341,21 @@ declare module "knex/types/tables" {
TIdentityUniversalAuthsInsert, TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate TIdentityUniversalAuthsUpdate
>; >;
[TableName.IdentityKubernetesAuth]: Knex.CompositeTableType<
TIdentityKubernetesAuths,
TIdentityKubernetesAuthsInsert,
TIdentityKubernetesAuthsUpdate
>;
[TableName.IdentityGcpAuth]: Knex.CompositeTableType<
TIdentityGcpAuths,
TIdentityGcpAuthsInsert,
TIdentityGcpAuthsUpdate
>;
[TableName.IdentityAwsAuth]: Knex.CompositeTableType<
TIdentityAwsAuths,
TIdentityAwsAuthsInsert,
TIdentityAwsAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType< [TableName.IdentityUaClientSecret]: Knex.CompositeTableType<
TIdentityUaClientSecrets, TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert, TIdentityUaClientSecretsInsert,
@ -341,6 +386,31 @@ declare module "knex/types/tables" {
TIdentityProjectAdditionalPrivilegeInsert, TIdentityProjectAdditionalPrivilegeInsert,
TIdentityProjectAdditionalPrivilegeUpdate TIdentityProjectAdditionalPrivilegeUpdate
>; >;
[TableName.AccessApprovalPolicy]: Knex.CompositeTableType<
TAccessApprovalPolicies,
TAccessApprovalPoliciesInsert,
TAccessApprovalPoliciesUpdate
>;
[TableName.AccessApprovalPolicyApprover]: Knex.CompositeTableType<
TAccessApprovalPoliciesApprovers,
TAccessApprovalPoliciesApproversInsert,
TAccessApprovalPoliciesApproversUpdate
>;
[TableName.AccessApprovalRequest]: Knex.CompositeTableType<
TAccessApprovalRequests,
TAccessApprovalRequestsInsert,
TAccessApprovalRequestsUpdate
>;
[TableName.AccessApprovalRequestReviewer]: Knex.CompositeTableType<
TAccessApprovalRequestsReviewers,
TAccessApprovalRequestsReviewersInsert,
TAccessApprovalRequestsReviewersUpdate
>;
[TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>; [TableName.ScimToken]: Knex.CompositeTableType<TScimTokens, TScimTokensInsert, TScimTokensUpdate>;
[TableName.SecretApprovalPolicy]: Knex.CompositeTableType< [TableName.SecretApprovalPolicy]: Knex.CompositeTableType<
TSecretApprovalPolicies, TSecretApprovalPolicies,
@ -404,6 +474,11 @@ declare module "knex/types/tables" {
[TableName.LdapGroupMap]: Knex.CompositeTableType<TLdapGroupMaps, TLdapGroupMapsInsert, TLdapGroupMapsUpdate>; [TableName.LdapGroupMap]: Knex.CompositeTableType<TLdapGroupMaps, TLdapGroupMapsInsert, TLdapGroupMapsUpdate>;
[TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>; [TableName.OrgBot]: Knex.CompositeTableType<TOrgBots, TOrgBotsInsert, TOrgBotsUpdate>;
[TableName.AuditLog]: Knex.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>; [TableName.AuditLog]: Knex.CompositeTableType<TAuditLogs, TAuditLogsInsert, TAuditLogsUpdate>;
[TableName.AuditLogStream]: Knex.CompositeTableType<
TAuditLogStreams,
TAuditLogStreamsInsert,
TAuditLogStreamsUpdate
>;
[TableName.GitAppInstallSession]: Knex.CompositeTableType< [TableName.GitAppInstallSession]: Knex.CompositeTableType<
TGitAppInstallSessions, TGitAppInstallSessions,
TGitAppInstallSessionsInsert, TGitAppInstallSessionsInsert,

View File

@ -0,0 +1,28 @@
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.AuditLogStream))) {
await knex.schema.createTable(TableName.AuditLogStream, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("url").notNullable();
t.text("encryptedHeadersCiphertext");
t.text("encryptedHeadersIV");
t.text("encryptedHeadersTag");
t.string("encryptedHeadersAlgorithm");
t.string("encryptedHeadersKeyEncoding");
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.AuditLogStream);
}
export async function down(knex: Knex): Promise<void> {
await dropOnUpdateTrigger(knex, TableName.AuditLogStream);
await knex.schema.dropTableIfExists(TableName.AuditLogStream);
}

View File

@ -0,0 +1,54 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const isUsersTablePresent = await knex.schema.hasTable(TableName.Users);
if (isUsersTablePresent) {
const hasIsEmailVerifiedColumn = await knex.schema.hasColumn(TableName.Users, "isEmailVerified");
if (!hasIsEmailVerifiedColumn) {
await knex.schema.alterTable(TableName.Users, (t) => {
t.boolean("isEmailVerified").defaultTo(false);
});
}
// Backfilling the isEmailVerified to true where isAccepted is true
await knex(TableName.Users).update({ isEmailVerified: true }).where("isAccepted", true);
}
const isUserAliasTablePresent = await knex.schema.hasTable(TableName.UserAliases);
if (isUserAliasTablePresent) {
await knex.schema.alterTable(TableName.UserAliases, (t) => {
t.string("username").nullable().alter();
});
}
const isSuperAdminTablePresent = await knex.schema.hasTable(TableName.SuperAdmin);
if (isSuperAdminTablePresent) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.boolean("trustSamlEmails").defaultTo(false);
t.boolean("trustLdapEmails").defaultTo(false);
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.Users, "isEmailVerified")) {
await knex.schema.alterTable(TableName.Users, (t) => {
t.dropColumn("isEmailVerified");
});
}
if (await knex.schema.hasColumn(TableName.SuperAdmin, "trustSamlEmails")) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.dropColumn("trustSamlEmails");
});
}
if (await knex.schema.hasColumn(TableName.SuperAdmin, "trustLdapEmails")) {
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
t.dropColumn("trustLdapEmails");
});
}
}

View File

@ -0,0 +1,41 @@
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.AccessApprovalPolicy))) {
await knex.schema.createTable(TableName.AccessApprovalPolicy, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.integer("approvals").defaultTo(1).notNullable();
t.string("secretPath");
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicy);
}
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover))) {
await knex.schema.createTable(TableName.AccessApprovalPolicyApprover, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("approverId").notNullable();
t.foreign("approverId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyApprover);
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicyApprover);
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicy);
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicyApprover);
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicy);
}

View File

@ -0,0 +1,51 @@
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.AccessApprovalRequest))) {
await knex.schema.createTable(TableName.AccessApprovalRequest, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
t.uuid("privilegeId").nullable();
t.foreign("privilegeId").references("id").inTable(TableName.ProjectUserAdditionalPrivilege).onDelete("CASCADE");
t.uuid("requestedBy").notNullable();
t.foreign("requestedBy").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
// We use these values to create the actual privilege at a later point in time.
t.boolean("isTemporary").notNullable();
t.string("temporaryRange").nullable();
t.jsonb("permissions").notNullable();
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.AccessApprovalRequest);
if (!(await knex.schema.hasTable(TableName.AccessApprovalRequestReviewer))) {
await knex.schema.createTable(TableName.AccessApprovalRequestReviewer, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("member").notNullable();
t.foreign("member").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.string("status").notNullable();
t.uuid("requestId").notNullable();
t.foreign("requestId").references("id").inTable(TableName.AccessApprovalRequest).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.AccessApprovalRequestReviewer);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.AccessApprovalRequestReviewer);
await knex.schema.dropTableIfExists(TableName.AccessApprovalRequest);
await dropOnUpdateTrigger(knex, TableName.AccessApprovalRequestReviewer);
await dropOnUpdateTrigger(knex, TableName.AccessApprovalRequest);
}

View File

@ -0,0 +1,30 @@
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.IdentityAwsAuth))) {
await knex.schema.createTable(TableName.IdentityAwsAuth, (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("stsEndpoint").notNullable();
t.string("allowedPrincipalArns").notNullable();
t.string("allowedAccountIds").notNullable();
});
}
await createOnUpdateTrigger(knex, TableName.IdentityAwsAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAwsAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityAwsAuth);
}

View File

@ -0,0 +1,30 @@
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.IdentityGcpAuth))) {
await knex.schema.createTable(TableName.IdentityGcpAuth, (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("allowedServiceAccounts").notNullable();
t.string("allowedProjects").notNullable();
t.string("allowedZones").notNullable(); // GCE only (fully qualified zone names)
});
}
await createOnUpdateTrigger(knex, TableName.IdentityGcpAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityGcpAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityGcpAuth);
}

View File

@ -0,0 +1,24 @@
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.SecretReference))) {
await knex.schema.createTable(TableName.SecretReference, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("environment").notNullable();
t.string("secretPath").notNullable();
t.uuid("secretId").notNullable();
t.foreign("secretId").references("id").inTable(TableName.Secret).onDelete("CASCADE");
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.SecretReference);
}
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.SecretReference);
await dropOnUpdateTrigger(knex, TableName.SecretReference);
}

View File

@ -0,0 +1,36 @@
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.IdentityKubernetesAuth))) {
await knex.schema.createTable(TableName.IdentityKubernetesAuth, (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("kubernetesHost").notNullable();
t.text("encryptedCaCert").notNullable();
t.string("caCertIV").notNullable();
t.string("caCertTag").notNullable();
t.text("encryptedTokenReviewerJwt").notNullable();
t.string("tokenReviewerJwtIV").notNullable();
t.string("tokenReviewerJwtTag").notNullable();
t.string("allowedNamespaces").notNullable();
t.string("allowedNames").notNullable();
t.string("allowedAudience").notNullable();
});
}
await createOnUpdateTrigger(knex, TableName.IdentityKubernetesAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityKubernetesAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityKubernetesAuth);
}

View File

@ -0,0 +1,43 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced");
const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage");
const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId");
await knex.schema.alterTable(TableName.Integration, (t) => {
if (!hasIsSyncedColumn) {
t.boolean("isSynced").nullable();
}
if (!hasSyncMessageColumn) {
t.text("syncMessage").nullable();
}
if (!hasLastSyncJobId) {
t.string("lastSyncJobId").nullable();
}
});
}
export async function down(knex: Knex): Promise<void> {
const hasIsSyncedColumn = await knex.schema.hasColumn(TableName.Integration, "isSynced");
const hasSyncMessageColumn = await knex.schema.hasColumn(TableName.Integration, "syncMessage");
const hasLastSyncJobId = await knex.schema.hasColumn(TableName.Integration, "lastSyncJobId");
await knex.schema.alterTable(TableName.Integration, (t) => {
if (hasIsSyncedColumn) {
t.dropColumn("isSynced");
}
if (hasSyncMessageColumn) {
t.dropColumn("syncMessage");
}
if (hasLastSyncJobId) {
t.dropColumn("lastSyncJobId");
}
});
}

View File

@ -0,0 +1,26 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesProjectIdExist) t.index("projectId");
if (doesOrgIdExist) t.index("orgId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesProjectIdExist) t.dropIndex("projectId");
if (doesOrgIdExist) t.dropIndex("orgId");
});
}
}

View File

@ -0,0 +1,22 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesEnvIdExist) t.index("envId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "envId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesEnvIdExist) t.dropIndex("envId");
});
}
}

View File

@ -0,0 +1,22 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
if (await knex.schema.hasTable(TableName.SecretVersion)) {
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
if (doesEnvIdExist) t.index("envId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesEnvIdExist = await knex.schema.hasColumn(TableName.SecretVersion, "envId");
if (await knex.schema.hasTable(TableName.SecretVersion)) {
await knex.schema.alterTable(TableName.SecretVersion, (t) => {
if (doesEnvIdExist) t.dropIndex("envId");
});
}
}

View File

@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesSnapshotIdExist) t.index("snapshotId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotSecret, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotSecret)) {
await knex.schema.alterTable(TableName.SnapshotSecret, (t) => {
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
});
}
}

View File

@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
if (doesSnapshotIdExist) t.index("snapshotId");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesSnapshotIdExist = await knex.schema.hasColumn(TableName.SnapshotFolder, "snapshotId");
if (await knex.schema.hasTable(TableName.SnapshotFolder)) {
await knex.schema.alterTable(TableName.SnapshotFolder, (t) => {
if (doesSnapshotIdExist) t.dropIndex("snapshotId");
});
}
}

View File

@ -0,0 +1,24 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
if (await knex.schema.hasTable(TableName.Secret)) {
await knex.schema.alterTable(TableName.Secret, (t) => {
if (doesFolderIdExist && doesUserIdExist) t.index(["folderId", "userId"]);
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesFolderIdExist = await knex.schema.hasColumn(TableName.Secret, "folderId");
const doesUserIdExist = await knex.schema.hasColumn(TableName.Secret, "userId");
if (await knex.schema.hasTable(TableName.Secret)) {
await knex.schema.alterTable(TableName.Secret, (t) => {
if (doesUserIdExist && doesFolderIdExist) t.dropIndex(["folderId", "userId"]);
});
}
}

View File

@ -0,0 +1,22 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesExpireAtExist) t.index("expiresAt");
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesExpireAtExist = await knex.schema.hasColumn(TableName.AuditLog, "expiresAt");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesExpireAtExist) t.dropIndex("expiresAt");
});
}
}

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 AccessApprovalPoliciesApproversSchema = z.object({
id: z.string().uuid(),
approverId: z.string().uuid(),
policyId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
export type TAccessApprovalPoliciesApproversInsert = Omit<
z.input<typeof AccessApprovalPoliciesApproversSchema>,
TImmutableDBKeys
>;
export type TAccessApprovalPoliciesApproversUpdate = Partial<
Omit<z.input<typeof AccessApprovalPoliciesApproversSchema>, TImmutableDBKeys>
>;

View File

@ -7,16 +7,18 @@ import { z } from "zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const SecretApprovalPoliciesSchema = z.object({ export const AccessApprovalPoliciesSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
name: z.string(), name: z.string(),
secretPath: z.string().nullable().optional(),
approvals: z.number().default(1), approvals: z.number().default(1),
secretPath: z.string().nullable().optional(),
envId: z.string().uuid(), envId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date()
}); });
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>; export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
export type TSecretApprovalPoliciesInsert = Omit<TSecretApprovalPolicies, TImmutableDBKeys>; export type TAccessApprovalPoliciesInsert = Omit<z.input<typeof AccessApprovalPoliciesSchema>, TImmutableDBKeys>;
export type TSecretApprovalPoliciesUpdate = Partial<Omit<TSecretApprovalPolicies, TImmutableDBKeys>>; export type TAccessApprovalPoliciesUpdate = Partial<
Omit<z.input<typeof AccessApprovalPoliciesSchema>, TImmutableDBKeys>
>;

View File

@ -0,0 +1,26 @@
// 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 AccessApprovalRequestsReviewersSchema = z.object({
id: z.string().uuid(),
member: z.string().uuid(),
status: z.string(),
requestId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAccessApprovalRequestsReviewers = z.infer<typeof AccessApprovalRequestsReviewersSchema>;
export type TAccessApprovalRequestsReviewersInsert = Omit<
z.input<typeof AccessApprovalRequestsReviewersSchema>,
TImmutableDBKeys
>;
export type TAccessApprovalRequestsReviewersUpdate = Partial<
Omit<z.input<typeof AccessApprovalRequestsReviewersSchema>, TImmutableDBKeys>
>;

View File

@ -0,0 +1,26 @@
// 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 AccessApprovalRequestsSchema = z.object({
id: z.string().uuid(),
policyId: z.string().uuid(),
privilegeId: z.string().uuid().nullable().optional(),
requestedBy: z.string().uuid(),
isTemporary: z.boolean(),
temporaryRange: z.string().nullable().optional(),
permissions: z.unknown(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;
export type TAccessApprovalRequestsInsert = Omit<z.input<typeof AccessApprovalRequestsSchema>, TImmutableDBKeys>;
export type TAccessApprovalRequestsUpdate = Partial<
Omit<z.input<typeof AccessApprovalRequestsSchema>, TImmutableDBKeys>
>;

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 AuditLogStreamsSchema = z.object({
id: z.string().uuid(),
url: z.string(),
encryptedHeadersCiphertext: z.string().nullable().optional(),
encryptedHeadersIV: z.string().nullable().optional(),
encryptedHeadersTag: z.string().nullable().optional(),
encryptedHeadersAlgorithm: z.string().nullable().optional(),
encryptedHeadersKeyEncoding: z.string().nullable().optional(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAuditLogStreams = z.infer<typeof AuditLogStreamsSchema>;
export type TAuditLogStreamsInsert = Omit<z.input<typeof AuditLogStreamsSchema>, TImmutableDBKeys>;
export type TAuditLogStreamsUpdate = Partial<Omit<z.input<typeof AuditLogStreamsSchema>, TImmutableDBKeys>>;

View File

@ -0,0 +1,27 @@
// 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 IdentityAwsAuthsSchema = 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(),
stsEndpoint: z.string(),
allowedPrincipalArns: z.string(),
allowedAccountIds: z.string()
});
export type TIdentityAwsAuths = z.infer<typeof IdentityAwsAuthsSchema>;
export type TIdentityAwsAuthsInsert = Omit<z.input<typeof IdentityAwsAuthsSchema>, TImmutableDBKeys>;
export type TIdentityAwsAuthsUpdate = Partial<Omit<z.input<typeof IdentityAwsAuthsSchema>, TImmutableDBKeys>>;

View File

@ -0,0 +1,27 @@
// 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 IdentityGcpAuthsSchema = 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(),
allowedServiceAccounts: z.string(),
allowedProjects: z.string(),
allowedZones: z.string()
});
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
export type TIdentityGcpAuthsInsert = Omit<z.input<typeof IdentityGcpAuthsSchema>, TImmutableDBKeys>;
export type TIdentityGcpAuthsUpdate = Partial<Omit<z.input<typeof IdentityGcpAuthsSchema>, TImmutableDBKeys>>;

View File

@ -0,0 +1,35 @@
// 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 IdentityKubernetesAuthsSchema = 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(),
kubernetesHost: z.string(),
encryptedCaCert: z.string(),
caCertIV: z.string(),
caCertTag: z.string(),
encryptedTokenReviewerJwt: z.string(),
tokenReviewerJwtIV: z.string(),
tokenReviewerJwtTag: z.string(),
allowedNamespaces: z.string(),
allowedNames: z.string(),
allowedAudience: z.string()
});
export type TIdentityKubernetesAuths = z.infer<typeof IdentityKubernetesAuthsSchema>;
export type TIdentityKubernetesAuthsInsert = Omit<z.input<typeof IdentityKubernetesAuthsSchema>, TImmutableDBKeys>;
export type TIdentityKubernetesAuthsUpdate = Partial<
Omit<z.input<typeof IdentityKubernetesAuthsSchema>, TImmutableDBKeys>
>;

View File

@ -1,4 +1,9 @@
export * from "./access-approval-policies";
export * from "./access-approval-policies-approvers";
export * from "./access-approval-requests";
export * from "./access-approval-requests-reviewers";
export * from "./api-keys"; export * from "./api-keys";
export * from "./audit-log-streams";
export * from "./audit-logs"; export * from "./audit-logs";
export * from "./auth-token-sessions"; export * from "./auth-token-sessions";
export * from "./auth-tokens"; export * from "./auth-tokens";
@ -12,6 +17,9 @@ export * from "./group-project-memberships";
export * from "./groups"; export * from "./groups";
export * from "./identities"; export * from "./identities";
export * from "./identity-access-tokens"; export * from "./identity-access-tokens";
export * from "./identity-aws-auths";
export * from "./identity-gcp-auths";
export * from "./identity-kubernetes-auths";
export * from "./identity-org-memberships"; export * from "./identity-org-memberships";
export * from "./identity-project-additional-privilege"; export * from "./identity-project-additional-privilege";
export * from "./identity-project-membership-role"; export * from "./identity-project-membership-role";

View File

@ -28,7 +28,10 @@ export const IntegrationsSchema = z.object({
secretPath: z.string().default("/"), secretPath: z.string().default("/"),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
lastUsed: z.date().nullable().optional() lastUsed: z.date().nullable().optional(),
isSynced: z.boolean().nullable().optional(),
syncMessage: z.string().nullable().optional(),
lastSyncJobId: z.string().nullable().optional()
}); });
export type TIntegrations = z.infer<typeof IntegrationsSchema>; export type TIntegrations = z.infer<typeof IntegrationsSchema>;

View File

@ -28,6 +28,7 @@ export enum TableName {
ProjectUserMembershipRole = "project_user_membership_roles", ProjectUserMembershipRole = "project_user_membership_roles",
ProjectKeys = "project_keys", ProjectKeys = "project_keys",
Secret = "secrets", Secret = "secrets",
SecretReference = "secret_references",
SecretBlindIndex = "secret_blind_indexes", SecretBlindIndex = "secret_blind_indexes",
SecretVersion = "secret_versions", SecretVersion = "secret_versions",
SecretFolder = "secret_folders", SecretFolder = "secret_folders",
@ -44,12 +45,19 @@ export enum TableName {
Identity = "identities", Identity = "identities",
IdentityAccessToken = "identity_access_tokens", IdentityAccessToken = "identity_access_tokens",
IdentityUniversalAuth = "identity_universal_auths", IdentityUniversalAuth = "identity_universal_auths",
IdentityKubernetesAuth = "identity_kubernetes_auths",
IdentityGcpAuth = "identity_gcp_auths",
IdentityUaClientSecret = "identity_ua_client_secrets", IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityAwsAuth = "identity_aws_auths",
IdentityOrgMembership = "identity_org_memberships", IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships", IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role", IdentityProjectMembershipRole = "identity_project_membership_role",
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege", IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
ScimToken = "scim_tokens", ScimToken = "scim_tokens",
AccessApprovalPolicy = "access_approval_policies",
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
AccessApprovalRequest = "access_approval_requests",
AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
SecretApprovalPolicy = "secret_approval_policies", SecretApprovalPolicy = "secret_approval_policies",
SecretApprovalPolicyApprover = "secret_approval_policies_approvers", SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
SecretApprovalRequest = "secret_approval_requests", SecretApprovalRequest = "secret_approval_requests",
@ -62,6 +70,7 @@ export enum TableName {
LdapConfig = "ldap_configs", LdapConfig = "ldap_configs",
LdapGroupMap = "ldap_group_maps", LdapGroupMap = "ldap_group_maps",
AuditLog = "audit_logs", AuditLog = "audit_logs",
AuditLogStream = "audit_log_streams",
GitAppInstallSession = "git_app_install_sessions", GitAppInstallSession = "git_app_install_sessions",
GitAppOrg = "git_app_org", GitAppOrg = "git_app_org",
SecretScanningGitRisk = "secret_scanning_git_risks", SecretScanningGitRisk = "secret_scanning_git_risks",
@ -137,5 +146,8 @@ export enum ProjectUpgradeStatus {
} }
export enum IdentityAuthMethod { export enum IdentityAuthMethod {
Univeral = "universal-auth" Univeral = "universal-auth",
KUBERNETES_AUTH = "kubernetes-auth",
GCP_AUTH = "gcp-auth",
AWS_AUTH = "aws-auth"
} }

View File

@ -0,0 +1,21 @@
// 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 SecretReferencesSchema = z.object({
id: z.string().uuid(),
environment: z.string(),
secretPath: z.string(),
secretId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TSecretReferences = z.infer<typeof SecretReferencesSchema>;
export type TSecretReferencesInsert = Omit<z.input<typeof SecretReferencesSchema>, TImmutableDBKeys>;
export type TSecretReferencesUpdate = Partial<Omit<z.input<typeof SecretReferencesSchema>, TImmutableDBKeys>>;

View File

@ -14,7 +14,9 @@ export const SuperAdminSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
allowedSignUpDomain: z.string().nullable().optional(), allowedSignUpDomain: z.string().nullable().optional(),
instanceId: z.string().uuid().default("00000000-0000-0000-0000-000000000000") instanceId: z.string().uuid().default("00000000-0000-0000-0000-000000000000"),
trustSamlEmails: z.boolean().default(false).nullable().optional(),
trustLdapEmails: z.boolean().default(false).nullable().optional()
}); });
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>; export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;

View File

@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
export const UserAliasesSchema = z.object({ export const UserAliasesSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
userId: z.string().uuid(), userId: z.string().uuid(),
username: z.string(), username: z.string().nullable().optional(),
aliasType: z.string(), aliasType: z.string(),
externalId: z.string(), externalId: z.string(),
emails: z.string().array().nullable().optional(), emails: z.string().array().nullable().optional(),

View File

@ -21,7 +21,8 @@ export const UsersSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
isGhost: z.boolean().default(false), isGhost: z.boolean().default(false),
username: z.string() username: z.string(),
isEmailVerified: z.boolean().default(false).nullable().optional()
}); });
export type TUsers = z.infer<typeof UsersSchema>; export type TUsers = z.infer<typeof UsersSchema>;

View File

@ -0,0 +1,168 @@
import { nanoid } from "nanoid";
import { z } from "zod";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z
.object({
projectSlug: z.string().trim(),
name: z.string().optional(),
secretPath: z.string().trim().default("/"),
environment: z.string(),
approvers: z.string().array().min(1),
approvals: z.number().min(1).default(1)
})
.refine((data) => data.approvals <= data.approvers.length, {
path: ["approvals"],
message: "The number of approvals should be lower than the number of approvers."
}),
response: {
200: z.object({
approval: sapPubSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const approval = await server.services.accessApprovalPolicy.createAccessApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
projectSlug: req.body.projectSlug,
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
});
return { approval };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
projectSlug: z.string().trim()
}),
response: {
200: z.object({
approvals: sapPubSchema.extend({ approvers: z.string().array(), secretPath: z.string().optional() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const approvals = await server.services.accessApprovalPolicy.getAccessApprovalPolicyByProjectSlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectSlug: req.query.projectSlug
});
return { approvals };
}
});
server.route({
url: "/count",
method: "GET",
schema: {
querystring: z.object({
projectSlug: z.string(),
envSlug: z.string()
}),
response: {
200: z.object({
count: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { count } = await server.services.accessApprovalPolicy.getAccessPolicyCountByEnvSlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
projectSlug: req.query.projectSlug,
actorOrgId: req.permission.orgId,
envSlug: req.query.envSlug
});
return { count };
}
});
server.route({
url: "/:policyId",
method: "PATCH",
schema: {
params: z.object({
policyId: z.string()
}),
body: z
.object({
name: z.string().optional(),
secretPath: z
.string()
.trim()
.optional()
.transform((val) => (val === "" ? "/" : val)),
approvers: z.string().array().min(1),
approvals: z.number().min(1).default(1)
})
.refine((data) => data.approvals <= data.approvers.length, {
path: ["approvals"],
message: "The number of approvals should be lower than the number of approvers."
}),
response: {
200: z.object({
approval: sapPubSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.accessApprovalPolicy.updateAccessApprovalPolicy({
policyId: req.params.policyId,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
...req.body
});
}
});
server.route({
url: "/:policyId",
method: "DELETE",
schema: {
params: z.object({
policyId: z.string()
}),
response: {
200: z.object({
approval: sapPubSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const approval = await server.services.accessApprovalPolicy.deleteAccessApprovalPolicy({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
policyId: req.params.policyId
});
return { approval };
}
});
};

View File

@ -0,0 +1,160 @@
import { z } from "zod";
import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema } from "@app/db/schemas";
import { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerAccessApprovalRequestRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
schema: {
body: z.object({
permissions: z.any().array(),
isTemporary: z.boolean(),
temporaryRange: z.string().optional()
}),
querystring: z.object({
projectSlug: z.string().trim()
}),
response: {
200: z.object({
approval: AccessApprovalRequestsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { request } = await server.services.accessApprovalRequest.createAccessApprovalRequest({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
permissions: req.body.permissions,
actorOrgId: req.permission.orgId,
projectSlug: req.query.projectSlug,
temporaryRange: req.body.temporaryRange,
isTemporary: req.body.isTemporary
});
return { approval: request };
}
});
server.route({
url: "/count",
method: "GET",
schema: {
querystring: z.object({
projectSlug: z.string().trim()
}),
response: {
200: z.object({
pendingCount: z.number(),
finalizedCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { count } = await server.services.accessApprovalRequest.getCount({
projectSlug: req.query.projectSlug,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
return { ...count };
}
});
server.route({
url: "/",
method: "GET",
schema: {
querystring: z.object({
projectSlug: z.string().trim(),
authorProjectMembershipId: z.string().trim().optional(),
envSlug: z.string().trim().optional()
}),
response: {
200: z.object({
requests: AccessApprovalRequestsSchema.extend({
environmentName: z.string(),
isApproved: z.boolean(),
privilege: z
.object({
membershipId: z.string(),
isTemporary: z.boolean(),
temporaryMode: z.string().nullish(),
temporaryRange: z.string().nullish(),
temporaryAccessStartTime: z.date().nullish(),
temporaryAccessEndTime: z.date().nullish(),
permissions: z.unknown()
})
.nullable(),
policy: z.object({
id: z.string(),
name: z.string(),
approvals: z.number(),
approvers: z.string().array(),
secretPath: z.string().nullish(),
envId: z.string()
}),
reviewers: z
.object({
member: z.string(),
status: z.string()
})
.array()
}).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { requests } = await server.services.accessApprovalRequest.listApprovalRequests({
projectSlug: req.query.projectSlug,
authorProjectMembershipId: req.query.authorProjectMembershipId,
envSlug: req.query.envSlug,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
return { requests };
}
});
server.route({
url: "/:requestId/review",
method: "POST",
schema: {
params: z.object({
requestId: z.string().trim()
}),
body: z.object({
status: z.enum([ApprovalStatus.APPROVED, ApprovalStatus.REJECTED])
}),
response: {
200: z.object({
review: AccessApprovalRequestsReviewersSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const review = await server.services.accessApprovalRequest.reviewAccessRequest({
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
requestId: req.params.requestId,
status: req.body.status
});
return { review };
}
});
};

View File

@ -0,0 +1,215 @@
import { z } from "zod";
import { AUDIT_LOG_STREAMS } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { SanitizedAuditLogStreamSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type";
export const registerAuditLogStreamRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
description: "Create an Audit Log Stream.",
security: [
{
bearerAuth: []
}
],
body: z.object({
url: z.string().min(1).describe(AUDIT_LOG_STREAMS.CREATE.url),
headers: z
.object({
key: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.CREATE.headers.key),
value: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.CREATE.headers.value)
})
.describe(AUDIT_LOG_STREAMS.CREATE.headers.desc)
.array()
.optional()
}),
response: {
200: z.object({
auditLogStream: SanitizedAuditLogStreamSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const auditLogStream = await server.services.auditLogStream.create({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
url: req.body.url,
headers: req.body.headers
});
return { auditLogStream };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
description: "Update an Audit Log Stream by ID.",
security: [
{
bearerAuth: []
}
],
params: z.object({
id: z.string().describe(AUDIT_LOG_STREAMS.UPDATE.id)
}),
body: z.object({
url: z.string().optional().describe(AUDIT_LOG_STREAMS.UPDATE.url),
headers: z
.object({
key: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.UPDATE.headers.key),
value: z.string().min(1).trim().describe(AUDIT_LOG_STREAMS.UPDATE.headers.value)
})
.describe(AUDIT_LOG_STREAMS.UPDATE.headers.desc)
.array()
.optional()
}),
response: {
200: z.object({
auditLogStream: SanitizedAuditLogStreamSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const auditLogStream = await server.services.auditLogStream.updateById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.id,
url: req.body.url,
headers: req.body.headers
});
return { auditLogStream };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
description: "Delete an Audit Log Stream by ID.",
security: [
{
bearerAuth: []
}
],
params: z.object({
id: z.string().describe(AUDIT_LOG_STREAMS.DELETE.id)
}),
response: {
200: z.object({
auditLogStream: SanitizedAuditLogStreamSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const auditLogStream = await server.services.auditLogStream.deleteById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.id
});
return { auditLogStream };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
description: "Get an Audit Log Stream by ID.",
security: [
{
bearerAuth: []
}
],
params: z.object({
id: z.string().describe(AUDIT_LOG_STREAMS.GET_BY_ID.id)
}),
response: {
200: z.object({
auditLogStream: SanitizedAuditLogStreamSchema.extend({
headers: z
.object({
key: z.string(),
value: z.string()
})
.array()
.optional()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const auditLogStream = await server.services.auditLogStream.getById({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.id
});
return { auditLogStream };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
description: "List Audit Log Streams.",
security: [
{
bearerAuth: []
}
],
response: {
200: z.object({
auditLogStreams: SanitizedAuditLogStreamSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const auditLogStreams = await server.services.auditLogStream.list({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
return { auditLogStreams };
}
});
};

View File

@ -1,16 +1,14 @@
import { MongoAbility, RawRuleOf } from "@casl/ability"; import { packRules } from "@casl/ability/extra";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import ms from "ms"; import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types"; import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
import { ProjectPermissionSet } from "@app/ee/services/permission/project-permission";
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs"; import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ProjectPermissionSchema, SanitizedIdentityPrivilegeSchema } from "@app/server/routes/sanitizedSchemas";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => { export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
@ -41,11 +39,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}) })
.optional() .optional()
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug), .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions) permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions)
}), }),
response: { response: {
200: z.object({ 200: z.object({
privilege: IdentityProjectAdditionalPrivilegeSchema privilege: SanitizedIdentityPrivilegeSchema
}) })
} }
}, },
@ -92,7 +90,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}) })
.optional() .optional()
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug), .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.slug),
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions), permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.permissions),
temporaryMode: z temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) .nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode), .describe(IDENTITY_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
@ -107,7 +105,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}), }),
response: { response: {
200: z.object({ 200: z.object({
privilege: IdentityProjectAdditionalPrivilegeSchema privilege: SanitizedIdentityPrivilegeSchema
}) })
} }
}, },
@ -157,7 +155,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
message: "Slug must be a valid slug" message: "Slug must be a valid slug"
}) })
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug), .describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.newSlug),
permissions: z.any().array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions), permissions: ProjectPermissionSchema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary), isTemporary: z.boolean().describe(IDENTITY_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
temporaryMode: z temporaryMode: z
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode) .nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
@ -175,7 +173,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}), }),
response: { response: {
200: z.object({ 200: z.object({
privilege: IdentityProjectAdditionalPrivilegeSchema privilege: SanitizedIdentityPrivilegeSchema
}) })
} }
}, },
@ -219,7 +217,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}), }),
response: { response: {
200: z.object({ 200: z.object({
privilege: IdentityProjectAdditionalPrivilegeSchema privilege: SanitizedIdentityPrivilegeSchema
}) })
} }
}, },
@ -260,7 +258,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
}), }),
response: { response: {
200: z.object({ 200: z.object({
privilege: IdentityProjectAdditionalPrivilegeSchema privilege: SanitizedIdentityPrivilegeSchema
}) })
} }
}, },
@ -293,16 +291,11 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
], ],
querystring: z.object({ querystring: z.object({
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.identityId), identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.identityId),
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.projectSlug), projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.projectSlug)
unpacked: z
.enum(["false", "true"])
.transform((el) => el === "true")
.default("true")
.describe(IDENTITY_ADDITIONAL_PRIVILEGE.LIST.unpacked)
}), }),
response: { response: {
200: z.object({ 200: z.object({
privileges: IdentityProjectAdditionalPrivilegeSchema.array() privileges: SanitizedIdentityPrivilegeSchema.array()
}) })
} }
}, },
@ -315,15 +308,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
...req.query ...req.query
}); });
if (req.query.unpacked) { return {
return { privileges
privileges: privileges.map(({ permissions, ...el }) => ({ };
...el,
permissions: unpackRules(permissions as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
}))
};
}
return { privileges };
} }
}); });
}; };

View File

@ -1,3 +1,6 @@
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router"; import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
import { registerDynamicSecretRouter } from "./dynamic-secret-router"; import { registerDynamicSecretRouter } from "./dynamic-secret-router";
import { registerGroupRouter } from "./group-router"; import { registerGroupRouter } from "./group-router";
@ -40,6 +43,9 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
prefix: "/secret-rotation-providers" prefix: "/secret-rotation-providers"
}); });
await server.register(registerAccessApprovalPolicyRouter, { prefix: "/access-approvals/policies" });
await server.register(registerAccessApprovalRequestRouter, { prefix: "/access-approvals/requests" });
await server.register( await server.register(
async (dynamicSecretRouter) => { async (dynamicSecretRouter) => {
await dynamicSecretRouter.register(registerDynamicSecretRouter); await dynamicSecretRouter.register(registerDynamicSecretRouter);
@ -55,6 +61,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" }); await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
await server.register(registerSecretVersionRouter, { prefix: "/secret" }); await server.register(registerSecretVersionRouter, { prefix: "/secret" });
await server.register(registerGroupRouter, { prefix: "/groups" }); await server.register(registerGroupRouter, { prefix: "/groups" });
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
await server.register( await server.register(
async (privilegeRouter) => { async (privilegeRouter) => {
await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" }); await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" });

View File

@ -18,6 +18,7 @@ import { LdapConfigsSchema, LdapGroupMapsSchema } from "@app/db/schemas";
import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types"; import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types";
import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns"; import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@ -52,6 +53,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
// eslint-disable-next-line // eslint-disable-next-line
async (req: IncomingMessage, user, cb) => { async (req: IncomingMessage, user, cb) => {
try { try {
if (!user.email) throw new BadRequestError({ message: "Invalid request. Missing email." });
const ldapConfig = (req as unknown as FastifyRequest).ldapConfig as TLDAPConfig; const ldapConfig = (req as unknown as FastifyRequest).ldapConfig as TLDAPConfig;
let groups: { dn: string; cn: string }[] | undefined; let groups: { dn: string; cn: string }[] | undefined;
@ -74,7 +76,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
username: user.uid, username: user.uid,
firstName: user.givenName ?? user.cn ?? "", firstName: user.givenName ?? user.cn ?? "",
lastName: user.sn ?? "", lastName: user.sn ?? "",
emails: user.mail ? [user.mail] : [], email: user.mail,
groups, groups,
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState, relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
orgId: (req as unknown as FastifyRequest).ldapConfig.organization orgId: (req as unknown as FastifyRequest).ldapConfig.organization

View File

@ -102,12 +102,12 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
if (!profile) throw new BadRequestError({ message: "Missing profile" }); if (!profile) throw new BadRequestError({ message: "Missing profile" });
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
if (!profile.email || !profile.firstName) { if (!email || !profile.firstName) {
throw new BadRequestError({ message: "Invalid request. Missing email or first name" }); throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
} }
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({ const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
username: profile.nameID ?? email, externalId: profile.nameID,
email, email,
firstName: profile.firstName as string, firstName: profile.firstName as string,
lastName: profile.lastName as string, lastName: profile.lastName as string,

View File

@ -153,7 +153,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => { handler: async (req) => {
const users = await req.server.services.scim.listScimUsers({ const users = await req.server.services.scim.listScimUsers({
offset: req.query.startIndex, startIndex: req.query.startIndex,
limit: req.query.count, limit: req.query.count,
filter: req.query.filter, filter: req.query.filter,
orgId: req.permission.orgId orgId: req.permission.orgId
@ -163,11 +163,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}); });
server.route({ server.route({
url: "/Users/:userId", url: "/Users/:orgMembershipId",
method: "GET", method: "GET",
schema: { schema: {
params: z.object({ params: z.object({
userId: z.string().trim() orgMembershipId: z.string().trim()
}), }),
response: { response: {
201: z.object({ 201: z.object({
@ -193,7 +193,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => { handler: async (req) => {
const user = await req.server.services.scim.getScimUser({ const user = await req.server.services.scim.getScimUser({
userId: req.params.userId, orgMembershipId: req.params.orgMembershipId,
orgId: req.permission.orgId orgId: req.permission.orgId
}); });
return user; return user;
@ -249,7 +249,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
const primaryEmail = req.body.emails?.find((email) => email.primary)?.value; const primaryEmail = req.body.emails?.find((email) => email.primary)?.value;
const user = await req.server.services.scim.createScimUser({ const user = await req.server.services.scim.createScimUser({
username: req.body.userName, externalId: req.body.userName,
email: primaryEmail, email: primaryEmail,
firstName: req.body.name.givenName, firstName: req.body.name.givenName,
lastName: req.body.name.familyName, lastName: req.body.name.familyName,
@ -261,11 +261,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}); });
server.route({ server.route({
url: "/Users/:userId", url: "/Users/:orgMembershipId",
method: "DELETE", method: "DELETE",
schema: { schema: {
params: z.object({ params: z.object({
userId: z.string().trim() orgMembershipId: z.string().trim()
}), }),
response: { response: {
200: z.object({}) 200: z.object({})
@ -274,7 +274,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => { handler: async (req) => {
const user = await req.server.services.scim.deleteScimUser({ const user = await req.server.services.scim.deleteScimUser({
userId: req.params.userId, orgMembershipId: req.params.orgMembershipId,
orgId: req.permission.orgId orgId: req.permission.orgId
}); });
@ -361,7 +361,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
handler: async (req) => { handler: async (req) => {
const groups = await req.server.services.scim.listScimGroups({ const groups = await req.server.services.scim.listScimGroups({
orgId: req.permission.orgId, orgId: req.permission.orgId,
offset: req.query.startIndex, startIndex: req.query.startIndex,
limit: req.query.count limit: req.query.count
}); });
@ -416,10 +416,10 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
displayName: z.string().trim(), displayName: z.string().trim(),
members: z.array( members: z.array(
z.object({ z.object({
value: z.string(), // infisical userId value: z.string(), // infisical orgMembershipId
display: z.string() display: z.string()
}) })
) // note: is this where members are added to group? )
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -534,11 +534,11 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
}); });
server.route({ server.route({
url: "/Users/:userId", url: "/Users/:orgMembershipId",
method: "PUT", method: "PUT",
schema: { schema: {
params: z.object({ params: z.object({
userId: z.string().trim() orgMembershipId: z.string().trim()
}), }),
body: z.object({ body: z.object({
schemas: z.array(z.string()), schemas: z.array(z.string()),
@ -575,7 +575,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.SCIM_TOKEN]), onRequest: verifyAuth([AuthMode.SCIM_TOKEN]),
handler: async (req) => { handler: async (req) => {
const user = await req.server.services.scim.replaceScimUser({ const user = await req.server.services.scim.replaceScimUser({
userId: req.params.userId, orgMembershipId: req.params.orgMembershipId,
orgId: req.permission.orgId, orgId: req.permission.orgId,
active: req.body.active active: req.body.active
}); });

View File

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

View File

@ -0,0 +1,76 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TAccessApprovalPolicies } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, mergeOneToManyRelation, ormify, selectAllTableCols, TFindFilter } from "@app/lib/knex";
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
const accessApprovalPolicyOrm = ormify(db, TableName.AccessApprovalPolicy);
const accessApprovalPolicyFindQuery = async (tx: Knex, filter: TFindFilter<TAccessApprovalPolicies>) => {
const result = await tx(TableName.AccessApprovalPolicy)
// eslint-disable-next-line
.where(buildFindFilter(filter))
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.join(
TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.select(tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
.select(tx.ref("projectId").withSchema(TableName.Environment))
.select(selectAllTableCols(TableName.AccessApprovalPolicy));
return result;
};
const findById = async (id: string, tx?: Knex) => {
try {
const doc = await accessApprovalPolicyFindQuery(tx || db, {
[`${TableName.AccessApprovalPolicy}.id` as "id"]: id
});
const formatedDoc = mergeOneToManyRelation(
doc,
"id",
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
...el,
envId,
environment: { id: envId, name, slug }
}),
({ approverId }) => approverId,
"approvers"
);
return formatedDoc?.[0];
} catch (error) {
throw new DatabaseError({ error, name: "FindById" });
}
};
const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => {
try {
const docs = await accessApprovalPolicyFindQuery(tx || db, filter);
const formatedDoc = mergeOneToManyRelation(
docs,
"id",
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
...el,
envId,
environment: { id: envId, name, slug }
}),
({ approverId }) => approverId,
"approvers"
);
return formatedDoc.map((policy) => ({ ...policy, secretPath: policy.secretPath || undefined }));
} catch (error) {
throw new DatabaseError({ error, name: "Find" });
}
};
return { ...accessApprovalPolicyOrm, find, findById };
};

View File

@ -0,0 +1,36 @@
import { ForbiddenError, subject } from "@casl/ability";
import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TVerifyApprovers } from "./access-approval-policy-types";
export const verifyApprovers = async ({
userIds,
projectId,
orgId,
envSlug,
actorAuthMethod,
secretPath,
permissionService
}: TVerifyApprovers) => {
for await (const userId of userIds) {
try {
const { permission: approverPermission } = await permissionService.getProjectPermission(
ActorType.USER,
userId,
projectId,
actorAuthMethod,
orgId
);
ForbiddenError.from(approverPermission).throwUnlessCan(
ProjectPermissionActions.Create,
subject(ProjectPermissionSub.Secrets, { environment: envSlug, secretPath })
);
} catch (err) {
throw new BadRequestError({ message: "One or more approvers doesn't have access to be specified secret path" });
}
}
};

View File

@ -0,0 +1,273 @@
import { ForbiddenError } from "@casl/ability";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
import { verifyApprovers } from "./access-approval-policy-fns";
import {
TCreateAccessApprovalPolicy,
TDeleteAccessApprovalPolicy,
TGetAccessPolicyCountByEnvironmentDTO,
TListAccessApprovalPoliciesDTO,
TUpdateAccessApprovalPolicy
} from "./access-approval-policy-types";
type TSecretApprovalPolicyServiceFactoryDep = {
projectDAL: TProjectDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory;
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">;
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
};
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
export const accessApprovalPolicyServiceFactory = ({
accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL,
permissionService,
projectEnvDAL,
projectDAL,
projectMembershipDAL
}: TSecretApprovalPolicyServiceFactoryDep) => {
const createAccessApprovalPolicy = async ({
name,
actor,
actorId,
actorOrgId,
secretPath,
actorAuthMethod,
approvals,
approvers,
projectSlug,
environment
}: TCreateAccessApprovalPolicy) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
if (approvals > approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval
);
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
if (!env) throw new BadRequestError({ message: "Environment not found" });
const secretApprovers = await projectMembershipDAL.find({
projectId: project.id,
$in: { id: approvers }
});
if (secretApprovers.length !== approvers.length) {
throw new BadRequestError({ message: "Approver not found in project" });
}
await verifyApprovers({
projectId: project.id,
orgId: actorOrgId,
envSlug: environment,
secretPath,
actorAuthMethod,
permissionService,
userIds: secretApprovers.map((approver) => approver.userId)
});
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
const doc = await accessApprovalPolicyDAL.create(
{
envId: env.id,
approvals,
secretPath,
name
},
tx
);
await accessApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({
approverId: id,
policyId: doc.id
})),
tx
);
return doc;
});
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 BadRequestError({ message: "Project not found" });
// Anyone in the project should be able to get the policies.
/* const { permission } = */ await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
// ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id });
return accessApprovalPolicies;
};
const updateAccessApprovalPolicy = async ({
policyId,
approvers,
secretPath,
name,
actorId,
actor,
actorOrgId,
actorAuthMethod,
approvals
}: TUpdateAccessApprovalPolicy) => {
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
accessApprovalPolicy.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
const doc = await accessApprovalPolicyDAL.updateById(
accessApprovalPolicy.id,
{
approvals,
secretPath,
name
},
tx
);
if (approvers) {
// Find the workspace project memberships of the users passed in the approvers array
const secretApprovers = await projectMembershipDAL.find(
{
projectId: accessApprovalPolicy.projectId,
$in: { id: approvers }
},
{ tx }
);
await verifyApprovers({
projectId: accessApprovalPolicy.projectId,
orgId: actorOrgId,
envSlug: accessApprovalPolicy.environment.slug,
secretPath: doc.secretPath!,
actorAuthMethod,
permissionService,
userIds: secretApprovers.map((approver) => approver.userId)
});
if (secretApprovers.length !== approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
await accessApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({
approverId: id,
policyId: doc.id
})),
tx
);
}
return doc;
});
return {
...updatedPolicy,
environment: accessApprovalPolicy.environment,
projectId: accessApprovalPolicy.projectId
};
};
const deleteAccessApprovalPolicy = async ({
policyId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TDeleteAccessApprovalPolicy) => {
const policy = await accessApprovalPolicyDAL.findById(policyId);
if (!policy) throw new BadRequestError({ message: "Secret approval policy not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
policy.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
);
await accessApprovalPolicyDAL.deleteById(policyId);
return policy;
};
const getAccessPolicyCountByEnvSlug = async ({
actor,
actorOrgId,
actorAuthMethod,
projectSlug,
actorId,
envSlug
}: TGetAccessPolicyCountByEnvironmentDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" });
const { membership } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
if (!membership) throw new BadRequestError({ message: "User not found in project" });
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
if (!environment) throw new BadRequestError({ message: "Environment not found" });
const policies = await accessApprovalPolicyDAL.find({ envId: environment.id, projectId: project.id });
if (!policies) throw new BadRequestError({ message: "No policies found" });
return { count: policies.length };
};
return {
getAccessPolicyCountByEnvSlug,
createAccessApprovalPolicy,
deleteAccessApprovalPolicy,
updateAccessApprovalPolicy,
getAccessApprovalPolicyByProjectSlug
};
};

View File

@ -0,0 +1,44 @@
import { TProjectPermission } from "@app/lib/types";
import { ActorAuthMethod } from "@app/services/auth/auth-type";
import { TPermissionServiceFactory } from "../permission/permission-service";
export type TVerifyApprovers = {
userIds: string[];
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
envSlug: string;
actorAuthMethod: ActorAuthMethod;
secretPath: string;
projectId: string;
orgId: string;
};
export type TCreateAccessApprovalPolicy = {
approvals: number;
secretPath: string;
environment: string;
approvers: string[];
projectSlug: string;
name: string;
} & Omit<TProjectPermission, "projectId">;
export type TUpdateAccessApprovalPolicy = {
policyId: string;
approvals?: number;
approvers?: string[];
secretPath?: string;
name?: string;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteAccessApprovalPolicy = {
policyId: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetAccessPolicyCountByEnvironmentDTO = {
envSlug: string;
projectSlug: string;
} & Omit<TProjectPermission, "projectId">;
export type TListAccessApprovalPoliciesDTO = {
projectSlug: string;
} & Omit<TProjectPermission, "projectId">;

View File

@ -0,0 +1,266 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
import { ApprovalStatus } from "./access-approval-request-types";
export type TAccessApprovalRequestDALFactory = ReturnType<typeof accessApprovalRequestDALFactory>;
export const accessApprovalRequestDALFactory = (db: TDbClient) => {
const accessApprovalRequestOrm = ormify(db, TableName.AccessApprovalRequest);
const findRequestsWithPrivilegeByPolicyIds = async (policyIds: string[]) => {
try {
const docs = await db(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.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.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("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
)
.select(db.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(
db.ref("projectId").withSchema(TableName.Environment),
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
db.ref("name").withSchema(TableName.Environment).as("envName")
)
.select(
db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"),
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
)
.select(
db
.ref("projectMembershipId")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeMembershipId"),
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,
policy: {
id: doc.policyId,
name: doc.policyName,
approvals: doc.policyApprovals,
secretPath: doc.policySecretPath,
envId: doc.policyEnvId
},
privilege: doc.privilegeId
? {
membershipId: doc.privilegeMembershipId,
isTemporary: doc.privilegeIsTemporary,
temporaryMode: doc.privilegeTemporaryMode,
temporaryRange: doc.privilegeTemporaryRange,
temporaryAccessStartTime: doc.privilegeTemporaryAccessStartTime,
temporaryAccessEndTime: doc.privilegeTemporaryAccessEndTime,
permissions: doc.privilegePermissions
}
: null,
isApproved: !!doc.privilegeId
}),
childrenMapper: [
{
key: "reviewerMemberId",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
},
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
]
});
if (!formattedDocs) return [];
return formattedDocs.map((doc) => ({
...doc,
policy: { ...doc.policy, approvers: doc.approvers }
}));
} catch (error) {
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
}
};
const findQuery = (filter: TFindFilter<TAccessApprovalRequests>, tx: Knex) =>
tx(TableName.AccessApprovalRequest)
.where(filter)
.join(
TableName.AccessApprovalPolicy,
`${TableName.AccessApprovalRequest}.policyId`,
`${TableName.AccessApprovalPolicy}.id`
)
.join(
TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.leftJoin(
TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(
tx.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"),
tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"),
tx.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
tx.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
tx.ref("projectId").withSchema(TableName.Environment),
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
);
const findById = async (id: string, tx?: Knex) => {
try {
const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db);
const docs = await sql;
const formatedDoc = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (el) => ({
...AccessApprovalRequestsSchema.parse(el),
projectId: el.projectId,
environment: el.environment,
policy: {
id: el.policyId,
name: el.policyName,
approvals: el.policyApprovals,
secretPath: el.policySecretPath
}
}),
childrenMapper: [
{
key: "reviewerMemberId",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
},
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
]
});
if (!formatedDoc?.[0]) return;
return {
...formatedDoc[0],
policy: { ...formatedDoc[0].policy, approvers: formatedDoc[0].approvers }
};
} catch (error) {
throw new DatabaseError({ error, name: "FindByIdAccessApprovalRequest" });
}
};
const getCount = async ({ projectId }: { projectId: string }) => {
try {
const accessRequests = await db(TableName.AccessApprovalRequest)
.leftJoin(
TableName.AccessApprovalPolicy,
`${TableName.AccessApprovalRequest}.policyId`,
`${TableName.AccessApprovalPolicy}.id`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
`${TableName.AccessApprovalRequest}.privilegeId`,
`${TableName.ProjectUserAdditionalPrivilege}.id`
)
.leftJoin(
TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId`
)
.where(`${TableName.Environment}.projectId`, projectId)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
.select(db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"));
const formattedRequests = sqlNestRelationships({
data: accessRequests,
key: "id",
parentMapper: (doc) => ({
...AccessApprovalRequestsSchema.parse(doc)
}),
childrenMapper: [
{
key: "reviewerMemberId",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
}
]
});
// an approval is pending if there is no reviewer rejections and no privilege ID is set
const pendingApprovals = formattedRequests.filter(
(req) => !req.privilegeId && !req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
);
// an approval is finalized if there are any rejections or a privilege ID is set
const finalizedApprovals = formattedRequests.filter(
(req) => req.privilegeId || req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
);
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };
} catch (error) {
throw new DatabaseError({ error, name: "GetCountAccessApprovalRequest" });
}
};
return { ...accessApprovalRequestOrm, findById, findRequestsWithPrivilegeByPolicyIds, getCount };
};

View File

@ -0,0 +1,53 @@
import { PackRule, unpackRules } from "@casl/ability/extra";
import { UnauthorizedError } from "@app/lib/errors";
import { TVerifyPermission } from "./access-approval-request-types";
function filterUnique(value: string, index: number, array: string[]) {
return array.indexOf(value) === index;
}
export const verifyRequestedPermissions = ({ permissions }: TVerifyPermission) => {
const permission = unpackRules(
permissions as PackRule<{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
conditions?: Record<string, any>;
action: string;
subject: [string];
}>[]
);
if (!permission || !permission.length) {
throw new UnauthorizedError({ message: "No permission provided" });
}
const requestedPermissions: string[] = [];
for (const p of permission) {
if (p.action[0] === "read") requestedPermissions.push("Read Access");
if (p.action[0] === "create") requestedPermissions.push("Create Access");
if (p.action[0] === "delete") requestedPermissions.push("Delete Access");
if (p.action[0] === "edit") requestedPermissions.push("Edit Access");
}
const firstPermission = permission[0];
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const permissionSecretPath = firstPermission.conditions?.secretPath?.$glob;
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-unsafe-assignment
const permissionEnv = firstPermission.conditions?.environment;
if (!permissionEnv || typeof permissionEnv !== "string") {
throw new UnauthorizedError({ message: "Permission environment is not a string" });
}
if (!permissionSecretPath || typeof permissionSecretPath !== "string") {
throw new UnauthorizedError({ message: "Permission path is not a string" });
}
return {
envSlug: permissionEnv,
secretPath: permissionSecretPath,
accessTypes: requestedPermissions.filter(filterUnique)
};
};

View File

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

View File

@ -0,0 +1,369 @@
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
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 { verifyApprovers } from "../access-approval-policy/access-approval-policy-fns";
import { TPermissionServiceFactory } from "../permission/permission-service";
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";
type TSecretApprovalRequestServiceFactoryDep = {
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "create" | "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
accessApprovalPolicyApproverDAL: Pick<TAccessApprovalPolicyApproverDALFactory, "find">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findProjectBySlug">;
accessApprovalRequestDAL: Pick<
TAccessApprovalRequestDALFactory,
| "create"
| "find"
| "findRequestsWithPrivilegeByPolicyIds"
| "findById"
| "transaction"
| "updateById"
| "findOne"
| "getCount"
>;
accessApprovalPolicyDAL: Pick<TAccessApprovalPolicyDALFactory, "findOne" | "find">;
accessApprovalRequestReviewerDAL: Pick<
TAccessApprovalRequestReviewerDALFactory,
"create" | "find" | "findOne" | "transaction"
>;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "findUserByProjectMembershipId" | "findUsersByProjectMembershipIds">;
};
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
export const accessApprovalRequestServiceFactory = ({
projectDAL,
projectEnvDAL,
permissionService,
accessApprovalRequestDAL,
accessApprovalRequestReviewerDAL,
projectMembershipDAL,
accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL,
additionalPrivilegeDAL,
smtpService,
userDAL
}: TSecretApprovalRequestServiceFactoryDep) => {
const createAccessApprovalRequest = async ({
isTemporary,
temporaryRange,
actorId,
permissions: requestedPermissions,
actor,
actorOrgId,
actorAuthMethod,
projectSlug
}: TCreateAccessApprovalRequestDTO) => {
const cfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new UnauthorizedError({ message: "Project not found" });
// Anyone can create an access approval request.
const { membership } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
const requestedByUser = await userDAL.findUserByProjectMembershipId(membership.id);
if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" });
await projectDAL.checkProjectUpgradeStatus(project.id);
const { envSlug, secretPath, accessTypes } = verifyRequestedPermissions({ permissions: requestedPermissions });
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
if (!environment) throw new UnauthorizedError({ message: "Environment not found" });
const policy = await accessApprovalPolicyDAL.findOne({
envId: environment.id,
secretPath
});
if (!policy) throw new UnauthorizedError({ message: "No policy matching criteria was found." });
const approvers = await accessApprovalPolicyApproverDAL.find({
policyId: policy.id
});
const approverUsers = await userDAL.findUsersByProjectMembershipIds(
approvers.map((approver) => approver.approverId)
);
const duplicateRequests = await accessApprovalRequestDAL.find({
policyId: policy.id,
requestedBy: membership.id,
permissions: JSON.stringify(requestedPermissions),
isTemporary
});
if (duplicateRequests?.length > 0) {
for await (const duplicateRequest of duplicateRequests) {
if (duplicateRequest.privilegeId) {
const privilege = await additionalPrivilegeDAL.findById(duplicateRequest.privilegeId);
const isExpired = new Date() > new Date(privilege.temporaryAccessEndTime || ("" as string));
if (!isExpired || !privilege.isTemporary) {
throw new BadRequestError({ message: "You already have an active privilege with the same criteria" });
}
} else {
const reviewers = await accessApprovalRequestReviewerDAL.find({
requestId: duplicateRequest.id
});
const isRejected = reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED);
if (!isRejected) {
throw new BadRequestError({ message: "You already have a pending access request with the same criteria" });
}
}
}
}
const approval = await accessApprovalRequestDAL.transaction(async (tx) => {
const approvalRequest = await accessApprovalRequestDAL.create(
{
policyId: policy.id,
requestedBy: membership.id,
temporaryRange: temporaryRange || null,
permissions: JSON.stringify(requestedPermissions),
isTemporary
},
tx
);
await smtpService.sendMail({
recipients: approverUsers.filter((approver) => approver.email).map((approver) => approver.email!),
subjectLine: "Access Approval Request",
substitutions: {
projectName: project.name,
requesterFullName: `${requestedByUser.firstName} ${requestedByUser.lastName}`,
requesterEmail: requestedByUser.email,
isTemporary,
...(isTemporary && {
expiresIn: ms(ms(temporaryRange || ""), { long: true })
}),
secretPath,
environment: envSlug,
permissions: accessTypes,
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval`
},
template: SmtpTemplates.AccessApprovalRequest
});
return approvalRequest;
});
return { request: approval };
};
const listApprovalRequests = async ({
projectSlug,
authorProjectMembershipId,
envSlug,
actor,
actorOrgId,
actorId,
actorAuthMethod
}: TListApprovalRequestsDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new UnauthorizedError({ message: "Project not found" });
const { membership } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
if (authorProjectMembershipId) {
requests = requests.filter((request) => request.requestedBy === authorProjectMembershipId);
}
if (envSlug) {
requests = requests.filter((request) => request.environment === envSlug);
}
return { requests };
};
const reviewAccessRequest = async ({
requestId,
actor,
status,
actorId,
actorAuthMethod,
actorOrgId
}: TReviewAccessRequestDTO) => {
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
if (!accessApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
const { policy } = accessApprovalRequest;
const { membership, hasRole } = await permissionService.getProjectPermission(
actor,
actorId,
accessApprovalRequest.projectId,
actorAuthMethod,
actorOrgId
);
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
if (
!hasRole(ProjectMembershipRole.Admin) &&
accessApprovalRequest.requestedBy !== membership.id && // The request wasn't made by the current user
!policy.approvers.find((approverId) => approverId === membership.id) // The request isn't performed by an assigned approver
) {
throw new UnauthorizedError({ message: "You are not authorized to approve this request" });
}
const reviewerProjectMembership = await projectMembershipDAL.findById(membership.id);
await verifyApprovers({
projectId: accessApprovalRequest.projectId,
orgId: actorOrgId,
envSlug: accessApprovalRequest.environment,
secretPath: accessApprovalRequest.policy.secretPath!,
actorAuthMethod,
permissionService,
userIds: [reviewerProjectMembership.userId]
});
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" });
}
const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => {
const review = await accessApprovalRequestReviewerDAL.findOne(
{
requestId: accessApprovalRequest.id,
member: membership.id
},
tx
);
if (!review) {
const newReview = await accessApprovalRequestReviewerDAL.create(
{
status,
requestId: accessApprovalRequest.id,
member: membership.id
},
tx
);
const allReviews = [...existingReviews, newReview];
const approvedReviews = allReviews.filter((r) => r.status === ApprovalStatus.APPROVED);
// approvals is the required number of approvals. If the number of approved reviews is equal to the number of required approvals, then the request is approved.
if (approvedReviews.length === policy.approvals) {
if (accessApprovalRequest.isTemporary && !accessApprovalRequest.temporaryRange) {
throw new BadRequestError({ message: "Temporary range is required for temporary access" });
}
let privilegeId: string | null = null;
if (!accessApprovalRequest.isTemporary && !accessApprovalRequest.temporaryRange) {
// Permanent access
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.requestedBy,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions)
},
tx
);
privilegeId = privilege.id;
} else {
// Temporary access
const relativeTempAllocatedTimeInMs = ms(accessApprovalRequest.temporaryRange!);
const startTime = new Date();
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.requestedBy,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions),
isTemporary: true,
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: accessApprovalRequest.temporaryRange!,
temporaryAccessStartTime: startTime,
temporaryAccessEndTime: new Date(new Date(startTime).getTime() + relativeTempAllocatedTimeInMs)
},
tx
);
privilegeId = privilege.id;
}
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { privilegeId }, tx);
}
return newReview;
}
throw new BadRequestError({ message: "You have already reviewed this request" });
});
return reviewStatus;
};
const getCount = async ({ projectSlug, actor, actorAuthMethod, actorId, actorOrgId }: TGetAccessRequestCountDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new UnauthorizedError({ message: "Project not found" });
const { membership } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
if (!membership) throw new BadRequestError({ message: "User not found in project" });
const count = await accessApprovalRequestDAL.getCount({ projectId: project.id });
return { count };
};
return {
createAccessApprovalRequest,
listApprovalRequests,
reviewAccessRequest,
getCount
};
};

View File

@ -0,0 +1,33 @@
import { TProjectPermission } from "@app/lib/types";
export enum ApprovalStatus {
PENDING = "pending",
APPROVED = "approved",
REJECTED = "rejected"
}
export type TVerifyPermission = {
permissions: unknown;
};
export type TGetAccessRequestCountDTO = {
projectSlug: string;
} & Omit<TProjectPermission, "projectId">;
export type TReviewAccessRequestDTO = {
requestId: string;
status: ApprovalStatus;
} & Omit<TProjectPermission, "projectId">;
export type TCreateAccessApprovalRequestDTO = {
projectSlug: string;
permissions: unknown;
isTemporary: boolean;
temporaryRange?: string;
} & Omit<TProjectPermission, "projectId">;
export type TListApprovalRequestsDTO = {
projectSlug: string;
authorProjectMembershipId?: string;
envSlug?: string;
} & Omit<TProjectPermission, "projectId">;

View File

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

View File

@ -0,0 +1,233 @@
import { ForbiddenError } from "@casl/ability";
import { RawAxiosRequestHeaders } from "axios";
import { SecretKeyEncoding } from "@app/db/schemas";
import { request } from "@app/lib/config/request";
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors";
import { validateLocalIps } 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 { TAuditLogStreamDALFactory } from "./audit-log-stream-dal";
import {
LogStreamHeaders,
TCreateAuditLogStreamDTO,
TDeleteAuditLogStreamDTO,
TGetDetailsAuditLogStreamDTO,
TListAuditLogStreamDTO,
TUpdateAuditLogStreamDTO
} from "./audit-log-stream-types";
type TAuditLogStreamServiceFactoryDep = {
auditLogStreamDAL: TAuditLogStreamDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
};
export type TAuditLogStreamServiceFactory = ReturnType<typeof auditLogStreamServiceFactory>;
export const auditLogStreamServiceFactory = ({
auditLogStreamDAL,
permissionService,
licenseService
}: TAuditLogStreamServiceFactoryDep) => {
const create = async ({
url,
actor,
headers = [],
actorId,
actorOrgId,
actorAuthMethod
}: TCreateAuditLogStreamDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.auditLogStreams)
throw new BadRequestError({
message: "Failed to create audit log streams due to plan restriction. Upgrade plan to create group."
});
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
validateLocalIps(url);
const totalStreams = await auditLogStreamDAL.find({ orgId: actorOrgId });
if (totalStreams.length >= plan.auditLogStreamLimit) {
throw new BadRequestError({
message:
"Failed to create audit log streams due to plan limit reached. Kindly contact Infisical to add more streams."
});
}
// testing connection first
const streamHeaders: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
if (headers.length)
headers.forEach(({ key, value }) => {
streamHeaders[key] = value;
});
await request
.post(
url,
{ ping: "ok" },
{
headers: streamHeaders,
// request timeout
timeout: AUDIT_LOG_STREAM_TIMEOUT,
// connection timeout
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
}
)
.catch((err) => {
throw new Error(`Failed to connect with the source ${(err as Error)?.message}`);
});
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
const logStream = await auditLogStreamDAL.create({
orgId: actorOrgId,
url,
...(encryptedHeaders
? {
encryptedHeadersCiphertext: encryptedHeaders.ciphertext,
encryptedHeadersIV: encryptedHeaders.iv,
encryptedHeadersTag: encryptedHeaders.tag,
encryptedHeadersAlgorithm: encryptedHeaders.algorithm,
encryptedHeadersKeyEncoding: encryptedHeaders.encoding
}
: {})
});
return logStream;
};
const updateById = async ({
id,
url,
actor,
headers = [],
actorId,
actorOrgId,
actorAuthMethod
}: TUpdateAuditLogStreamDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.auditLogStreams)
throw new BadRequestError({
message: "Failed to update audit log streams due to plan restriction. Upgrade plan to create group."
});
const logStream = await auditLogStreamDAL.findById(id);
if (!logStream) throw new BadRequestError({ message: "Audit log stream not found" });
const { orgId } = logStream;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
if (url) validateLocalIps(url);
// testing connection first
const streamHeaders: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
if (headers.length)
headers.forEach(({ key, value }) => {
streamHeaders[key] = value;
});
await request
.post(
url || logStream.url,
{ ping: "ok" },
{
headers: streamHeaders,
// request timeout
timeout: AUDIT_LOG_STREAM_TIMEOUT,
// connection timeout
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
}
)
.catch((err) => {
throw new Error(`Failed to connect with the source ${(err as Error)?.message}`);
});
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
const updatedLogStream = await auditLogStreamDAL.updateById(id, {
url,
...(encryptedHeaders
? {
encryptedHeadersCiphertext: encryptedHeaders.ciphertext,
encryptedHeadersIV: encryptedHeaders.iv,
encryptedHeadersTag: encryptedHeaders.tag,
encryptedHeadersAlgorithm: encryptedHeaders.algorithm,
encryptedHeadersKeyEncoding: encryptedHeaders.encoding
}
: {})
});
return updatedLogStream;
};
const deleteById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TDeleteAuditLogStreamDTO) => {
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
const logStream = await auditLogStreamDAL.findById(id);
if (!logStream) throw new BadRequestError({ message: "Audit log stream not found" });
const { orgId } = logStream;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Settings);
const deletedLogStream = await auditLogStreamDAL.deleteById(id);
return deletedLogStream;
};
const getById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetDetailsAuditLogStreamDTO) => {
const logStream = await auditLogStreamDAL.findById(id);
if (!logStream) throw new BadRequestError({ message: "Audit log stream not found" });
const { orgId } = logStream;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
const headers =
logStream?.encryptedHeadersCiphertext && logStream?.encryptedHeadersIV && logStream?.encryptedHeadersTag
? (JSON.parse(
infisicalSymmetricDecrypt({
tag: logStream.encryptedHeadersTag,
iv: logStream.encryptedHeadersIV,
ciphertext: logStream.encryptedHeadersCiphertext,
keyEncoding: logStream.encryptedHeadersKeyEncoding as SecretKeyEncoding
})
) as LogStreamHeaders[])
: undefined;
return { ...logStream, headers };
};
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListAuditLogStreamDTO) => {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
const logStreams = await auditLogStreamDAL.find({ orgId: actorOrgId });
return logStreams;
};
return {
create,
updateById,
deleteById,
getById,
list
};
};

View File

@ -0,0 +1,27 @@
import { TOrgPermission } from "@app/lib/types";
export type LogStreamHeaders = {
key: string;
value: string;
};
export type TCreateAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
url: string;
headers?: LogStreamHeaders[];
};
export type TUpdateAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
id: string;
url?: string;
headers?: LogStreamHeaders[];
};
export type TDeleteAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
id: string;
};
export type TListAuditLogStreamDTO = Omit<TOrgPermission, "orgId">;
export type TGetDetailsAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
id: string;
};

View File

@ -1,13 +1,21 @@
import { RawAxiosRequestHeaders } from "axios";
import { SecretKeyEncoding } from "@app/db/schemas";
import { request } from "@app/lib/config/request";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TAuditLogStreamDALFactory } from "../audit-log-stream/audit-log-stream-dal";
import { LogStreamHeaders } from "../audit-log-stream/audit-log-stream-types";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { TAuditLogDALFactory } from "./audit-log-dal"; import { TAuditLogDALFactory } from "./audit-log-dal";
import { TCreateAuditLogDTO } from "./audit-log-types"; import { TCreateAuditLogDTO } from "./audit-log-types";
type TAuditLogQueueServiceFactoryDep = { type TAuditLogQueueServiceFactoryDep = {
auditLogDAL: TAuditLogDALFactory; auditLogDAL: TAuditLogDALFactory;
auditLogStreamDAL: Pick<TAuditLogStreamDALFactory, "find">;
queueService: TQueueServiceFactory; queueService: TQueueServiceFactory;
projectDAL: Pick<TProjectDALFactory, "findById">; projectDAL: Pick<TProjectDALFactory, "findById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
@ -15,11 +23,15 @@ type TAuditLogQueueServiceFactoryDep = {
export type TAuditLogQueueServiceFactory = ReturnType<typeof auditLogQueueServiceFactory>; export type TAuditLogQueueServiceFactory = ReturnType<typeof auditLogQueueServiceFactory>;
// 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
export const AUDIT_LOG_STREAM_TIMEOUT = 5 * 1000;
export const auditLogQueueServiceFactory = ({ export const auditLogQueueServiceFactory = ({
auditLogDAL, auditLogDAL,
queueService, queueService,
projectDAL, projectDAL,
licenseService licenseService,
auditLogStreamDAL
}: TAuditLogQueueServiceFactoryDep) => { }: TAuditLogQueueServiceFactoryDep) => {
const pushToLog = async (data: TCreateAuditLogDTO) => { const pushToLog = async (data: TCreateAuditLogDTO) => {
await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, { await queueService.queue(QueueName.AuditLog, QueueJobs.AuditLog, data, {
@ -47,7 +59,7 @@ export const auditLogQueueServiceFactory = ({
// skip inserting if audit log retention is 0 meaning its not supported // skip inserting if audit log retention is 0 meaning its not supported
if (ttl === 0) return; if (ttl === 0) return;
await auditLogDAL.create({ const auditLog = await auditLogDAL.create({
actor: actor.type, actor: actor.type,
actorMetadata: actor.metadata, actorMetadata: actor.metadata,
userAgent, userAgent,
@ -59,6 +71,46 @@ export const auditLogQueueServiceFactory = ({
eventMetadata: event.metadata, eventMetadata: event.metadata,
userAgentType userAgentType
}); });
const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
await Promise.allSettled(
logStreams.map(
async ({
url,
encryptedHeadersTag,
encryptedHeadersIV,
encryptedHeadersKeyEncoding,
encryptedHeadersCiphertext
}) => {
const streamHeaders =
encryptedHeadersIV && encryptedHeadersCiphertext && encryptedHeadersTag
? (JSON.parse(
infisicalSymmetricDecrypt({
keyEncoding: encryptedHeadersKeyEncoding as SecretKeyEncoding,
iv: encryptedHeadersIV,
tag: encryptedHeadersTag,
ciphertext: encryptedHeadersCiphertext
})
) as LogStreamHeaders[])
: [];
const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
if (streamHeaders.length)
streamHeaders.forEach(({ key, value }) => {
headers[key] = value;
});
return request.post(url, auditLog, {
headers,
// request timeout
timeout: AUDIT_LOG_STREAM_TIMEOUT,
// connection timeout
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
});
}
)
);
}); });
queueService.start(QueueName.AuditLogPrune, async () => { queueService.start(QueueName.AuditLogPrune, async () => {

View File

@ -51,6 +51,7 @@ export enum EventType {
UNAUTHORIZE_INTEGRATION = "unauthorize-integration", UNAUTHORIZE_INTEGRATION = "unauthorize-integration",
CREATE_INTEGRATION = "create-integration", CREATE_INTEGRATION = "create-integration",
DELETE_INTEGRATION = "delete-integration", DELETE_INTEGRATION = "delete-integration",
MANUAL_SYNC_INTEGRATION = "manual-sync-integration",
ADD_TRUSTED_IP = "add-trusted-ip", ADD_TRUSTED_IP = "add-trusted-ip",
UPDATE_TRUSTED_IP = "update-trusted-ip", UPDATE_TRUSTED_IP = "update-trusted-ip",
DELETE_TRUSTED_IP = "delete-trusted-ip", DELETE_TRUSTED_IP = "delete-trusted-ip",
@ -63,9 +64,21 @@ export enum EventType {
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth", ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth", UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth", GET_IDENTITY_UNIVERSAL_AUTH = "get-identity-universal-auth",
LOGIN_IDENTITY_KUBERNETES_AUTH = "login-identity-kubernetes-auth",
ADD_IDENTITY_KUBERNETES_AUTH = "add-identity-kubernetes-auth",
UPDATE_IDENTITY_KUBENETES_AUTH = "update-identity-kubernetes-auth",
GET_IDENTITY_KUBERNETES_AUTH = "get-identity-kubernetes-auth",
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret", CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret", REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret", GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
LOGIN_IDENTITY_GCP_AUTH = "login-identity-gcp-auth",
ADD_IDENTITY_GCP_AUTH = "add-identity-gcp-auth",
UPDATE_IDENTITY_GCP_AUTH = "update-identity-gcp-auth",
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-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",
GET_IDENTITY_AWS_AUTH = "get-identity-aws-auth",
CREATE_ENVIRONMENT = "create-environment", CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment", UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment", DELETE_ENVIRONMENT = "delete-environment",
@ -269,6 +282,25 @@ interface DeleteIntegrationEvent {
}; };
} }
interface ManualSyncIntegrationEvent {
type: EventType.MANUAL_SYNC_INTEGRATION;
metadata: {
integrationId: string;
integration: string;
environment: string;
secretPath: string;
url?: string;
app?: string;
appId?: string;
targetEnvironment?: string;
targetEnvironmentId?: string;
targetService?: string;
targetServiceId?: string;
path?: string;
region?: string;
};
}
interface AddTrustedIPEvent { interface AddTrustedIPEvent {
type: EventType.ADD_TRUSTED_IP; type: EventType.ADD_TRUSTED_IP;
metadata: { metadata: {
@ -383,6 +415,50 @@ interface GetIdentityUniversalAuthEvent {
}; };
} }
interface LoginIdentityKubernetesAuthEvent {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH;
metadata: {
identityId: string;
identityKubernetesAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityKubernetesAuthEvent {
type: EventType.ADD_IDENTITY_KUBERNETES_AUTH;
metadata: {
identityId: string;
kubernetesHost: string;
allowedNamespaces: string;
allowedNames: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface UpdateIdentityKubernetesAuthEvent {
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH;
metadata: {
identityId: string;
kubernetesHost?: string;
allowedNamespaces?: string;
allowedNames?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityKubernetesAuthEvent {
type: EventType.GET_IDENTITY_KUBERNETES_AUTH;
metadata: {
identityId: string;
};
}
interface CreateIdentityUniversalAuthClientSecretEvent { interface CreateIdentityUniversalAuthClientSecretEvent {
type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET; type: EventType.CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET;
metadata: { metadata: {
@ -406,6 +482,96 @@ interface RevokeIdentityUniversalAuthClientSecretEvent {
}; };
} }
interface LoginIdentityGcpAuthEvent {
type: EventType.LOGIN_IDENTITY_GCP_AUTH;
metadata: {
identityId: string;
identityGcpAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityGcpAuthEvent {
type: EventType.ADD_IDENTITY_GCP_AUTH;
metadata: {
identityId: string;
type: string;
allowedServiceAccounts: string;
allowedProjects: string;
allowedZones: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface UpdateIdentityGcpAuthEvent {
type: EventType.UPDATE_IDENTITY_GCP_AUTH;
metadata: {
identityId: string;
type?: string;
allowedServiceAccounts?: string;
allowedProjects?: string;
allowedZones?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityGcpAuthEvent {
type: EventType.GET_IDENTITY_GCP_AUTH;
metadata: {
identityId: string;
};
}
interface LoginIdentityAwsAuthEvent {
type: EventType.LOGIN_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
identityAwsAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityAwsAuthEvent {
type: EventType.ADD_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
stsEndpoint: string;
allowedPrincipalArns: string;
allowedAccountIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface UpdateIdentityAwsAuthEvent {
type: EventType.UPDATE_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
stsEndpoint?: string;
allowedPrincipalArns?: string;
allowedAccountIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityAwsAuthEvent {
type: EventType.GET_IDENTITY_AWS_AUTH;
metadata: {
identityId: string;
};
}
interface CreateEnvironmentEvent { interface CreateEnvironmentEvent {
type: EventType.CREATE_ENVIRONMENT; type: EventType.CREATE_ENVIRONMENT;
metadata: { metadata: {
@ -645,6 +811,7 @@ export type Event =
| UnauthorizeIntegrationEvent | UnauthorizeIntegrationEvent
| CreateIntegrationEvent | CreateIntegrationEvent
| DeleteIntegrationEvent | DeleteIntegrationEvent
| ManualSyncIntegrationEvent
| AddTrustedIPEvent | AddTrustedIPEvent
| UpdateTrustedIPEvent | UpdateTrustedIPEvent
| DeleteTrustedIPEvent | DeleteTrustedIPEvent
@ -657,9 +824,21 @@ export type Event =
| AddIdentityUniversalAuthEvent | AddIdentityUniversalAuthEvent
| UpdateIdentityUniversalAuthEvent | UpdateIdentityUniversalAuthEvent
| GetIdentityUniversalAuthEvent | GetIdentityUniversalAuthEvent
| LoginIdentityKubernetesAuthEvent
| AddIdentityKubernetesAuthEvent
| UpdateIdentityKubernetesAuthEvent
| GetIdentityKubernetesAuthEvent
| CreateIdentityUniversalAuthClientSecretEvent | CreateIdentityUniversalAuthClientSecretEvent
| GetIdentityUniversalAuthClientSecretsEvent | GetIdentityUniversalAuthClientSecretsEvent
| RevokeIdentityUniversalAuthClientSecretEvent | RevokeIdentityUniversalAuthClientSecretEvent
| LoginIdentityGcpAuthEvent
| AddIdentityGcpAuthEvent
| UpdateIdentityGcpAuthEvent
| GetIdentityGcpAuthEvent
| LoginIdentityAwsAuthEvent
| AddIdentityAwsAuthEvent
| UpdateIdentityAwsAuthEvent
| GetIdentityAwsAuthEvent
| CreateEnvironmentEvent | CreateEnvironmentEvent
| UpdateEnvironmentEvent | UpdateEnvironmentEvent
| DeleteEnvironmentEvent | DeleteEnvironmentEvent

View File

@ -1,6 +1,6 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { SecretKeyEncoding, TUsers } from "@app/db/schemas"; import { SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ScimRequestError } from "@app/lib/errors"; import { BadRequestError, ScimRequestError } from "@app/lib/errors";
@ -188,9 +188,9 @@ export const addUsersToGroupByUserIds = async ({
// check if all user(s) are part of the organization // check if all user(s) are part of the organization
const existingUserOrgMemberships = await orgDAL.findMembership( const existingUserOrgMemberships = await orgDAL.findMembership(
{ {
orgId: group.orgId, [`${TableName.OrgMembership}.orgId` as "orgId"]: group.orgId,
$in: { $in: {
userId: userIds [`${TableName.OrgMembership}.userId` as "userId"]: userIds
} }
}, },
{ tx } { tx }

View File

@ -1,5 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, unpackRules } from "@casl/ability/extra";
import ms from "ms"; import ms from "ms";
import { z } from "zod";
import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
@ -8,7 +10,7 @@ import { TIdentityProjectDALFactory } from "@app/services/identity-project/ident
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSet, ProjectPermissionSub } from "../permission/project-permission";
import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal"; import { TIdentityProjectAdditionalPrivilegeDALFactory } from "./identity-project-additional-privilege-dal";
import { import {
IdentityProjectAdditionalPrivilegeTemporaryMode, IdentityProjectAdditionalPrivilegeTemporaryMode,
@ -30,6 +32,27 @@ export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
typeof identityProjectAdditionalPrivilegeServiceFactory typeof identityProjectAdditionalPrivilegeServiceFactory
>; >;
// TODO(akhilmhdh): move this to more centralized
export const UnpackedPermissionSchema = z.object({
subject: z.union([z.string().min(1), z.string().array()]).optional(),
action: z.union([z.string().min(1), z.string().array()]),
conditions: z
.object({
environment: z.string().optional(),
secretPath: z
.object({
$glob: z.string().min(1)
})
.optional()
})
.optional()
});
const unpackPermissions = (permissions: unknown) =>
UnpackedPermissionSchema.array().parse(
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
);
export const identityProjectAdditionalPrivilegeServiceFactory = ({ export const identityProjectAdditionalPrivilegeServiceFactory = ({
identityProjectAdditionalPrivilegeDAL, identityProjectAdditionalPrivilegeDAL,
identityProjectDAL, identityProjectDAL,
@ -86,7 +109,10 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
slug, slug,
permissions: customPermission permissions: customPermission
}); });
return additionalPrivilege; return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
} }
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange); const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
@ -100,7 +126,10 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime), temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs) temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
}); });
return additionalPrivilege; return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}; };
const updateBySlug = async ({ const updateBySlug = async ({
@ -163,7 +192,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""), temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || "")) temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
}); });
return additionalPrivilege; return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
} }
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, { const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
@ -174,7 +207,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
temporaryRange: null, temporaryRange: null,
temporaryMode: null temporaryMode: null
}); });
return additionalPrivilege; return {
...additionalPrivilege,
permissions: unpackPermissions(additionalPrivilege.permissions)
};
}; };
const deleteBySlug = async ({ const deleteBySlug = async ({
@ -220,7 +257,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" }); if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id); const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
return deletedPrivilege; return {
...deletedPrivilege,
permissions: unpackPermissions(deletedPrivilege.permissions)
};
}; };
const getPrivilegeDetailsBySlug = async ({ const getPrivilegeDetailsBySlug = async ({
@ -254,7 +295,10 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
}); });
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" }); if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
return identityPrivilege; return {
...identityPrivilege,
permissions: unpackPermissions(identityPrivilege.permissions)
};
}; };
const listIdentityProjectPrivileges = async ({ const listIdentityProjectPrivileges = async ({
@ -284,7 +328,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({ const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find({
projectMembershipId: identityProjectMembership.id projectMembershipId: identityProjectMembership.id
}); });
return identityPrivileges; return identityPrivileges.map((el) => ({
...el,
permissions: unpackPermissions(el.permissions)
}));
}; };
return { return {

View File

@ -1,7 +1,14 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TLdapConfigsUpdate } from "@app/db/schemas"; import {
OrgMembershipRole,
OrgMembershipStatus,
SecretKeyEncoding,
TableName,
TLdapConfigsUpdate,
TUsers
} from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -19,12 +26,15 @@ import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal"; import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns"; import { normalizeUsername } from "@app/services/user/user-fns";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
@ -46,6 +56,7 @@ import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal";
type TLdapConfigServiceFactoryDep = { type TLdapConfigServiceFactoryDep = {
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne">; ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne">;
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">; ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
orgDAL: Pick< orgDAL: Pick<
TOrgDALFactory, TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
@ -75,6 +86,7 @@ export const ldapConfigServiceFactory = ({
ldapConfigDAL, ldapConfigDAL,
ldapGroupMapDAL, ldapGroupMapDAL,
orgDAL, orgDAL,
orgMembershipDAL,
orgBotDAL, orgBotDAL,
groupDAL, groupDAL,
groupProjectDAL, groupProjectDAL,
@ -379,16 +391,17 @@ export const ldapConfigServiceFactory = ({
username, username,
firstName, firstName,
lastName, lastName,
emails, email,
groups, groups,
orgId, orgId,
relayState relayState
}: TLdapLoginDTO) => { }: TLdapLoginDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
const serverCfg = await getServerCfg();
let userAlias = await userAliasDAL.findOne({ let userAlias = await userAliasDAL.findOne({
externalId, externalId,
orgId, orgId,
aliasType: AuthMethod.LDAP aliasType: UserAliasType.LDAP
}); });
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
@ -396,7 +409,13 @@ export const ldapConfigServiceFactory = ({
if (userAlias) { if (userAlias) {
await userDAL.transaction(async (tx) => { await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership({ userId: userAlias.userId }, { tx }); const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: userAlias.userId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
},
{ tx }
);
if (!orgMembership) { if (!orgMembership) {
await orgDAL.createMembership( await orgDAL.createMembership(
{ {
@ -419,40 +438,75 @@ export const ldapConfigServiceFactory = ({
}); });
} else { } else {
userAlias = await userDAL.transaction(async (tx) => { userAlias = await userDAL.transaction(async (tx) => {
const uniqueUsername = await normalizeUsername(username, userDAL); let newUser: TUsers | undefined;
const newUser = await userDAL.create( if (serverCfg.trustSamlEmails) {
{ newUser = await userDAL.findOne(
username: uniqueUsername, {
email: emails[0], email,
firstName, isEmailVerified: true
lastName, },
authMethods: [AuthMethod.LDAP], tx
isGhost: false );
}, }
tx
); if (!newUser) {
const uniqueUsername = await normalizeUsername(username, userDAL);
newUser = await userDAL.create(
{
username: serverCfg.trustLdapEmails ? email : uniqueUsername,
email,
isEmailVerified: serverCfg.trustLdapEmails,
firstName,
lastName,
authMethods: [],
isGhost: false
},
tx
);
}
const newUserAlias = await userAliasDAL.create( const newUserAlias = await userAliasDAL.create(
{ {
userId: newUser.id, userId: newUser.id,
username, username,
aliasType: AuthMethod.LDAP, aliasType: UserAliasType.LDAP,
externalId, externalId,
emails, emails: [email],
orgId orgId
}, },
tx tx
); );
await orgDAL.createMembership( const [orgMembership] = await orgDAL.findMembership(
{ {
userId: newUser.id, [`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
orgId, [`${TableName.OrgMembership}.orgId` as "id"]: orgId
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
}, },
tx { tx }
); );
if (!orgMembership) {
await orgMembershipDAL.create(
{
userId: userAlias.userId,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && newUser.isAccepted) {
await orgDAL.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
return newUserAlias; return newUserAlias;
}); });
} }
@ -543,11 +597,14 @@ export const ldapConfigServiceFactory = ({
authTokenType: AuthTokenType.PROVIDER_TOKEN, authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id, userId: user.id,
username: user.username, username: user.username,
...(user.email && { email: user.email, isEmailVerified: user.isEmailVerified }),
firstName, firstName,
lastName, lastName,
organizationName: organization.name, organizationName: organization.name,
organizationId: organization.id, organizationId: organization.id,
organizationSlug: organization.slug,
authMethod: AuthMethod.LDAP, authMethod: AuthMethod.LDAP,
authType: UserAliasType.LDAP,
isUserCompleted, isUserCompleted,
...(relayState ...(relayState
? { ? {

View File

@ -51,7 +51,7 @@ export type TLdapLoginDTO = {
username: string; username: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
emails: string[]; email: string;
orgId: string; orgId: string;
groups?: { groups?: {
dn: string; dn: string;

View File

@ -24,6 +24,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
customAlerts: false, customAlerts: false,
auditLogs: false, auditLogs: false,
auditLogsRetentionDays: 0, auditLogsRetentionDays: 0,
auditLogStreams: false,
auditLogStreamLimit: 3,
samlSSO: false, samlSSO: false,
scim: false, scim: false,
ldap: false, ldap: false,

View File

@ -121,8 +121,8 @@ export const licenseServiceFactory = ({
if (isValidOfflineLicense) { if (isValidOfflineLicense) {
onPremFeatures = contents.license.features; onPremFeatures = contents.license.features;
instanceType = InstanceType.EnterpriseOnPrem; instanceType = InstanceType.EnterpriseOnPremOffline;
logger.info(`Instance type: ${InstanceType.EnterpriseOnPrem}`); logger.info(`Instance type: ${InstanceType.EnterpriseOnPremOffline}`);
isValidLicense = true; isValidLicense = true;
return; return;
} }

View File

@ -3,6 +3,7 @@ import { TOrgPermission } from "@app/lib/types";
export enum InstanceType { export enum InstanceType {
OnPrem = "self-hosted", OnPrem = "self-hosted",
EnterpriseOnPrem = "enterprise-self-hosted", EnterpriseOnPrem = "enterprise-self-hosted",
EnterpriseOnPremOffline = "enterprise-self-hosted-offline",
Cloud = "cloud" Cloud = "cloud"
} }
@ -40,6 +41,8 @@ export type TFeatureSet = {
customAlerts: false; customAlerts: false;
auditLogs: false; auditLogs: false;
auditLogsRetentionDays: 0; auditLogsRetentionDays: 0;
auditLogStreams: false;
auditLogStreamLimit: 3;
samlSSO: false; samlSSO: false;
scim: false; scim: false;
ldap: false; ldap: false;

View File

@ -7,7 +7,8 @@ import {
SecretKeyEncoding, SecretKeyEncoding,
TableName, TableName,
TSamlConfigs, TSamlConfigs,
TSamlConfigsUpdate TSamlConfigsUpdate,
TUsers
} from "@app/db/schemas"; } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { import {
@ -19,10 +20,18 @@ import {
infisicalSymmetricEncypt infisicalSymmetricEncypt
} from "@app/lib/crypto/encryption"; } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
@ -31,15 +40,19 @@ import { TSamlConfigDALFactory } from "./saml-config-dal";
import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } from "./saml-config-types"; import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } from "./saml-config-types";
type TSamlConfigServiceFactoryDep = { type TSamlConfigServiceFactoryDep = {
samlConfigDAL: TSamlConfigDALFactory; samlConfigDAL: Pick<TSamlConfigDALFactory, "create" | "findOne" | "update" | "findById">;
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById">; userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById" | "findById">;
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
orgDAL: Pick< orgDAL: Pick<
TOrgDALFactory, TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById" "createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
>; >;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">; orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
smtpService: Pick<TSmtpService, "sendMail">;
}; };
export type TSamlConfigServiceFactory = ReturnType<typeof samlConfigServiceFactory>; export type TSamlConfigServiceFactory = ReturnType<typeof samlConfigServiceFactory>;
@ -48,9 +61,13 @@ export const samlConfigServiceFactory = ({
samlConfigDAL, samlConfigDAL,
orgBotDAL, orgBotDAL,
orgDAL, orgDAL,
orgMembershipDAL,
userDAL, userDAL,
userAliasDAL,
permissionService, permissionService,
licenseService licenseService,
tokenService,
smtpService
}: TSamlConfigServiceFactoryDep) => { }: TSamlConfigServiceFactoryDep) => {
const createSamlCfg = async ({ const createSamlCfg = async ({
cert, cert,
@ -305,7 +322,7 @@ export const samlConfigServiceFactory = ({
}; };
const samlLogin = async ({ const samlLogin = async ({
username, externalId,
email, email,
firstName, firstName,
lastName, lastName,
@ -314,38 +331,40 @@ export const samlConfigServiceFactory = ({
relayState relayState
}: TSamlLoginDTO) => { }: TSamlLoginDTO) => {
const appCfg = getConfig(); const appCfg = getConfig();
let user = await userDAL.findOne({ username }); const serverCfg = await getServerCfg();
const userAlias = await userAliasDAL.findOne({
externalId,
orgId,
aliasType: UserAliasType.SAML
});
const organization = await orgDAL.findOrgById(orgId); const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new BadRequestError({ message: "Org not found" }); if (!organization) throw new BadRequestError({ message: "Org not found" });
// TODO(dangtony98): remove this after aliases update let user: TUsers;
if (authProvider === AuthMethod.KEYCLOAK_SAML && appCfg.LICENSE_SERVER_KEY) { if (userAlias) {
throw new BadRequestError({ message: "Keycloak SAML is not yet available on Infisical Cloud" }); user = await userDAL.transaction(async (tx) => {
} const foundUser = await userDAL.findById(userAlias.userId, tx);
if (user) {
await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership( const [orgMembership] = await orgDAL.findMembership(
{ {
userId: user.id, [`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId [`${TableName.OrgMembership}.orgId` as "id"]: orgId
}, },
{ tx } { tx }
); );
if (!orgMembership) { if (!orgMembership) {
await orgDAL.createMembership( await orgMembershipDAL.create(
{ {
userId: user.id, userId: userAlias.userId,
orgId,
inviteEmail: email, inviteEmail: email,
orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
}, },
tx tx
); );
// Only update the membership to Accepted if the user account is already completed. // Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) { } else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
await orgDAL.updateMembershipById( await orgDAL.updateMembershipById(
orgMembership.id, orgMembership.id,
{ {
@ -354,40 +373,97 @@ export const samlConfigServiceFactory = ({
tx tx
); );
} }
return foundUser;
}); });
} else { } else {
user = await userDAL.transaction(async (tx) => { user = await userDAL.transaction(async (tx) => {
const newUser = await userDAL.create( let newUser: TUsers | undefined;
if (serverCfg.trustSamlEmails) {
newUser = await userDAL.findOne(
{
email,
isEmailVerified: true
},
tx
);
}
if (!newUser) {
const uniqueUsername = await normalizeUsername(`${firstName ?? ""}-${lastName ?? ""}`, userDAL);
newUser = await userDAL.create(
{
username: serverCfg.trustSamlEmails ? email : uniqueUsername,
email,
isEmailVerified: serverCfg.trustSamlEmails,
firstName,
lastName,
authMethods: [],
isGhost: false
},
tx
);
}
await userAliasDAL.create(
{ {
username, userId: newUser.id,
email, aliasType: UserAliasType.SAML,
firstName, externalId,
lastName, emails: email ? [email] : [],
authMethods: [AuthMethod.EMAIL], orgId
isGhost: false
}, },
tx tx
); );
await orgDAL.createMembership({
inviteEmail: email, const [orgMembership] = await orgDAL.findMembership(
orgId, {
role: OrgMembershipRole.Member, [`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
status: OrgMembershipStatus.Invited [`${TableName.OrgMembership}.orgId` as "id"]: orgId
}); },
{ tx }
);
if (!orgMembership) {
await orgMembershipDAL.create(
{
userId: newUser.id,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && newUser.isAccepted) {
await orgDAL.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
return newUser; return newUser;
}); });
} }
const isUserCompleted = Boolean(user.isAccepted); const isUserCompleted = Boolean(user.isAccepted);
const providerAuthToken = jwt.sign( const providerAuthToken = jwt.sign(
{ {
authTokenType: AuthTokenType.PROVIDER_TOKEN, authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id, userId: user.id,
username: user.username, username: user.username,
...(user.email && { email: user.email, isEmailVerified: user.isEmailVerified }),
firstName, firstName,
lastName, lastName,
organizationName: organization.name, organizationName: organization.name,
organizationId: organization.id, organizationId: organization.id,
organizationSlug: organization.slug,
authMethod: authProvider, authMethod: authProvider,
authType: UserAliasType.SAML,
isUserCompleted, isUserCompleted,
...(relayState ...(relayState
? { ? {
@ -403,6 +479,22 @@ export const samlConfigServiceFactory = ({
await samlConfigDAL.update({ orgId }, { lastUsed: new Date() }); await samlConfigDAL.update({ orgId }, { lastUsed: new Date() });
if (user.email && !user.isEmailVerified) {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_VERIFICATION,
userId: user.id
});
await smtpService.sendMail({
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email],
substitutions: {
code: token
}
});
}
return { isUserCompleted, providerAuthToken }; return { isUserCompleted, providerAuthToken };
}; };

View File

@ -45,8 +45,8 @@ export type TGetSamlCfgDTO =
}; };
export type TSamlLoginDTO = { export type TSamlLoginDTO = {
username: string; externalId: string;
email?: string; email: string;
firstName: string; firstName: string;
lastName?: string; lastName?: string;
authProvider: string; authProvider: string;

View File

@ -2,31 +2,31 @@ import { TListScimGroups, TListScimUsers, TScimGroup, TScimUser } from "./scim-t
export const buildScimUserList = ({ export const buildScimUserList = ({
scimUsers, scimUsers,
offset, startIndex,
limit limit
}: { }: {
scimUsers: TScimUser[]; scimUsers: TScimUser[];
offset: number; startIndex: number;
limit: number; limit: number;
}): TListScimUsers => { }): TListScimUsers => {
return { return {
Resources: scimUsers, Resources: scimUsers,
itemsPerPage: limit, itemsPerPage: limit,
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
startIndex: offset, startIndex,
totalResults: scimUsers.length totalResults: scimUsers.length
}; };
}; };
export const buildScimUser = ({ export const buildScimUser = ({
userId, orgMembershipId,
username, username,
email, email,
firstName, firstName,
lastName, lastName,
active active
}: { }: {
userId: string; orgMembershipId: string;
username: string; username: string;
email?: string | null; email?: string | null;
firstName: string; firstName: string;
@ -35,7 +35,7 @@ export const buildScimUser = ({
}): TScimUser => { }): TScimUser => {
const scimUser = { const scimUser = {
schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"], schemas: ["urn:ietf:params:scim:schemas:core:2.0:User"],
id: userId, id: orgMembershipId,
userName: username, userName: username,
displayName: `${firstName} ${lastName}`, displayName: `${firstName} ${lastName}`,
name: { name: {
@ -65,18 +65,18 @@ export const buildScimUser = ({
export const buildScimGroupList = ({ export const buildScimGroupList = ({
scimGroups, scimGroups,
offset, startIndex,
limit limit
}: { }: {
scimGroups: TScimGroup[]; scimGroups: TScimGroup[];
offset: number; startIndex: number;
limit: number; limit: number;
}): TListScimGroups => { }): TListScimGroups => {
return { return {
Resources: scimGroups, Resources: scimGroups,
itemsPerPage: limit, itemsPerPage: limit,
schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], schemas: ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
startIndex: offset, startIndex,
totalResults: scimGroups.length totalResults: scimGroups.length
}; };
}; };

View File

@ -2,7 +2,7 @@ import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups } from "@app/db/schemas"; import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -11,16 +11,21 @@ import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TOrgPermission } from "@app/lib/types"; import { TOrgPermission } from "@app/lib/types";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { AuthTokenType } from "@app/services/auth/auth-type";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
import { deleteOrgMembership } from "@app/services/org/org-fns"; import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal"; import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service"; import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TLicenseServiceFactory } from "../license/license-service"; import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
@ -47,24 +52,32 @@ import {
type TScimServiceFactoryDep = { type TScimServiceFactoryDep = {
scimDAL: Pick<TScimDALFactory, "create" | "find" | "findById" | "deleteById">; scimDAL: Pick<TScimDALFactory, "create" | "find" | "findById" | "deleteById">;
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "create" | "transaction" | "findUserEncKeyByUserIdsBatch">; userDAL: Pick<
TUserDALFactory,
"find" | "findOne" | "create" | "transaction" | "findUserEncKeyByUserIdsBatch" | "findById"
>;
userAliasDAL: Pick<TUserAliasDALFactory, "findOne" | "create" | "delete">;
orgDAL: Pick< orgDAL: Pick<
TOrgDALFactory, TOrgDALFactory,
"createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" "createMembership" | "findById" | "findMembership" | "deleteMembershipById" | "transaction" | "updateMembershipById"
>; >;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById">;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">; projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">; projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick< groupDAL: Pick<
TGroupDALFactory, TGroupDALFactory,
"create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups" | "transaction" "create" | "findOne" | "findAllGroupMembers" | "update" | "delete" | "findGroups" | "transaction"
>; >;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">; groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
userGroupMembershipDAL: TUserGroupMembershipDALFactory; // TODO: Pick userGroupMembershipDAL: Pick<
TUserGroupMembershipDALFactory,
"find" | "transaction" | "insertMany" | "filterProjectsByUserMembership" | "delete"
>;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">; projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">; projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
smtpService: TSmtpService; smtpService: Pick<TSmtpService, "sendMail">;
}; };
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>; export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
@ -73,7 +86,9 @@ export const scimServiceFactory = ({
licenseService, licenseService,
scimDAL, scimDAL,
userDAL, userDAL,
userAliasDAL,
orgDAL, orgDAL,
orgMembershipDAL,
projectDAL, projectDAL,
projectMembershipDAL, projectMembershipDAL,
groupDAL, groupDAL,
@ -160,7 +175,7 @@ export const scimServiceFactory = ({
}; };
// SCIM server endpoints // SCIM server endpoints
const listScimUsers = async ({ offset, limit, filter, orgId }: TListScimUsersDTO): Promise<TListScimUsers> => { const listScimUsers = async ({ startIndex, limit, filter, orgId }: TListScimUsersDTO): Promise<TListScimUsers> => {
const org = await orgDAL.findById(orgId); const org = await orgDAL.findById(orgId);
if (!org.scimEnabled) if (!org.scimEnabled)
@ -178,11 +193,11 @@ export const scimServiceFactory = ({
attributeName = "email"; attributeName = "email";
} }
return { [attributeName]: parsedValue }; return { [attributeName]: parsedValue.replace(/"/g, "") };
}; };
const findOpts = { const findOpts = {
...(offset && { offset }), ...(startIndex && { offset: startIndex - 1 }),
...(limit && { limit }) ...(limit && { limit })
}; };
@ -194,10 +209,10 @@ export const scimServiceFactory = ({
findOpts findOpts
); );
const scimUsers = users.map(({ userId, username, firstName, lastName, email }) => const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email }) =>
buildScimUser({ buildScimUser({
userId: userId ?? "", orgMembershipId: id ?? "",
username, username: externalId ?? username,
firstName: firstName ?? "", firstName: firstName ?? "",
lastName: lastName ?? "", lastName: lastName ?? "",
email, email,
@ -207,16 +222,16 @@ export const scimServiceFactory = ({
return buildScimUserList({ return buildScimUserList({
scimUsers, scimUsers,
offset, startIndex,
limit limit
}); });
}; };
const getScimUser = async ({ userId, orgId }: TGetScimUserDTO) => { const getScimUser = async ({ orgMembershipId, orgId }: TGetScimUserDTO) => {
const [membership] = await orgDAL const [membership] = await orgDAL
.findMembership({ .findMembership({
userId, [`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId [`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
}) })
.catch(() => { .catch(() => {
throw new ScimRequestError({ throw new ScimRequestError({
@ -238,8 +253,8 @@ export const scimServiceFactory = ({
}); });
return buildScimUser({ return buildScimUser({
userId: membership.userId as string, orgMembershipId: membership.id,
username: membership.username, username: membership.externalId ?? membership.username,
email: membership.email ?? "", email: membership.email ?? "",
firstName: membership.firstName as string, firstName: membership.firstName as string,
lastName: membership.lastName as string, lastName: membership.lastName as string,
@ -247,7 +262,9 @@ export const scimServiceFactory = ({
}); });
}; };
const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => { const createScimUser = async ({ externalId, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
if (!email) throw new ScimRequestError({ detail: "Invalid request. Missing email.", status: 400 });
const org = await orgDAL.findById(orgId); const org = await orgDAL.findById(orgId);
if (!org) if (!org)
@ -262,67 +279,121 @@ export const scimServiceFactory = ({
status: 403 status: 403
}); });
let user = await userDAL.findOne({ const appCfg = getConfig();
username const serverCfg = await getServerCfg();
const userAlias = await userAliasDAL.findOne({
externalId,
orgId,
aliasType: UserAliasType.SAML
}); });
if (user) { const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => {
await userDAL.transaction(async (tx) => { let user: TUsers | undefined;
const [orgMembership] = await orgDAL.findMembership( let orgMembership: TOrgMemberships;
if (userAlias) {
user = await userDAL.findById(userAlias.userId, tx);
orgMembership = await orgMembershipDAL.findOne(
{ {
userId: user.id, userId: user.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId orgId
}, },
{ tx } tx
); );
if (orgMembership)
throw new ScimRequestError({
detail: "User already exists in the database",
status: 409
});
if (!orgMembership) { if (!orgMembership) {
await orgDAL.createMembership( orgMembership = await orgMembershipDAL.create(
{ {
userId: user.id, userId: userAlias.userId,
orgId,
inviteEmail: email, inviteEmail: email,
orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
orgMembership = await orgMembershipDAL.updateById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
}, },
tx tx
); );
} }
}); } else {
} else { if (serverCfg.trustSamlEmails) {
user = await userDAL.transaction(async (tx) => { user = await userDAL.findOne(
const newUser = await userDAL.create( {
email,
isEmailVerified: true
},
tx
);
}
if (!user) {
const uniqueUsername = await normalizeUsername(`${firstName}-${lastName}`, userDAL);
user = await userDAL.create(
{
username: serverCfg.trustSamlEmails ? email : uniqueUsername,
email,
isEmailVerified: serverCfg.trustSamlEmails,
firstName,
lastName,
authMethods: [],
isGhost: false
},
tx
);
}
await userAliasDAL.create(
{ {
username, userId: user.id,
email, aliasType: UserAliasType.SAML,
firstName, externalId,
lastName, emails: email ? [email] : [],
authMethods: [AuthMethod.EMAIL], orgId
isGhost: false
}, },
tx tx
); );
await orgDAL.createMembership( const [foundOrgMembership] = await orgDAL.findMembership(
{ {
inviteEmail: email, [`${TableName.OrgMembership}.userId` as "userId"]: user.id,
orgId, [`${TableName.OrgMembership}.orgId` as "id"]: orgId
userId: newUser.id,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
}, },
tx { tx }
); );
return newUser;
});
}
const appCfg = getConfig(); orgMembership = foundOrgMembership;
if (!orgMembership) {
orgMembership = await orgMembershipDAL.create(
{
userId: user.id,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
orgMembership = await orgDAL.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
}
return { user, orgMembership };
});
if (email) { if (email) {
await smtpService.sendMail({ await smtpService.sendMail({
@ -337,20 +408,20 @@ export const scimServiceFactory = ({
} }
return buildScimUser({ return buildScimUser({
userId: user.id, orgMembershipId: createdOrgMembership.id,
username: user.username, username: externalId,
firstName: user.firstName as string, firstName: createdUser.firstName as string,
lastName: user.lastName as string, lastName: createdUser.lastName as string,
email: user.email ?? "", email: createdUser.email ?? "",
active: true active: true
}); });
}; };
const updateScimUser = async ({ userId, orgId, operations }: TUpdateScimUserDTO) => { const updateScimUser = async ({ orgMembershipId, orgId, operations }: TUpdateScimUserDTO) => {
const [membership] = await orgDAL const [membership] = await orgDAL
.findMembership({ .findMembership({
userId, [`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId [`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
}) })
.catch(() => { .catch(() => {
throw new ScimRequestError({ throw new ScimRequestError({
@ -386,18 +457,20 @@ export const scimServiceFactory = ({
}); });
if (!active) { if (!active) {
await deleteOrgMembership({ await deleteOrgMembershipFn({
orgMembershipId: membership.id, orgMembershipId: membership.id,
orgId: membership.orgId, orgId: membership.orgId,
orgDAL, orgDAL,
projectDAL, projectMembershipDAL,
projectMembershipDAL projectKeyDAL,
userAliasDAL,
licenseService
}); });
} }
return buildScimUser({ return buildScimUser({
userId: membership.userId as string, orgMembershipId: membership.id,
username: membership.username, username: membership.externalId ?? membership.username,
email: membership.email, email: membership.email,
firstName: membership.firstName as string, firstName: membership.firstName as string,
lastName: membership.lastName as string, lastName: membership.lastName as string,
@ -405,11 +478,11 @@ export const scimServiceFactory = ({
}); });
}; };
const replaceScimUser = async ({ userId, active, orgId }: TReplaceScimUserDTO) => { const replaceScimUser = async ({ orgMembershipId, active, orgId }: TReplaceScimUserDTO) => {
const [membership] = await orgDAL const [membership] = await orgDAL
.findMembership({ .findMembership({
userId, [`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId [`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
}) })
.catch(() => { .catch(() => {
throw new ScimRequestError({ throw new ScimRequestError({
@ -431,19 +504,20 @@ export const scimServiceFactory = ({
}); });
if (!active) { if (!active) {
// tx await deleteOrgMembershipFn({
await deleteOrgMembership({
orgMembershipId: membership.id, orgMembershipId: membership.id,
orgId: membership.orgId, orgId: membership.orgId,
orgDAL, orgDAL,
projectDAL, projectMembershipDAL,
projectMembershipDAL projectKeyDAL,
userAliasDAL,
licenseService
}); });
} }
return buildScimUser({ return buildScimUser({
userId: membership.userId as string, orgMembershipId: membership.id,
username: membership.username, username: membership.externalId ?? membership.username,
email: membership.email, email: membership.email,
firstName: membership.firstName as string, firstName: membership.firstName as string,
lastName: membership.lastName as string, lastName: membership.lastName as string,
@ -451,18 +525,11 @@ export const scimServiceFactory = ({
}); });
}; };
const deleteScimUser = async ({ userId, orgId }: TDeleteScimUserDTO) => { const deleteScimUser = async ({ orgMembershipId, orgId }: TDeleteScimUserDTO) => {
const [membership] = await orgDAL const [membership] = await orgDAL.findMembership({
.findMembership({ [`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
userId, [`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
[`${TableName.OrgMembership}.orgId` as "id"]: orgId });
})
.catch(() => {
throw new ScimRequestError({
detail: "User not found",
status: 404
});
});
if (!membership) if (!membership)
throw new ScimRequestError({ throw new ScimRequestError({
@ -477,18 +544,20 @@ export const scimServiceFactory = ({
}); });
} }
await deleteOrgMembership({ await deleteOrgMembershipFn({
orgMembershipId: membership.id, orgMembershipId: membership.id,
orgId: membership.orgId, orgId: membership.orgId,
orgDAL, orgDAL,
projectDAL, projectMembershipDAL,
projectMembershipDAL projectKeyDAL,
userAliasDAL,
licenseService
}); });
return {}; // intentionally return empty object upon success return {}; // intentionally return empty object upon success
}; };
const listScimGroups = async ({ orgId, offset, limit }: TListScimGroupsDTO) => { const listScimGroups = async ({ orgId, startIndex, limit }: TListScimGroupsDTO) => {
const plan = await licenseService.getPlan(orgId); const plan = await licenseService.getPlan(orgId);
if (!plan.groups) if (!plan.groups)
throw new BadRequestError({ throw new BadRequestError({
@ -509,21 +578,27 @@ export const scimServiceFactory = ({
status: 403 status: 403
}); });
const groups = await groupDAL.findGroups({ const groups = await groupDAL.findGroups(
orgId {
}); orgId
},
{
offset: startIndex - 1,
limit
}
);
const scimGroups = groups.map((group) => const scimGroups = groups.map((group) =>
buildScimGroup({ buildScimGroup({
groupId: group.id, groupId: group.id,
name: group.name, name: group.name,
members: [] members: [] // does this need to be populated?
}) })
); );
return buildScimGroupList({ return buildScimGroupList({
scimGroups, scimGroups,
offset, startIndex,
limit limit
}); });
}; };
@ -562,9 +637,15 @@ export const scimServiceFactory = ({
); );
if (members && members.length) { if (members && members.length) {
const orgMemberships = await orgMembershipDAL.find({
$in: {
id: members.map((member) => member.value)
}
});
const newMembers = await addUsersToGroupByUserIds({ const newMembers = await addUsersToGroupByUserIds({
group, group,
userIds: members.map((member) => member.value), userIds: orgMemberships.map((membership) => membership.userId as string),
userDAL, userDAL,
userGroupMembershipDAL, userGroupMembershipDAL,
orgDAL, orgDAL,
@ -581,12 +662,19 @@ export const scimServiceFactory = ({
return { group, newMembers: [] }; return { group, newMembers: [] };
}); });
const orgMemberships = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
$in: {
[`${TableName.OrgMembership}.userId` as "userId"]: newGroup.newMembers.map((member) => member.id)
}
});
return buildScimGroup({ return buildScimGroup({
groupId: newGroup.group.id, groupId: newGroup.group.id,
name: newGroup.group.name, name: newGroup.group.name,
members: newGroup.newMembers.map((member) => ({ members: orgMemberships.map(({ id, firstName, lastName }) => ({
value: member.id, value: id,
display: `${member.firstName} ${member.lastName}` display: `${firstName} ${lastName}`
})) }))
}); });
}; };
@ -615,15 +703,22 @@ export const scimServiceFactory = ({
groupId: group.id groupId: group.id
}); });
const orgMemberships = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
$in: {
[`${TableName.OrgMembership}.userId` as "userId"]: users
.filter((user) => user.isPartOfGroup)
.map((user) => user.id)
}
});
return buildScimGroup({ return buildScimGroup({
groupId: group.id, groupId: group.id,
name: group.name, name: group.name,
members: users members: orgMemberships.map(({ id, firstName, lastName }) => ({
.filter((user) => user.isPartOfGroup) value: id,
.map((user) => ({ display: `${firstName} ${lastName}`
value: user.id, }))
display: `${user.firstName} ${user.lastName}`
}))
}); });
}; };
@ -667,7 +762,13 @@ export const scimServiceFactory = ({
} }
if (members) { if (members) {
const membersIdsSet = new Set(members.map((member) => member.value)); const orgMemberships = await orgMembershipDAL.find({
$in: {
id: members.map((member) => member.value)
}
});
const membersIdsSet = new Set(orgMemberships.map((orgMembership) => orgMembership.userId));
const directMemberUserIds = ( const directMemberUserIds = (
await userGroupMembershipDAL.find({ await userGroupMembershipDAL.find({
@ -686,13 +787,13 @@ export const scimServiceFactory = ({
const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds); const allMembersUserIds = directMemberUserIds.concat(pendingGroupAdditionsUserIds);
const allMembersUserIdsSet = new Set(allMembersUserIds); const allMembersUserIdsSet = new Set(allMembersUserIds);
const toAddUserIds = members.filter((member) => !allMembersUserIdsSet.has(member.value)); const toAddUserIds = orgMemberships.filter((member) => !allMembersUserIdsSet.has(member.userId as string));
const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId)); const toRemoveUserIds = allMembersUserIds.filter((userId) => !membersIdsSet.has(userId));
if (toAddUserIds.length) { if (toAddUserIds.length) {
await addUsersToGroupByUserIds({ await addUsersToGroupByUserIds({
group, group,
userIds: toAddUserIds.map((member) => member.value), userIds: toAddUserIds.map((member) => member.userId as string),
userDAL, userDAL,
userGroupMembershipDAL, userGroupMembershipDAL,
orgDAL, orgDAL,

View File

@ -12,7 +12,7 @@ export type TDeleteScimTokenDTO = {
// SCIM server endpoint types // SCIM server endpoint types
export type TListScimUsersDTO = { export type TListScimUsersDTO = {
offset: number; startIndex: number;
limit: number; limit: number;
filter?: string; filter?: string;
orgId: string; orgId: string;
@ -27,12 +27,12 @@ export type TListScimUsers = {
}; };
export type TGetScimUserDTO = { export type TGetScimUserDTO = {
userId: string; orgMembershipId: string;
orgId: string; orgId: string;
}; };
export type TCreateScimUserDTO = { export type TCreateScimUserDTO = {
username: string; externalId: string;
email?: string; email?: string;
firstName: string; firstName: string;
lastName: string; lastName: string;
@ -40,7 +40,7 @@ export type TCreateScimUserDTO = {
}; };
export type TUpdateScimUserDTO = { export type TUpdateScimUserDTO = {
userId: string; orgMembershipId: string;
orgId: string; orgId: string;
operations: { operations: {
op: string; op: string;
@ -54,18 +54,18 @@ export type TUpdateScimUserDTO = {
}; };
export type TReplaceScimUserDTO = { export type TReplaceScimUserDTO = {
userId: string; orgMembershipId: string;
active: boolean; active: boolean;
orgId: string; orgId: string;
}; };
export type TDeleteScimUserDTO = { export type TDeleteScimUserDTO = {
userId: string; orgMembershipId: string;
orgId: string; orgId: string;
}; };
export type TListScimGroupsDTO = { export type TListScimGroupsDTO = {
offset: number; startIndex: number;
limit: number; limit: number;
orgId: string; orgId: string;
}; };

View File

@ -7,12 +7,15 @@ import {
SecretType, SecretType,
TSecretApprovalRequestsSecretsInsert TSecretApprovalRequestsSecretsInsert
} from "@app/db/schemas"; } from "@app/db/schemas";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { groupBy, pick, unique } from "@app/lib/fn"; import { groupBy, pick, unique } from "@app/lib/fn";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
import { TSecretDALFactory } from "@app/services/secret/secret-dal"; import { TSecretDALFactory } from "@app/services/secret/secret-dal";
import { getAllNestedSecretReferences } from "@app/services/secret/secret-fns";
import { TSecretQueueFactory } from "@app/services/secret/secret-queue"; import { TSecretQueueFactory } from "@app/services/secret/secret-queue";
import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal"; import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
@ -53,6 +56,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">; secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">; secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">; projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus">;
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
secretService: Pick< secretService: Pick<
TSecretServiceFactory, TSecretServiceFactory,
| "fnSecretBulkInsert" | "fnSecretBulkInsert"
@ -80,7 +84,8 @@ export const secretApprovalRequestServiceFactory = ({
snapshotService, snapshotService,
secretService, secretService,
secretVersionDAL, secretVersionDAL,
secretQueueService secretQueueService,
projectBotService
}: TSecretApprovalRequestServiceFactoryDep) => { }: TSecretApprovalRequestServiceFactoryDep) => {
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => { const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
@ -352,7 +357,7 @@ export const secretApprovalRequestServiceFactory = ({
} }
const secretDeletionCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Delete); const secretDeletionCommits = secretApprovalSecrets.filter(({ op }) => op === CommitType.Delete);
const botKey = await projectBotService.getBotKey(projectId).catch(() => null);
const mergeStatus = await secretApprovalRequestDAL.transaction(async (tx) => { const mergeStatus = await secretApprovalRequestDAL.transaction(async (tx) => {
const newSecrets = secretCreationCommits.length const newSecrets = secretCreationCommits.length
? await secretService.fnSecretBulkInsert({ ? await secretService.fnSecretBulkInsert({
@ -379,7 +384,17 @@ export const secretApprovalRequestServiceFactory = ({
]), ]),
tags: el?.tags.map(({ id }) => id), tags: el?.tags.map(({ id }) => id),
version: 1, version: 1,
type: SecretType.Shared type: SecretType.Shared,
references: botKey
? getAllNestedSecretReferences(
decryptSymmetric128BitHexKeyUTF8({
ciphertext: el.secretValueCiphertext,
iv: el.secretValueIV,
tag: el.secretValueTag,
key: botKey
})
)
: undefined
})), })),
secretDAL, secretDAL,
secretVersionDAL, secretVersionDAL,
@ -414,7 +429,17 @@ export const secretApprovalRequestServiceFactory = ({
"secretReminderNote", "secretReminderNote",
"secretReminderRepeatDays", "secretReminderRepeatDays",
"secretBlindIndex" "secretBlindIndex"
]) ]),
references: botKey
? getAllNestedSecretReferences(
decryptSymmetric128BitHexKeyUTF8({
ciphertext: el.secretValueCiphertext,
iv: el.secretValueIV,
tag: el.secretValueTag,
key: botKey
})
)
: undefined
} }
})), })),
secretDAL, secretDAL,

View File

@ -90,15 +90,17 @@ export const secretScanningServiceFactory = ({
const { const {
data: { repositories } data: { repositories }
} = await octokit.apps.listReposAccessibleToInstallation(); } = await octokit.apps.listReposAccessibleToInstallation();
await Promise.all( if (!appCfg.DISABLE_SECRET_SCANNING) {
repositories.map(({ id, full_name }) => await Promise.all(
secretScanningQueue.startFullRepoScan({ repositories.map(({ id, full_name }) =>
organizationId: session.orgId, secretScanningQueue.startFullRepoScan({
installationId, organizationId: session.orgId,
repository: { id, fullName: full_name } installationId,
}) repository: { id, fullName: full_name }
) })
); )
);
}
return { installatedApp }; return { installatedApp };
}; };
@ -151,6 +153,7 @@ export const secretScanningServiceFactory = ({
}; };
const handleRepoPushEvent = async (payload: WebhookEventMap["push"]) => { const handleRepoPushEvent = async (payload: WebhookEventMap["push"]) => {
const appCfg = getConfig();
const { commits, repository, installation, pusher } = payload; const { commits, repository, installation, pusher } = payload;
if (!commits || !repository || !installation || !pusher) { if (!commits || !repository || !installation || !pusher) {
return; return;
@ -161,13 +164,15 @@ export const secretScanningServiceFactory = ({
}); });
if (!installationLink) return; if (!installationLink) return;
await secretScanningQueue.startPushEventScan({ if (!appCfg.DISABLE_SECRET_SCANNING) {
commits, await secretScanningQueue.startPushEventScan({
pusher: { name: pusher.name, email: pusher.email }, commits,
repository: { fullName: repository.full_name, id: repository.id }, pusher: { name: pusher.name, email: pusher.email },
organizationId: installationLink.orgId, repository: { fullName: repository.full_name, id: repository.id },
installationId: String(installation?.id) organizationId: installationLink.orgId,
}); installationId: String(installation?.id)
});
}
}; };
const handleRepoDeleteEvent = async (installationId: string, repositoryIds: string[]) => { const handleRepoDeleteEvent = async (installationId: string, repositoryIds: string[]) => {

View File

@ -89,6 +89,21 @@ export const UNIVERSAL_AUTH = {
}, },
RENEW_ACCESS_TOKEN: { RENEW_ACCESS_TOKEN: {
accessToken: "The access token to renew." accessToken: "The access token to renew."
},
REVOKE_ACCESS_TOKEN: {
accessToken: "The access token to revoke."
}
} as const;
export const AWS_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
iamHttpRequestMethod: "The HTTP request method used in the signed request.",
iamRequestUrl:
"The base64-encoded HTTP URL used in the signed request. Most likely, the base64-encoding of https://sts.amazonaws.com/",
iamRequestBody:
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
} }
} as const; } as const;
@ -133,36 +148,6 @@ export const PROJECTS = {
name: "The new name of the project.", name: "The new name of the project.",
autoCapitalization: "Disable or enable auto-capitalization for the project." autoCapitalization: "Disable or enable auto-capitalization for the project."
}, },
INVITE_MEMBER: {
projectId: "The ID of the project to invite the member to.",
emails: "A list of organization member emails to invite to the project.",
usernames: "A list of usernames to invite to the project."
},
REMOVE_MEMBER: {
projectId: "The ID of the project to remove the member from.",
emails: "A list of organization member emails to remove from the project.",
usernames: "A list of usernames to remove from the project."
},
GET_USER_MEMBERSHIPS: {
workspaceId: "The ID of the project to get memberships from."
},
UPDATE_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to update the membership for.",
membershipId: "The ID of the membership to update.",
roles: "A list of roles to update the membership to."
},
LIST_IDENTITY_MEMBERSHIPS: {
projectId: "The ID of the project to get identity memberships from."
},
UPDATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to update the identity membership for.",
identityId: "The ID of the identity to update the membership for.",
roles: "A list of roles to update the membership to."
},
DELETE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to delete the identity membership from.",
identityId: "The ID of the identity to delete the membership from."
},
GET_KEY: { GET_KEY: {
workspaceId: "The ID of the project to get the key from." workspaceId: "The ID of the project to get the key from."
}, },
@ -201,6 +186,70 @@ export const PROJECTS = {
} }
} as const; } as const;
export const PROJECT_USERS = {
INVITE_MEMBER: {
projectId: "The ID of the project to invite the member to.",
emails: "A list of organization member emails to invite to the project.",
usernames: "A list of usernames to invite to the project."
},
REMOVE_MEMBER: {
projectId: "The ID of the project to remove the member from.",
emails: "A list of organization member emails to remove from the project.",
usernames: "A list of usernames to remove from the project."
},
GET_USER_MEMBERSHIPS: {
workspaceId: "The ID of the project to get memberships from."
},
GET_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to get memberships from.",
username: "The username to get project membership of. Email is the default username."
},
UPDATE_USER_MEMBERSHIP: {
workspaceId: "The ID of the project to update the membership for.",
membershipId: "The ID of the membership to update.",
roles: "A list of roles to update the membership to."
}
};
export const PROJECT_IDENTITIES = {
LIST_IDENTITY_MEMBERSHIPS: {
projectId: "The ID of the project to get identity memberships from."
},
GET_IDENTITY_MEMBERSHIP_BY_ID: {
identityId: "The ID of the identity to get the membership for.",
projectId: "The ID of the project to get the identity membership for."
},
UPDATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to update the identity membership for.",
identityId: "The ID of the identity to update the membership for.",
roles: {
description: "A list of role slugs to assign to the identity project membership.",
role: "The role slug to assign to the newly created identity project membership.",
isTemporary: "Whether the assigned role is temporary.",
temporaryMode: "Type of temporary expiry.",
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
temporaryAccessStartTime: "Time to which the temporary access starts"
}
},
DELETE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to delete the identity membership from.",
identityId: "The ID of the identity to delete the membership from."
},
CREATE_IDENTITY_MEMBERSHIP: {
projectId: "The ID of the project to create the identity membership from.",
identityId: "The ID of the identity to create the membership from.",
role: "The role slug to assign to the newly created identity project membership.",
roles: {
description: "A list of role slugs to assign to the newly created identity project membership.",
role: "The role slug to assign to the newly created identity project membership.",
isTemporary: "Whether the assigned role is temporary.",
temporaryMode: "Type of temporary expiry.",
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
temporaryAccessStartTime: "Time to which the temporary access starts"
}
}
};
export const ENVIRONMENTS = { export const ENVIRONMENTS = {
CREATE: { CREATE: {
workspaceId: "The ID of the project to create the environment in.", workspaceId: "The ID of the project to create the environment in.",
@ -240,6 +289,7 @@ export const FOLDERS = {
name: "The new name of the folder.", name: "The new name of the folder.",
path: "The path of the folder to update.", path: "The path of the folder to update.",
directory: "The new directory of the folder to update. (Deprecated in favor of path)", directory: "The new directory of the folder to update. (Deprecated in favor of path)",
projectSlug: "The slug of the project where the folder is located.",
workspaceId: "The ID of the project where the folder is located." workspaceId: "The ID of the project where the folder is located."
}, },
DELETE: { DELETE: {
@ -272,10 +322,12 @@ export const SECRETS = {
export const RAW_SECRETS = { export const RAW_SECRETS = {
LIST: { LIST: {
expand: "Whether or not to expand secret references",
recursive: recursive:
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.", "Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
workspaceId: "The ID of the project to list secrets from.", workspaceId: "The ID of the project to list secrets from.",
workspaceSlug: "The slug of the project to list secrets from. This parameter is only usable by machine identities.", workspaceSlug:
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
environment: "The slug of the environment to list secrets from.", environment: "The slug of the environment to list secrets from.",
secretPath: "The secret path to list secrets from.", secretPath: "The secret path to list secrets from.",
includeImports: "Weather to include imported secrets or not." includeImports: "Weather to include imported secrets or not."
@ -294,6 +346,7 @@ export const RAW_SECRETS = {
GET: { GET: {
secretName: "The name of the secret to get.", secretName: "The name of the secret to get.",
workspaceId: "The ID of the project to get the secret from.", workspaceId: "The ID of the project to get the secret from.",
workspaceSlug: "The slug of the project to get the secret from.",
environment: "The slug of the environment to get the secret from.", environment: "The slug of the environment to get the secret from.",
secretPath: "The path of the secret to get.", secretPath: "The path of the secret to get.",
version: "The version of the secret to get.", version: "The version of the secret to get.",
@ -464,12 +517,21 @@ export const SECRET_TAGS = {
export const IDENTITY_ADDITIONAL_PRIVILEGE = { export const IDENTITY_ADDITIONAL_PRIVILEGE = {
CREATE: { CREATE: {
projectSlug: "The slug of the project of the identity in.", projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to delete.", identityId: "The ID of the identity to create.",
slug: "The slug of the privilege to create.", slug: "The slug of the privilege to create.",
permissions: `The permission object for the privilege. permissions: `The permission object for the privilege.
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]] - Read secrets
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]] \`\`\`
2. [["read", "secrets", {environment: "dev"}]] { "permissions": [{"action": "read", "subject": "secrets"]}
\`\`\`
- Read and Write secrets
\`\`\`
{ "permissions": [{"action": "read", "subject": "secrets"], {"action": "write", "subject": "secrets"]}
\`\`\`
- Read secrets scoped to an environment and secret path
\`\`\`
- { "permissions": [{"action": "read", "subject": "secrets", "conditions": { "environment": "dev", "secretPath": { "$glob": "/" } }}] }
\`\`\`
`, `,
isPackPermission: "Whether the server should pack(compact) the permission object.", isPackPermission: "Whether the server should pack(compact) the permission object.",
isTemporary: "Whether the privilege is temporary.", isTemporary: "Whether the privilege is temporary.",
@ -483,11 +545,19 @@ export const IDENTITY_ADDITIONAL_PRIVILEGE = {
slug: "The slug of the privilege to update.", slug: "The slug of the privilege to update.",
newSlug: "The new slug of the privilege to update.", newSlug: "The new slug of the privilege to update.",
permissions: `The permission object for the privilege. permissions: `The permission object for the privilege.
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]] - Read secrets
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]] \`\`\`
2. [["read", "secrets", {environment: "dev"}]] { "permissions": [{"action": "read", "subject": "secrets"]}
\`\`\`
- Read and Write secrets
\`\`\`
{ "permissions": [{"action": "read", "subject": "secrets"], {"action": "write", "subject": "secrets"]}
\`\`\`
- Read secrets scoped to an environment and secret path
\`\`\`
- { "permissions": [{"action": "read", "subject": "secrets", "conditions": { "environment": "dev", "secretPath": { "$glob": "/" } }}] }
\`\`\`
`, `,
isPackPermission: "Whether the server should pack(compact) the permission object.",
isTemporary: "Whether the privilege is temporary.", isTemporary: "Whether the privilege is temporary.",
temporaryMode: "Type of temporary access given. Types: relative", temporaryMode: "Type of temporary access given. Types: relative",
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d", temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
@ -595,7 +665,8 @@ export const INTEGRATION = {
shouldAutoRedeploy: "Used by Render to trigger auto deploy.", shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
secretGCPLabel: "The label for GCP secrets.", secretGCPLabel: "The label for GCP secrets.",
secretAWSTag: "The tags for AWS secrets.", secretAWSTag: "The tags for AWS secrets.",
kmsKeyId: "The ID of the encryption key from AWS KMS." kmsKeyId: "The ID of the encryption key from AWS KMS.",
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store."
} }
}, },
UPDATE: { UPDATE: {
@ -612,5 +683,34 @@ export const INTEGRATION = {
}, },
DELETE: { DELETE: {
integrationId: "The ID of the integration object." integrationId: "The ID of the integration object."
},
SYNC: {
integrationId: "The ID of the integration object to manually sync"
}
};
export const AUDIT_LOG_STREAMS = {
CREATE: {
url: "The HTTP URL to push logs to.",
headers: {
desc: "The HTTP headers attached for the external prrovider requests.",
key: "The HTTP header key name.",
value: "The HTTP header value."
}
},
UPDATE: {
id: "The ID of the audit log stream to update.",
url: "The HTTP URL to push logs to.",
headers: {
desc: "The HTTP headers attached for the external prrovider requests.",
key: "The HTTP header key name.",
value: "The HTTP header value."
}
},
DELETE: {
id: "The ID of the audit log stream to delete."
},
GET_BY_ID: {
id: "The ID of the audit log stream to get details."
} }
}; };

View File

@ -13,6 +13,10 @@ const zodStrBool = z
const envSchema = z const envSchema = z
.object({ .object({
PORT: z.coerce.number().default(4000), PORT: z.coerce.number().default(4000),
DISABLE_SECRET_SCANNING: z
.enum(["true", "false"])
.default("false")
.transform((el) => el === "true"),
REDIS_URL: zpStr(z.string()), REDIS_URL: zpStr(z.string()),
HOST: zpStr(z.string().default("localhost")), HOST: zpStr(z.string().default("localhost")),
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default( DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default(
@ -119,6 +123,7 @@ const envSchema = z
}) })
.transform((data) => ({ .transform((data) => ({
...data, ...data,
isCloud: Boolean(data.LICENSE_SERVER_KEY),
isSmtpConfigured: Boolean(data.SMTP_HOST), isSmtpConfigured: Boolean(data.SMTP_HOST),
isRedisConfigured: Boolean(data.REDIS_URL), isRedisConfigured: Boolean(data.REDIS_URL),
isDevelopmentMode: data.NODE_ENV === "development", isDevelopmentMode: data.NODE_ENV === "development",

View File

@ -30,6 +30,37 @@ const loggerConfig = z.object({
NODE_ENV: z.enum(["development", "test", "production"]).default("production") NODE_ENV: z.enum(["development", "test", "production"]).default("production")
}); });
const redactedKeys = [
"accessToken",
"authToken",
"serviceToken",
"identityAccessToken",
"token",
"privateKey",
"serverPrivateKey",
"plainPrivateKey",
"plainProjectKey",
"encryptedPrivateKey",
"userPrivateKey",
"protectedKey",
"decryptKey",
"encryptedProjectKey",
"encryptedSymmetricKey",
"encryptedPrivateKey",
"backupPrivateKey",
"secretKey",
"SecretKey",
"botPrivateKey",
"encryptedKey",
"plaintextProjectKey",
"accessKey",
"botKey",
"decryptedSecret",
"secrets",
"key",
"password"
];
export const initLogger = async () => { export const initLogger = async () => {
const cfg = loggerConfig.parse(process.env); const cfg = loggerConfig.parse(process.env);
const targets: pino.TransportMultiOptions["targets"][number][] = [ const targets: pino.TransportMultiOptions["targets"][number][] = [
@ -74,7 +105,9 @@ export const initLogger = async () => {
hostname: bindings.hostname hostname: bindings.hostname
// node_version: process.version // node_version: process.version
}) })
} },
// redact until depth of three
redact: [...redactedKeys, ...redactedKeys.map((key) => `*.${key}`), ...redactedKeys.map((key) => `*.*.${key}`)]
}, },
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
transport transport

View File

@ -17,7 +17,7 @@ export type TOrgPermission = {
actorId: string; actorId: string;
orgId: string; orgId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId: string | undefined; actorOrgId: string;
}; };
export type TProjectPermission = { export type TProjectPermission = {

View File

@ -1 +1,2 @@
export { isDisposableEmail } from "./validate-email"; export { isDisposableEmail } from "./validate-email";
export { validateLocalIps } from "./validate-url";

View File

@ -0,0 +1,18 @@
import { getConfig } from "../config/env";
import { BadRequestError } from "../errors";
export const validateLocalIps = (url: string) => {
const validUrl = new URL(url);
const appCfg = getConfig();
// on cloud local ips are not allowed
if (
appCfg.isCloud &&
(validUrl.host === "host.docker.internal" ||
validUrl.host.match(/^10\.\d+\.\d+\.\d+/) ||
validUrl.host.match(/^192\.168\.\d+\.\d+/))
)
throw new BadRequestError({ message: "Local IPs not allowed as URL" });
if (validUrl.host === "localhost" || validUrl.host === "127.0.0.1")
throw new BadRequestError({ message: "Localhost not allowed" });
};

View File

@ -65,7 +65,13 @@ export type TQueueJobTypes = {
}; };
[QueueName.IntegrationSync]: { [QueueName.IntegrationSync]: {
name: QueueJobs.IntegrationSync; name: QueueJobs.IntegrationSync;
payload: { projectId: string; environment: string; secretPath: string; depth?: number }; payload: {
projectId: string;
environment: string;
secretPath: string;
depth?: number;
deDupeQueue?: Record<string, boolean>;
};
}; };
[QueueName.SecretFullRepoScan]: { [QueueName.SecretFullRepoScan]: {
name: QueueJobs.SecretScan; name: QueueJobs.SecretScan;

View File

@ -36,7 +36,7 @@ export const writeLimit: RateLimitOptions = {
export const secretsLimit: RateLimitOptions = { export const secretsLimit: RateLimitOptions = {
// secrets, folders, secret imports // secrets, folders, secret imports
timeWindow: 60 * 1000, timeWindow: 60 * 1000,
max: 600, max: 60,
keyGenerator: (req) => req.realIp keyGenerator: (req) => req.realIp
}; };

View File

@ -108,6 +108,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
if (req.url.includes("/api/v3/auth/")) { if (req.url.includes("/api/v3/auth/")) {
return; return;
} }
if (!authMode) return; if (!authMode) return;
switch (authMode) { switch (authMode) {

View File

@ -1,5 +1,6 @@
import fp from "fastify-plugin"; import fp from "fastify-plugin";
import { logger } from "@app/lib/logger";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
// inject permission type needed based on auth extracted // inject permission type needed based on auth extracted
@ -15,6 +16,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY" orgId: req.auth.orgId, // if the req.auth.authMode is AuthMode.API_KEY, the orgId will be "API_KEY"
authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null authMethod: req.auth.authMethod // if the req.auth.authMode is AuthMode.API_KEY, the authMethod will be null
}; };
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.userId}] [type=${ActorType.USER}]`
);
} else if (req.auth.actor === ActorType.IDENTITY) { } else if (req.auth.actor === ActorType.IDENTITY) {
req.permission = { req.permission = {
type: ActorType.IDENTITY, type: ActorType.IDENTITY,
@ -22,6 +27,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId, orgId: req.auth.orgId,
authMethod: null authMethod: null
}; };
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.identityId}] [type=${ActorType.IDENTITY}]`
);
} else if (req.auth.actor === ActorType.SERVICE) { } else if (req.auth.actor === ActorType.SERVICE) {
req.permission = { req.permission = {
type: ActorType.SERVICE, type: ActorType.SERVICE,
@ -29,6 +38,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId, orgId: req.auth.orgId,
authMethod: null authMethod: null
}; };
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.serviceTokenId}] [type=${ActorType.SERVICE}]`
);
} else if (req.auth.actor === ActorType.SCIM_CLIENT) { } else if (req.auth.actor === ActorType.SCIM_CLIENT) {
req.permission = { req.permission = {
type: ActorType.SCIM_CLIENT, type: ActorType.SCIM_CLIENT,
@ -36,6 +49,10 @@ export const injectPermission = fp(async (server) => {
orgId: req.auth.orgId, orgId: req.auth.orgId,
authMethod: null authMethod: null
}; };
logger.info(
`injectPermission: Injecting permissions for [permissionsForIdentity=${req.auth.scimTokenId}] [type=${ActorType.SCIM_CLIENT}]`
);
} }
}); });
}); });

View File

@ -5,8 +5,13 @@ import { getConfig } from "@app/lib/config/env";
export const maintenanceMode = fp(async (fastify) => { export const maintenanceMode = fp(async (fastify) => {
fastify.addHook("onRequest", async (req) => { fastify.addHook("onRequest", async (req) => {
const serverEnvs = getConfig(); const serverEnvs = getConfig();
if (req.url !== "/api/v1/auth/checkAuth" && req.method !== "GET" && serverEnvs.MAINTENANCE_MODE) { if (serverEnvs.MAINTENANCE_MODE) {
throw new Error("Infisical is in maintenance mode. Please try again later."); // skip if its universal auth login or renew
if (req.url === "/api/v1/auth/universal-auth/login" && req.method === "POST") return;
if (req.url === "/api/v1/auth/token/renew" && req.method === "POST") return;
if (req.url !== "/api/v1/auth/checkAuth" && req.method !== "GET") {
throw new Error("Infisical is in maintenance mode. Please try again later.");
}
} }
}); });
}); });

View File

@ -2,9 +2,17 @@ import { Knex } from "knex";
import { z } from "zod"; import { z } from "zod";
import { registerV1EERoutes } from "@app/ee/routes/v1"; import { registerV1EERoutes } from "@app/ee/routes/v1";
import { accessApprovalPolicyApproverDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal"; import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue"; import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service"; import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
import { auditLogStreamDALFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-dal";
import { auditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal"; import { dynamicSecretDALFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-dal";
import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service"; import { dynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers"; import { buildDynamicSecretProviders } from "@app/ee/services/dynamic-secret/providers";
@ -70,6 +78,12 @@ import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { identityServiceFactory } from "@app/services/identity/identity-service"; import { identityServiceFactory } from "@app/services/identity/identity-service";
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal"; import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service"; import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
import { identityGcpAuthDALFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-dal";
import { identityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
import { identityKubernetesAuthDALFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-dal";
import { identityKubernetesAuthServiceFactory } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal"; import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal"; import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal";
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service"; import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
@ -86,6 +100,7 @@ import { orgDALFactory } from "@app/services/org/org-dal";
import { orgRoleDALFactory } from "@app/services/org/org-role-dal"; import { orgRoleDALFactory } from "@app/services/org/org-role-dal";
import { orgRoleServiceFactory } from "@app/services/org/org-role-service"; import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
import { orgServiceFactory } from "@app/services/org/org-service"; import { orgServiceFactory } from "@app/services/org/org-service";
import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { projectDALFactory } from "@app/services/project/project-dal"; import { projectDALFactory } from "@app/services/project/project-dal";
import { projectQueueFactory } from "@app/services/project/project-queue"; import { projectQueueFactory } from "@app/services/project/project-queue";
import { projectServiceFactory } from "@app/services/project/project-service"; import { projectServiceFactory } from "@app/services/project/project-service";
@ -145,7 +160,10 @@ export const registerRoutes = async (
keyStore keyStore
}: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory } }: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory }
) => { ) => {
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" }); const appCfg = getConfig();
if (!appCfg.DISABLE_SECRET_SCANNING) {
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
}
// db layers // db layers
const userDAL = userDALFactory(db); const userDAL = userDALFactory(db);
@ -153,6 +171,7 @@ export const registerRoutes = async (
const authDAL = authDALFactory(db); const authDAL = authDALFactory(db);
const authTokenDAL = tokenDALFactory(db); const authTokenDAL = tokenDALFactory(db);
const orgDAL = orgDALFactory(db); const orgDAL = orgDALFactory(db);
const orgMembershipDAL = orgMembershipDALFactory(db);
const orgBotDAL = orgBotDALFactory(db); const orgBotDAL = orgBotDALFactory(db);
const incidentContactDAL = incidentContactDALFactory(db); const incidentContactDAL = incidentContactDALFactory(db);
const orgRoleDAL = orgRoleDALFactory(db); const orgRoleDAL = orgRoleDALFactory(db);
@ -190,9 +209,14 @@ export const registerRoutes = async (
const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db); const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db);
const identityUaDAL = identityUaDALFactory(db); const identityUaDAL = identityUaDALFactory(db);
const identityKubernetesAuthDAL = identityKubernetesAuthDALFactory(db);
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db); const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const identityAwsAuthDAL = identityAwsAuthDALFactory(db);
const identityGcpAuthDAL = identityGcpAuthDALFactory(db);
const auditLogDAL = auditLogDALFactory(db); const auditLogDAL = auditLogDALFactory(db);
const auditLogStreamDAL = auditLogStreamDALFactory(db);
const trustedIpDAL = trustedIpDALFactory(db); const trustedIpDAL = trustedIpDALFactory(db);
const telemetryDAL = telemetryDALFactory(db); const telemetryDAL = telemetryDALFactory(db);
@ -202,6 +226,12 @@ export const registerRoutes = async (
const scimDAL = scimDALFactory(db); const scimDAL = scimDALFactory(db);
const ldapConfigDAL = ldapConfigDALFactory(db); const ldapConfigDAL = ldapConfigDALFactory(db);
const ldapGroupMapDAL = ldapGroupMapDALFactory(db); const ldapGroupMapDAL = ldapGroupMapDALFactory(db);
const accessApprovalPolicyDAL = accessApprovalPolicyDALFactory(db);
const accessApprovalRequestDAL = accessApprovalRequestDALFactory(db);
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db); const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db); const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db); const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
@ -243,9 +273,15 @@ export const registerRoutes = async (
auditLogDAL, auditLogDAL,
queueService, queueService,
projectDAL, projectDAL,
licenseService licenseService,
auditLogStreamDAL
}); });
const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue }); const auditLogService = auditLogServiceFactory({ auditLogDAL, permissionService, auditLogQueue });
const auditLogStreamService = auditLogStreamServiceFactory({
licenseService,
permissionService,
auditLogStreamDAL
});
const sapService = secretApprovalPolicyServiceFactory({ const sapService = secretApprovalPolicyServiceFactory({
projectMembershipDAL, projectMembershipDAL,
projectEnvDAL, projectEnvDAL,
@ -253,13 +289,19 @@ export const registerRoutes = async (
permissionService, permissionService,
secretApprovalPolicyDAL secretApprovalPolicyDAL
}); });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const samlService = samlConfigServiceFactory({ const samlService = samlConfigServiceFactory({
permissionService, permissionService,
orgBotDAL, orgBotDAL,
orgDAL, orgDAL,
orgMembershipDAL,
userDAL, userDAL,
userAliasDAL,
samlConfigDAL, samlConfigDAL,
licenseService licenseService,
tokenService,
smtpService
}); });
const groupService = groupServiceFactory({ const groupService = groupServiceFactory({
userDAL, userDAL,
@ -288,7 +330,9 @@ export const registerRoutes = async (
licenseService, licenseService,
scimDAL, scimDAL,
userDAL, userDAL,
userAliasDAL,
orgDAL, orgDAL,
orgMembershipDAL,
projectDAL, projectDAL,
projectMembershipDAL, projectMembershipDAL,
groupDAL, groupDAL,
@ -304,6 +348,7 @@ export const registerRoutes = async (
ldapConfigDAL, ldapConfigDAL,
ldapGroupMapDAL, ldapGroupMapDAL,
orgDAL, orgDAL,
orgMembershipDAL,
orgBotDAL, orgBotDAL,
groupDAL, groupDAL,
groupProjectDAL, groupProjectDAL,
@ -327,8 +372,13 @@ export const registerRoutes = async (
queueService queueService
}); });
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL }); const userService = userServiceFactory({
const userService = userServiceFactory({ userDAL }); userDAL,
userAliasDAL,
orgMembershipDAL,
tokenService,
smtpService
});
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL }); const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL });
const passwordService = authPaswordServiceFactory({ const passwordService = authPaswordServiceFactory({
tokenService, tokenService,
@ -337,6 +387,7 @@ export const registerRoutes = async (
userDAL userDAL
}); });
const orgService = orgServiceFactory({ const orgService = orgServiceFactory({
userAliasDAL,
licenseService, licenseService,
samlConfigDAL, samlConfigDAL,
orgRoleDAL, orgRoleDAL,
@ -497,8 +548,10 @@ export const registerRoutes = async (
folderDAL, folderDAL,
folderVersionDAL, folderVersionDAL,
projectEnvDAL, projectEnvDAL,
snapshotService snapshotService,
projectDAL
}); });
const integrationAuthService = integrationAuthServiceFactory({ const integrationAuthService = integrationAuthServiceFactory({
integrationAuthDAL, integrationAuthDAL,
integrationDAL, integrationDAL,
@ -557,6 +610,7 @@ export const registerRoutes = async (
}); });
const sarService = secretApprovalRequestServiceFactory({ const sarService = secretApprovalRequestServiceFactory({
permissionService, permissionService,
projectBotService,
folderDAL, folderDAL,
secretDAL, secretDAL,
secretTagDAL, secretTagDAL,
@ -571,6 +625,30 @@ export const registerRoutes = async (
secretVersionTagDAL, secretVersionTagDAL,
secretQueueService secretQueueService
}); });
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL,
permissionService,
projectEnvDAL,
projectMembershipDAL,
projectDAL
});
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
projectDAL,
permissionService,
accessApprovalRequestReviewerDAL,
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
projectMembershipDAL,
accessApprovalPolicyDAL,
accessApprovalRequestDAL,
projectEnvDAL,
userDAL,
smtpService,
accessApprovalPolicyApproverDAL
});
const secretRotationQueue = secretRotationQueueFactory({ const secretRotationQueue = secretRotationQueueFactory({
telemetryService, telemetryService,
secretRotationDAL, secretRotationDAL,
@ -637,6 +715,32 @@ export const registerRoutes = async (
identityUaDAL, identityUaDAL,
licenseService licenseService
}); });
const identityKubernetesAuthService = identityKubernetesAuthServiceFactory({
identityKubernetesAuthDAL,
identityOrgMembershipDAL,
identityAccessTokenDAL,
identityDAL,
orgBotDAL,
permissionService,
licenseService
});
const identityGcpAuthService = identityGcpAuthServiceFactory({
identityGcpAuthDAL,
identityOrgMembershipDAL,
identityAccessTokenDAL,
identityDAL,
permissionService,
licenseService
});
const identityAwsAuthService = identityAwsAuthServiceFactory({
identityAccessTokenDAL,
identityAwsAuthDAL,
identityOrgMembershipDAL,
identityDAL,
licenseService,
permissionService
});
const dynamicSecretProviders = buildDynamicSecretProviders(); const dynamicSecretProviders = buildDynamicSecretProviders();
const dynamicSecretQueueService = dynamicSecretLeaseQueueServiceFactory({ const dynamicSecretQueueService = dynamicSecretLeaseQueueServiceFactory({
@ -706,7 +810,12 @@ export const registerRoutes = async (
identityAccessToken: identityAccessTokenService, identityAccessToken: identityAccessTokenService,
identityProject: identityProjectService, identityProject: identityProjectService,
identityUa: identityUaService, identityUa: identityUaService,
identityKubernetesAuth: identityKubernetesAuthService,
identityGcpAuth: identityGcpAuthService,
identityAwsAuth: identityAwsAuthService,
secretApprovalPolicy: sapService, secretApprovalPolicy: sapService,
accessApprovalPolicy: accessApprovalPolicyService,
accessApprovalRequest: accessApprovalRequestService,
secretApprovalRequest: sarService, secretApprovalRequest: sarService,
secretRotation: secretRotationService, secretRotation: secretRotationService,
dynamicSecret: dynamicSecretService, dynamicSecret: dynamicSecretService,
@ -715,6 +824,7 @@ export const registerRoutes = async (
saml: samlService, saml: samlService,
ldap: ldapService, ldap: ldapService,
auditLog: auditLogService, auditLog: auditLogService,
auditLogStream: auditLogStreamService,
secretScanning: secretScanningService, secretScanning: secretScanningService,
license: licenseService, license: licenseService,
trustedIp: trustedIpService, trustedIp: trustedIpService,

View File

@ -2,10 +2,13 @@ import { z } from "zod";
import { import {
DynamicSecretsSchema, DynamicSecretsSchema,
IdentityProjectAdditionalPrivilegeSchema,
IntegrationAuthsSchema, IntegrationAuthsSchema,
SecretApprovalPoliciesSchema, SecretApprovalPoliciesSchema,
UsersSchema UsersSchema
} from "@app/db/schemas"; } from "@app/db/schemas";
import { UnpackedPermissionSchema } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
// sometimes the return data must be santizied to avoid leaking important values // sometimes the return data must be santizied to avoid leaking important values
// always prefer pick over omit in zod // always prefer pick over omit in zod
@ -62,6 +65,33 @@ export const secretRawSchema = z.object({
secretComment: z.string().optional() secretComment: z.string().optional()
}); });
export const ProjectPermissionSchema = z.object({
action: z
.nativeEnum(ProjectPermissionActions)
.describe("Describe what action an entity can take. Possible actions: create, edit, delete, and read"),
subject: z
.nativeEnum(ProjectPermissionSub)
.describe("The entity this permission pertains to. Possible options: secrets, environments"),
conditions: z
.object({
environment: z.string().describe("The environment slug this permission should allow.").optional(),
secretPath: z
.object({
$glob: z
.string()
.min(1)
.describe("The secret path this permission should allow. Can be a glob pattern such as /folder-name/*/** ")
})
.optional()
})
.describe("When specified, only matching conditions will be allowed to access given resource.")
.optional()
});
export const SanitizedIdentityPrivilegeSchema = IdentityProjectAdditionalPrivilegeSchema.extend({
permissions: UnpackedPermissionSchema.array()
});
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
inputIV: true, inputIV: true,
inputTag: true, inputTag: true,
@ -69,3 +99,10 @@ export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
keyEncoding: true, keyEncoding: true,
algorithm: true algorithm: true
}); });
export const SanitizedAuditLogStreamSchema = z.object({
id: z.string(),
url: z.string(),
createdAt: z.date(),
updatedAt: z.date()
});

View File

@ -20,16 +20,23 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
response: { response: {
200: z.object({ 200: z.object({
config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).merge( config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).extend({
z.object({ isMigrationModeOn: z.boolean() }) isMigrationModeOn: z.boolean(),
) isSecretScanningDisabled: z.boolean()
})
}) })
} }
}, },
handler: async () => { handler: async () => {
const config = await getServerCfg(); const config = await getServerCfg();
const serverEnvs = getConfig(); const serverEnvs = getConfig();
return { config: { ...config, isMigrationModeOn: serverEnvs.MAINTENANCE_MODE } }; return {
config: {
...config,
isMigrationModeOn: serverEnvs.MAINTENANCE_MODE,
isSecretScanningDisabled: serverEnvs.DISABLE_SECRET_SCANNING
}
};
} }
}); });
@ -42,7 +49,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
schema: { schema: {
body: z.object({ body: z.object({
allowSignUp: z.boolean().optional(), allowSignUp: z.boolean().optional(),
allowedSignUpDomain: z.string().optional().nullable() allowedSignUpDomain: z.string().optional().nullable(),
trustSamlEmails: z.boolean().optional(),
trustLdapEmails: z.boolean().optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({

View File

@ -36,4 +36,29 @@ export const registerIdentityAccessTokenRouter = async (server: FastifyZodProvid
}; };
} }
}); });
server.route({
url: "/token/revoke",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
description: "Revoke access token",
body: z.object({
accessToken: z.string().trim().describe(UNIVERSAL_AUTH.REVOKE_ACCESS_TOKEN.accessToken)
}),
response: {
200: z.object({
message: z.string()
})
}
},
handler: async (req) => {
await server.services.identityAccessToken.revokeAccessToken(req.body.accessToken);
return {
message: "Successfully revoked access token"
};
}
});
}; };

View File

@ -0,0 +1,269 @@
import { z } from "zod";
import { IdentityAwsAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { AWS_AUTH } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import {
validateAccountIds,
validatePrincipalArns
} from "@app/services/identity-aws-auth/identity-aws-auth-validators";
export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/aws-auth/login",
config: {
rateLimit: writeLimit
},
schema: {
description: "Login with AWS Auth",
body: z.object({
identityId: z.string().describe(AWS_AUTH.LOGIN.identityId),
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { identityAwsAuth, accessToken, identityAccessToken, identityMembershipOrg } =
await server.services.identityAwsAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
event: {
type: EventType.LOGIN_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
identityAwsAuthId: identityAwsAuth.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL
};
}
});
server.route({
method: "POST",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Attach AWS Auth configuration onto identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
stsEndpoint: z.string().trim().min(1).default("https://sts.amazonaws.com/"),
allowedPrincipalArns: validatePrincipalArns,
allowedAccountIds: validateAccountIds,
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z
.number()
.int()
.min(1)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000),
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.attachAwsAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAwsAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId,
stsEndpoint: identityAwsAuth.stsEndpoint,
allowedPrincipalArns: identityAwsAuth.allowedPrincipalArns,
allowedAccountIds: identityAwsAuth.allowedAccountIds,
accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAwsAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
}
}
});
return { identityAwsAuth };
}
});
server.route({
method: "PATCH",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update AWS Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
body: z.object({
stsEndpoint: z.string().trim().min(1).optional(),
allowedPrincipalArns: validatePrincipalArns,
allowedAccountIds: validateAccountIds,
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.updateAwsAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAwsAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId,
stsEndpoint: identityAwsAuth.stsEndpoint,
allowedPrincipalArns: identityAwsAuth.allowedPrincipalArns,
allowedAccountIds: identityAwsAuth.allowedAccountIds,
accessTokenTTL: identityAwsAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityAwsAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityAwsAuth.accessTokenNumUsesLimit
}
}
});
return { identityAwsAuth };
}
});
server.route({
method: "GET",
url: "/aws-auth/identities/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Retrieve AWS Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identityAwsAuth: IdentityAwsAuthsSchema
})
}
},
handler: async (req) => {
const identityAwsAuth = await server.services.identityAwsAuth.getAwsAuth({
identityId: req.params.identityId,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityAwsAuth.orgId,
event: {
type: EventType.GET_IDENTITY_AWS_AUTH,
metadata: {
identityId: identityAwsAuth.identityId
}
}
});
return { identityAwsAuth };
}
});
};

View File

@ -0,0 +1,268 @@
import { z } from "zod";
import { IdentityGcpAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
import { validateGcpAuthField } from "@app/services/identity-gcp-auth/identity-gcp-auth-validators";
export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/gcp-auth/login",
config: {
rateLimit: writeLimit
},
schema: {
description: "Login with GCP Auth",
body: z.object({
identityId: z.string(),
jwt: z.string()
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { identityGcpAuth, accessToken, identityAccessToken, identityMembershipOrg } =
await server.services.identityGcpAuth.login(req.body);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
event: {
type: EventType.LOGIN_IDENTITY_GCP_AUTH,
metadata: {
identityId: identityGcpAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
identityGcpAuthId: identityGcpAuth.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityGcpAuth.accessTokenTTL,
accessTokenMaxTTL: identityGcpAuth.accessTokenMaxTTL
};
}
});
server.route({
method: "POST",
url: "/gcp-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Attach GCP Auth configuration onto identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
type: z.enum(["iam", "gce"]),
allowedServiceAccounts: validateGcpAuthField,
allowedProjects: validateGcpAuthField,
allowedZones: validateGcpAuthField,
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z
.number()
.int()
.min(1)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000),
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
}),
response: {
200: z.object({
identityGcpAuth: IdentityGcpAuthsSchema
})
}
},
handler: async (req) => {
const identityGcpAuth = await server.services.identityGcpAuth.attachGcpAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityGcpAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_GCP_AUTH,
metadata: {
identityId: identityGcpAuth.identityId,
type: identityGcpAuth.type,
allowedServiceAccounts: identityGcpAuth.allowedServiceAccounts,
allowedProjects: identityGcpAuth.allowedProjects,
allowedZones: identityGcpAuth.allowedZones,
accessTokenTTL: identityGcpAuth.accessTokenTTL,
accessTokenMaxTTL: identityGcpAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityGcpAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityGcpAuth.accessTokenNumUsesLimit
}
}
});
return { identityGcpAuth };
}
});
server.route({
method: "PATCH",
url: "/gcp-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update GCP Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
type: z.enum(["iam", "gce"]).optional(),
allowedServiceAccounts: validateGcpAuthField,
allowedProjects: validateGcpAuthField,
allowedZones: validateGcpAuthField,
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
}),
response: {
200: z.object({
identityGcpAuth: IdentityGcpAuthsSchema
})
}
},
handler: async (req) => {
const identityGcpAuth = await server.services.identityGcpAuth.updateGcpAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityGcpAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_GCP_AUTH,
metadata: {
identityId: identityGcpAuth.identityId,
type: identityGcpAuth.type,
allowedServiceAccounts: identityGcpAuth.allowedServiceAccounts,
allowedProjects: identityGcpAuth.allowedProjects,
allowedZones: identityGcpAuth.allowedZones,
accessTokenTTL: identityGcpAuth.accessTokenTTL,
accessTokenMaxTTL: identityGcpAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityGcpAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityGcpAuth.accessTokenNumUsesLimit
}
}
});
return { identityGcpAuth };
}
});
server.route({
method: "GET",
url: "/gcp-auth/identities/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Retrieve GCP Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identityGcpAuth: IdentityGcpAuthsSchema
})
}
},
handler: async (req) => {
const identityGcpAuth = await server.services.identityGcpAuth.getGcpAuth({
identityId: req.params.identityId,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityGcpAuth.orgId,
event: {
type: EventType.GET_IDENTITY_GCP_AUTH,
metadata: {
identityId: identityGcpAuth.identityId
}
}
});
return { identityGcpAuth };
}
});
};

View File

@ -0,0 +1,283 @@
import { z } from "zod";
import { IdentityKubernetesAuthsSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
const IdentityKubernetesAuthResponseSchema = IdentityKubernetesAuthsSchema.omit({
encryptedCaCert: true,
caCertIV: true,
caCertTag: true,
encryptedTokenReviewerJwt: true,
tokenReviewerJwtIV: true,
tokenReviewerJwtTag: true
}).extend({
caCert: z.string(),
tokenReviewerJwt: z.string()
});
export const registerIdentityKubernetesRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/kubernetes-auth/login",
config: {
rateLimit: writeLimit
},
schema: {
description: "Login with Kubernetes Auth",
body: z.object({
identityId: z.string().trim(),
jwt: z.string().trim()
}),
response: {
200: z.object({
accessToken: z.string(),
expiresIn: z.coerce.number(),
accessTokenMaxTTL: z.coerce.number(),
tokenType: z.literal("Bearer")
})
}
},
handler: async (req) => {
const { identityKubernetesAuth, accessToken, identityAccessToken, identityMembershipOrg } =
await server.services.identityKubernetesAuth.login({
identityId: req.body.identityId,
jwt: req.body.jwt
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityMembershipOrg?.orgId,
event: {
type: EventType.LOGIN_IDENTITY_KUBERNETES_AUTH,
metadata: {
identityId: identityKubernetesAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
identityKubernetesAuthId: identityKubernetesAuth.id
}
}
});
return {
accessToken,
tokenType: "Bearer" as const,
expiresIn: identityKubernetesAuth.accessTokenTTL,
accessTokenMaxTTL: identityKubernetesAuth.accessTokenMaxTTL
};
}
});
server.route({
method: "POST",
url: "/kubernetes-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Attach Kubernetes Auth configuration onto identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string().trim()
}),
body: z.object({
kubernetesHost: z.string().trim().min(1),
caCert: z.string().trim().default(""),
tokenReviewerJwt: z.string().trim().min(1),
allowedNamespaces: z.string(), // TODO: validation
allowedNames: z.string(),
allowedAudience: z.string(),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]),
accessTokenTTL: z
.number()
.int()
.min(1)
.refine((value) => value !== 0, {
message: "accessTokenTTL must have a non zero number"
})
.default(2592000),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.default(2592000),
accessTokenNumUsesLimit: z.number().int().min(0).default(0)
}),
response: {
200: z.object({
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
})
}
},
handler: async (req) => {
const identityKubernetesAuth = await server.services.identityKubernetesAuth.attachKubernetesAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityKubernetesAuth.orgId,
event: {
type: EventType.ADD_IDENTITY_KUBERNETES_AUTH,
metadata: {
identityId: identityKubernetesAuth.identityId,
kubernetesHost: identityKubernetesAuth.kubernetesHost,
allowedNamespaces: identityKubernetesAuth.allowedNamespaces,
allowedNames: identityKubernetesAuth.allowedNames,
accessTokenTTL: identityKubernetesAuth.accessTokenTTL,
accessTokenMaxTTL: identityKubernetesAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityKubernetesAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityKubernetesAuth.accessTokenNumUsesLimit
}
}
});
return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) };
}
});
server.route({
method: "PATCH",
url: "/kubernetes-auth/identities/:identityId",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Update Kubernetes Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
body: z.object({
kubernetesHost: z.string().trim().min(1).optional(),
caCert: z.string().trim().optional(),
tokenReviewerJwt: z.string().trim().min(1).optional(),
allowedNamespaces: z.string().optional(), // TODO: validation
allowedNames: z.string().optional(),
allowedAudience: z.string().optional(),
accessTokenTrustedIps: z
.object({
ipAddress: z.string().trim()
})
.array()
.min(1)
.optional(),
accessTokenTTL: z.number().int().min(0).optional(),
accessTokenNumUsesLimit: z.number().int().min(0).optional(),
accessTokenMaxTTL: z
.number()
.int()
.refine((value) => value !== 0, {
message: "accessTokenMaxTTL must have a non zero number"
})
.optional()
}),
response: {
200: z.object({
identityKubernetesAuth: IdentityKubernetesAuthsSchema
})
}
},
handler: async (req) => {
const identityKubernetesAuth = await server.services.identityKubernetesAuth.updateKubernetesAuth({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body,
identityId: req.params.identityId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityKubernetesAuth.orgId,
event: {
type: EventType.UPDATE_IDENTITY_KUBENETES_AUTH,
metadata: {
identityId: identityKubernetesAuth.identityId,
kubernetesHost: identityKubernetesAuth.kubernetesHost,
allowedNamespaces: identityKubernetesAuth.allowedNamespaces,
allowedNames: identityKubernetesAuth.allowedNames,
accessTokenTTL: identityKubernetesAuth.accessTokenTTL,
accessTokenMaxTTL: identityKubernetesAuth.accessTokenMaxTTL,
accessTokenTrustedIps: identityKubernetesAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
accessTokenNumUsesLimit: identityKubernetesAuth.accessTokenNumUsesLimit
}
}
});
return { identityKubernetesAuth };
}
});
server.route({
method: "GET",
url: "/kubernetes-auth/identities/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Retrieve Kubernetes Auth configuration on identity",
security: [
{
bearerAuth: []
}
],
params: z.object({
identityId: z.string()
}),
response: {
200: z.object({
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
})
}
},
handler: async (req) => {
const identityKubernetesAuth = await server.services.identityKubernetesAuth.getKubernetesAuth({
identityId: req.params.identityId,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: identityKubernetesAuth.orgId,
event: {
type: EventType.GET_IDENTITY_KUBERNETES_AUTH,
metadata: {
identityId: identityKubernetesAuth.identityId
}
}
});
return { identityKubernetesAuth: IdentityKubernetesAuthResponseSchema.parse(identityKubernetesAuth) };
}
});
};

View File

@ -2,6 +2,9 @@ import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router"; import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router"; import { registerProjectBotRouter } from "./bot-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router"; import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
import { registerIdentityGcpAuthRouter } from "./identity-gcp-auth-router";
import { registerIdentityKubernetesRouter } from "./identity-kubernetes-auth-router";
import { registerIdentityRouter } from "./identity-router"; import { registerIdentityRouter } from "./identity-router";
import { registerIdentityUaRouter } from "./identity-ua"; import { registerIdentityUaRouter } from "./identity-ua";
import { registerIntegrationAuthRouter } from "./integration-auth-router"; import { registerIntegrationAuthRouter } from "./integration-auth-router";
@ -27,7 +30,10 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
async (authRouter) => { async (authRouter) => {
await authRouter.register(registerAuthRoutes); await authRouter.register(registerAuthRoutes);
await authRouter.register(registerIdentityUaRouter); await authRouter.register(registerIdentityUaRouter);
await authRouter.register(registerIdentityKubernetesRouter);
await authRouter.register(registerIdentityGcpAuthRouter);
await authRouter.register(registerIdentityAccessTokenRouter); await authRouter.register(registerIdentityAccessTokenRouter);
await authRouter.register(registerIdentityAwsAuthRouter);
}, },
{ prefix: "/auth" } { prefix: "/auth" }
); );

View File

@ -66,7 +66,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
) )
.optional() .optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag), .describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId) kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
}) })
.default({}) .default({})
}), }),
@ -142,8 +143,8 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId) integrationId: z.string().trim().describe(INTEGRATION.UPDATE.integrationId)
}), }),
body: z.object({ body: z.object({
app: z.string().trim().describe(INTEGRATION.UPDATE.app), app: z.string().trim().optional().describe(INTEGRATION.UPDATE.app),
appId: z.string().trim().describe(INTEGRATION.UPDATE.appId), appId: z.string().trim().optional().describe(INTEGRATION.UPDATE.appId),
isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive), isActive: z.boolean().describe(INTEGRATION.UPDATE.isActive),
secretPath: z secretPath: z
.string() .string()
@ -153,7 +154,33 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
.describe(INTEGRATION.UPDATE.secretPath), .describe(INTEGRATION.UPDATE.secretPath),
targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment), targetEnvironment: z.string().trim().describe(INTEGRATION.UPDATE.targetEnvironment),
owner: z.string().trim().describe(INTEGRATION.UPDATE.owner), owner: z.string().trim().describe(INTEGRATION.UPDATE.owner),
environment: z.string().trim().describe(INTEGRATION.UPDATE.environment) environment: z.string().trim().describe(INTEGRATION.UPDATE.environment),
metadata: z
.object({
secretPrefix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretPrefix),
secretSuffix: z.string().optional().describe(INTEGRATION.CREATE.metadata.secretSuffix),
initialSyncBehavior: z.string().optional().describe(INTEGRATION.CREATE.metadata.initialSyncBehavoir),
shouldAutoRedeploy: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldAutoRedeploy),
secretGCPLabel: z
.object({
labelName: z.string(),
labelValue: z.string()
})
.optional()
.describe(INTEGRATION.CREATE.metadata.secretGCPLabel),
secretAWSTag: z
.array(
z.object({
key: z.string(),
value: z.string()
})
)
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
})
.optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -235,5 +262,64 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
} }
}); });
// TODO(akhilmhdh-pg): manual sync server.route({
method: "POST",
url: "/:integrationId/sync",
config: {
rateLimit: writeLimit
},
schema: {
description: "Manually trigger sync of an integration by integration id",
security: [
{
bearerAuth: []
}
],
params: z.object({
integrationId: z.string().trim().describe(INTEGRATION.SYNC.integrationId)
}),
response: {
200: z.object({
integration: IntegrationsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const integration = await server.services.integration.syncIntegration({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
id: req.params.integrationId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: integration.projectId,
event: {
type: EventType.MANUAL_SYNC_INTEGRATION,
// eslint-disable-next-line
metadata: shake({
integrationId: integration.id,
integration: integration.integration,
environment: integration.environment.slug,
secretPath: integration.secretPath,
url: integration.url,
app: integration.app,
appId: integration.appId,
targetEnvironment: integration.targetEnvironment,
targetEnvironmentId: integration.targetEnvironmentId,
targetService: integration.targetService,
targetServiceId: integration.targetServiceId,
path: integration.path,
region: integration.region
// eslint-disable-next-line
}) as any
}
});
return { integration };
}
});
}; };

View File

@ -9,7 +9,7 @@ import {
UsersSchema UsersSchema
} from "@app/db/schemas"; } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { PROJECTS } from "@app/lib/api-docs"; import { PROJECT_USERS } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@ -30,7 +30,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
} }
], ],
params: z.object({ params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.GET_USER_MEMBERSHIPS.workspaceId) workspaceId: z.string().trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIPS.workspaceId)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -74,6 +74,66 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
} }
}); });
server.route({
method: "POST",
url: "/:workspaceId/memberships/details",
config: {
rateLimit: readLimit
},
schema: {
description: "Return project user memberships",
security: [
{
bearerAuth: []
}
],
params: z.object({
workspaceId: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.workspaceId)
}),
body: z.object({
username: z.string().min(1).trim().describe(PROJECT_USERS.GET_USER_MEMBERSHIP.username)
}),
response: {
200: z.object({
membership: ProjectMembershipsSchema.extend({
user: UsersSchema.pick({
email: true,
firstName: true,
lastName: true,
id: true
}).merge(UserEncryptionKeysSchema.pick({ publicKey: true })),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
)
}).omit({ createdAt: true, updatedAt: true })
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const membership = await server.services.projectMembership.getProjectMembershipByUsername({
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId,
username: req.body.username
});
return { membership };
}
});
server.route({ server.route({
method: "POST", method: "POST",
url: "/:workspaceId/memberships", url: "/:workspaceId/memberships",
@ -142,8 +202,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
} }
], ],
params: z.object({ params: z.object({
workspaceId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.workspaceId), workspaceId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.workspaceId),
membershipId: z.string().trim().describe(PROJECTS.UPDATE_USER_MEMBERSHIP.membershipId) membershipId: z.string().trim().describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.membershipId)
}), }),
body: z.object({ body: z.object({
roles: z roles: z
@ -164,7 +224,7 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
) )
.min(1) .min(1)
.refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least one long lived role is required") .refine((data) => data.some(({ isTemporary }) => !isTemporary), "At least one long lived role is required")
.describe(PROJECTS.UPDATE_USER_MEMBERSHIP.roles) .describe(PROJECT_USERS.UPDATE_USER_MEMBERSHIP.roles)
}), }),
response: { response: {
200: z.object({ 200: z.object({

View File

@ -127,6 +127,70 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) =>
} }
}); });
server.route({
url: "/batch",
method: "PATCH",
config: {
rateLimit: secretsLimit
},
schema: {
description: "Update folders by batch",
security: [
{
bearerAuth: []
}
],
body: z.object({
projectSlug: z.string().trim().describe(FOLDERS.UPDATE.projectSlug),
folders: z
.object({
id: z.string().describe(FOLDERS.UPDATE.folderId),
environment: z.string().trim().describe(FOLDERS.UPDATE.environment),
name: z.string().trim().describe(FOLDERS.UPDATE.name),
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(FOLDERS.UPDATE.path)
})
.array()
.min(1)
}),
response: {
200: z.object({
folders: SecretFoldersSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { newFolders, oldFolders, projectId } = await server.services.folder.updateManyFolders({
...req.body,
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await Promise.all(
req.body.folders.map(async (folder, index) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.UPDATE_FOLDER,
metadata: {
environment: oldFolders[index].envId,
folderId: oldFolders[index].id,
folderPath: folder.path,
newFolderName: newFolders[index].name,
oldFolderName: oldFolders[index].name
}
}
});
})
);
return { folders: newFolders };
}
});
// TODO(daniel): Expose this route in api reference and write docs for it. // TODO(daniel): Expose this route in api reference and write docs for it.
server.route({ server.route({
method: "DELETE", method: "DELETE",

View File

@ -7,7 +7,8 @@ import {
ProjectMembershipRole, ProjectMembershipRole,
ProjectUserMembershipRolesSchema ProjectUserMembershipRolesSchema
} from "@app/db/schemas"; } from "@app/db/schemas";
import { PROJECTS } from "@app/lib/api-docs"; import { PROJECT_IDENTITIES } from "@app/lib/api-docs";
import { BadRequestError } from "@app/lib/errors";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
@ -22,12 +23,48 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: { schema: {
description: "Create project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({ params: z.object({
projectId: z.string().trim(), projectId: z.string().trim(),
identityId: z.string().trim() identityId: z.string().trim()
}), }),
body: z.object({ body: z.object({
role: z.string().trim().min(1).default(ProjectMembershipRole.NoAccess) // @depreciated
role: z.string().trim().optional().default(ProjectMembershipRole.NoAccess),
roles: z
.array(
z.union([
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
}),
z.object({
role: z.string().describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryMode: z
.nativeEnum(ProjectUserMembershipTemporaryMode)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.role)
})
])
)
.describe(PROJECT_IDENTITIES.CREATE_IDENTITY_MEMBERSHIP.roles.description)
.optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -36,6 +73,9 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
} }
}, },
handler: async (req) => { handler: async (req) => {
const { role, roles } = req.body;
if (!role && !roles) throw new BadRequestError({ message: "You must provide either role or roles field" });
const identityMembership = await server.services.identityProject.createProjectIdentity({ const identityMembership = await server.services.identityProject.createProjectIdentity({
actor: req.permission.type, actor: req.permission.type,
actorId: req.permission.id, actorId: req.permission.id,
@ -43,7 +83,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
identityId: req.params.identityId, identityId: req.params.identityId,
projectId: req.params.projectId, projectId: req.params.projectId,
role: req.body.role roles: roles || [{ role }]
}); });
return { identityMembership }; return { identityMembership };
} }
@ -64,28 +104,39 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
} }
], ],
params: z.object({ params: z.object({
projectId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.projectId), projectId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.identityId) identityId: z.string().trim().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.identityId)
}), }),
body: z.object({ body: z.object({
roles: z roles: z
.array( .array(
z.union([ z.union([
z.object({ z.object({
role: z.string(), role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(false).default(false) isTemporary: z
.literal(false)
.default(false)
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary)
}), }),
z.object({ z.object({
role: z.string(), role: z.string().describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.role),
isTemporary: z.literal(true), isTemporary: z.literal(true).describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.isTemporary),
temporaryMode: z.nativeEnum(ProjectUserMembershipTemporaryMode), temporaryMode: z
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"), .nativeEnum(ProjectUserMembershipTemporaryMode)
temporaryAccessStartTime: z.string().datetime() .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryMode),
temporaryRange: z
.string()
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryRange),
temporaryAccessStartTime: z
.string()
.datetime()
.describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.temporaryAccessStartTime)
}) })
]) ])
) )
.min(1) .min(1)
.describe(PROJECTS.UPDATE_IDENTITY_MEMBERSHIP.roles) .describe(PROJECT_IDENTITIES.UPDATE_IDENTITY_MEMBERSHIP.roles.description)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -122,8 +173,8 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
} }
], ],
params: z.object({ params: z.object({
projectId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.projectId), projectId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.projectId),
identityId: z.string().trim().describe(PROJECTS.DELETE_IDENTITY_MEMBERSHIP.identityId) identityId: z.string().trim().describe(PROJECT_IDENTITIES.DELETE_IDENTITY_MEMBERSHIP.identityId)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -159,7 +210,7 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
} }
], ],
params: z.object({ params: z.object({
projectId: z.string().trim().describe(PROJECTS.LIST_IDENTITY_MEMBERSHIPS.projectId) projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
}), }),
response: { response: {
200: z.object({ 200: z.object({
@ -200,4 +251,61 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
return { identityMemberships }; return { identityMemberships };
} }
}); });
server.route({
method: "GET",
url: "/:projectId/identity-memberships/:identityId",
config: {
rateLimit: readLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
description: "Return project identity membership",
security: [
{
bearerAuth: []
}
],
params: z.object({
projectId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.projectId),
identityId: z.string().trim().describe(PROJECT_IDENTITIES.GET_IDENTITY_MEMBERSHIP_BY_ID.identityId)
}),
response: {
200: z.object({
identityMembership: z.object({
id: z.string(),
identityId: z.string(),
createdAt: z.date(),
updatedAt: z.date(),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().optional().nullable(),
customRoleName: z.string().optional().nullable(),
customRoleSlug: z.string().optional().nullable(),
isTemporary: z.boolean(),
temporaryMode: z.string().optional().nullable(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional()
})
),
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
})
})
}
},
handler: async (req) => {
const identityMembership = await server.services.identityProject.getProjectIdentityByIdentityId({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.projectId,
identityId: req.params.identityId
});
return { identityMembership };
}
});
}; };

View File

@ -76,6 +76,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
.object({ .object({
id: z.string(), id: z.string(),
name: z.string(), name: z.string(),
slug: z.string(),
organization: z.string(), organization: z.string(),
environments: z environments: z
.object({ .object({

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