Compare commits

...

566 Commits

Author SHA1 Message Date
Maidul Islam
4faa9ced04 Merge pull request #1837 from akhilmhdh/feat/resource-daily-prune
Daily cron for cleaning up expired tokens from db
2024-05-24 12:53:26 -04:00
Maidul Islam
b6ff07b605 revert repete cron 2024-05-24 12:45:19 -04:00
Maidul Islam
1753cd76be update delete access token logic 2024-05-24 12:43:14 -04:00
Sheen Capadngan
f75fc54e10 Merge pull request #1870 from Infisical/doc/updated-gcp-secrets-manager-doc-reminder
doc: added reminder for GCP oauth user permissions
2024-05-25 00:00:15 +08:00
Maidul Islam
966bd77234 Update gcp-secret-manager.mdx 2024-05-24 11:55:29 -04:00
Sheen Capadngan
c782df1176 Merge pull request #1872 from Infisical/fix/resolve-cloudflare-pages-integration
fix: resolved cloudflare pages integration
2024-05-24 23:50:57 +08:00
Sheen Capadngan
e9c5b7f846 Merge pull request #1871 from Infisical/fix/address-json-drop-behavior
fix: address json drag behavior
2024-05-24 21:46:33 +08:00
Sheen Capadngan
008b37c0f4 fix: resolved cloudflare pages integration 2024-05-24 19:45:20 +08:00
Sheen Capadngan
c9b234dbea fix: address json drag behavior 2024-05-24 17:42:38 +08:00
Sheen Capadngan
049df6abec Merge pull request #1869 from Infisical/misc/made-aws-sm-mapping-plaintext-one-to-one
misc: made aws sm mapping one to one plaintext
2024-05-24 02:15:04 +08:00
Sheen Capadngan
8497182a7b misc: finalized addition 2024-05-24 02:11:03 +08:00
Sheen Capadngan
133841c322 doc: added reminder for oauth user permissions 2024-05-24 01:55:59 +08:00
Sheen Capadngan
e7c5645aa9 misc: made aws sm mapping one to one plaintext 2024-05-24 00:35:55 +08:00
Sheen Capadngan
0bc778b9bf Merge pull request #1865 from Infisical/feat/add-one-to-one-support-for-aws-sm
feat: added one to one support for aws secret manager integration
2024-05-23 23:48:47 +08:00
Sheen Capadngan
b0bc41da14 misc: finalized schema type 2024-05-23 22:18:24 +08:00
Daniel Hougaard
a234b686c2 Merge pull request #1867 from Infisical/daniel/better-no-project-found-error
Fix: Better error message on project not found during bot lookup
2024-05-23 15:54:14 +02:00
Daniel Hougaard
6230167794 Update project-bot-fns.ts 2024-05-23 15:48:54 +02:00
Daniel Hougaard
68d1849ba0 Fix: Better error message on project not found during bot lookup 2024-05-23 15:47:08 +02:00
Daniel Hougaard
5c10427eaf Merge pull request #1866 from Infisical/daniel/fix-no-orgs-selectable
Fix: No orgs selectable if a user has been removed from an organization
2024-05-23 15:19:13 +02:00
Daniel Hougaard
290d99e02c Fix: No orgs selectable if a user has been removed from an organization 2024-05-23 15:11:20 +02:00
Sheen Capadngan
b75d601754 misc: documentation changes and minor UI adjustments 2024-05-23 21:03:48 +08:00
Sheen Capadngan
de2a5b4255 feat: added one to one support for aws SM integration 2024-05-23 20:30:55 +08:00
Maidul Islam
663f8abc51 bring back last updated at for service token 2024-05-22 20:44:07 -04:00
Maidul Islam
941a71efaf add index for expire at needed for pruning 2024-05-22 20:38:04 -04:00
Maidul Islam
19bbc2ab26 add secrets index 2024-05-22 19:04:44 -04:00
Maidul Islam
f4de52e714 add index on secret snapshot folders 2024-05-22 18:15:04 -04:00
Maidul Islam
0b87121b67 add index to secret-snapshot-secret for field snapshotId 2024-05-22 17:46:16 -04:00
Maidul Islam
e649667da8 add indexs to secret versions and secret snapshot secrets 2024-05-22 16:52:18 -04:00
Maidul Islam
6af4b3f64c add index for audit logs 2024-05-22 15:48:24 -04:00
Maidul Islam
efcc248486 add tx to find ghost user 2024-05-22 14:54:20 -04:00
Maidul Islam
82eeae6030 track identity 2024-05-22 13:34:44 -04:00
Maidul Islam
440c77965c add logs to track permission inject 2024-05-21 22:35:13 -04:00
Maidul Islam
880289217e revert identity based rate limit 2024-05-21 22:24:53 -04:00
Maidul Islam
d0947f1040 update service tokens 2024-05-21 22:02:01 -04:00
Sheen Capadngan
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
Maidul Islam
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
Sheen Capadngan
c2830a56b6 misc: made digital ocean envs encrypted by default 2024-05-22 01:12:28 +08:00
Sheen Capadngan
b9a9b6b4d9 misc: applied ui/ux changes 2024-05-22 00:06:06 +08:00
Maidul Islam
e7f7f271c8 Merge pull request #1857 from Infisical/misc/added-pino-logger-redaction
misc: added logger redaction
2024-05-21 11:49:36 -04:00
Sheen Capadngan
b26e96c5a2 misc: added logger redaction 2024-05-21 23:04:11 +08:00
Sheen Capadngan
9b404c215b adjustment: ui changes to sync button 2024-05-21 16:04:36 +08:00
Sheen Capadngan
d6dae04959 misc: removed unnecessary notification 2024-05-21 14:01:15 +08:00
Sheen Capadngan
629bd9b7c6 added support for manual syncing of integrations 2024-05-21 13:56:44 +08:00
vmatsiiako
3d4aa0fdc9 Merge pull request #1853 from Infisical/daniel/jenkins-docs
Docs: Jenkins Plugin
2024-05-20 20:13:42 -07:00
Daniel Hougaard
711e30a6be Docs: Plugin installation 2024-05-21 04:58:24 +02:00
Daniel Hougaard
7b1462fdee Docs: Updated Jenkins docs to reflect new plugin 2024-05-21 04:56:26 +02:00
Daniel Hougaard
50915833ff Images 2024-05-21 04:56:15 +02:00
Maidul Islam
44e37fd531 update distinct id for service tokens 2024-05-20 19:50:52 -04:00
Maidul Islam
fa3f957738 count for null actor 2024-05-20 19:26:03 -04:00
Maidul Islam
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
Maidul Islam
e833d9e67c revert secret read limit 2024-05-20 19:01:01 -04:00
Maidul Islam
dc08edb7d2 rate limit based on identity 2024-05-20 18:52:23 -04:00
Maidul Islam
0b78e30848 Delete mongo infisical helm 2024-05-20 16:27:15 -04:00
Sheen Capadngan
9253c69325 misc: finalized ui design of integration sync status 2024-05-21 02:35:23 +08:00
Sheen Capadngan
7d3a62cc4c feat: added integration sync status 2024-05-20 20:56:29 +08:00
Tuan Dang
7e2147f14e Adjust aws iam auth docs 2024-05-19 22:05:38 -07:00
Akhil Mohan
32f39c98a7 Merge pull request #1842 from akhilmhdh/feat/membership-by-id
Endpoints for retreiving membership details
2024-05-19 23:51:30 +05:30
Maidul Islam
ddf6db5a7e small rephrase 2024-05-19 14:19:42 -04:00
BlackMagiq
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
github-actions
d1997f04c0 chore: renamed new migration files to latest timestamp (gh-action) 2024-05-18 14:26:13 +00:00
BlackMagiq
deefaa0961 Merge pull request #1827 from Infisical/k8s-auth
Kubernetes Native Authentication Method
2024-05-18 07:25:52 -07:00
Tuan Dang
a392c9f022 Move k8s migration to front 2024-05-17 22:41:33 -07:00
Maidul Islam
34222b83ee review fixes for k8s auth 2024-05-17 21:44:02 -04:00
Tuan Dang
ef36852a47 Add access token trusted ip support to k8s auth 2024-05-17 15:41:32 -07:00
Tuan Dang
d79fd826a4 Merge remote-tracking branch 'origin' into k8s-auth 2024-05-17 15:39:52 -07:00
Maidul Islam
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
Tuan Dang
32c33eaf6e Patch identity token trusted ips validation for aws/gcp auths 2024-05-17 11:58:08 -07:00
Maidul Islam
702699b4f0 Update faq.mdx 2024-05-17 12:13:11 -04:00
Maidul Islam
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
Akhil Mohan
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
Akhil Mohan
eca0e62764 Merge pull request #1829 from akhilmhdh/feat/revoke-access-token
Revoke access token endpoint
2024-05-16 23:41:38 +05:30
Maidul Islam
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
Maidul Islam
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
=
76c9d642a9 fix: resolved identity check failing due to comma seperated header in ip 2024-05-16 15:46:19 +05:30
=
3ed5dd6109 feat: removed audit log queue and switched to resource clean up queue 2024-05-16 15:46:19 +05:30
=
08e7815ec1 feat: added increment and decrement ops in update knex orm 2024-05-16 15:46:19 +05:30
=
04d961b832 feat: added dal to remove expired token for queue and fixed token validation check missing num uses increment and maxTTL failed check 2024-05-16 15:46:18 +05:30
Cristobal
a6fe233122 Feat: missing documentation for include-imports in export and run command 2024-05-16 11:44:29 +02:00
Maidul Islam
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
Akhil Mohan
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
Daniel Hougaard
75b8b521b3 Update secret-service.ts 2024-05-16 03:31:01 +02:00
Daniel Hougaard
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
Maidul Islam
6b5cafa631 Merge pull request #1834 from Infisical/patch-update-project-identity
patch project identity update
2024-05-15 20:23:09 -04:00
Maidul Islam
4a35623956 remove for of with for await 2024-05-15 20:19:10 -04:00
Maidul Islam
74fe673724 patch project identity update 2024-05-15 20:12:45 -04:00
Daniel Hougaard
2f92719771 Fix: Secret expansion with recursive mode 2024-05-16 00:29:07 +02:00
Akhil Mohan
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
Tuan Dang
ace008f44e Make rejectUnauthorized true if ca cert is passed for k8s auth method 2024-05-14 22:49:37 -07:00
Maidul Islam
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
Maidul Islam
3cd719f6b0 update index secret references button 2024-05-15 09:57:24 +05:30
Maidul Islam
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
Tuan Dang
e4afbe8662 Update k8s auth docs 2024-05-14 20:44:09 -07:00
Tuan Dang
0d89aa8607 Add docs for K8s auth method 2024-05-14 18:02:05 -07:00
Tuan Dang
2b91ec5ae9 Fix merge conflicts 2024-05-14 13:37:39 -07:00
Maidul Islam
c438479246 update prod pipeline names 2024-05-14 16:14:42 -04:00
Justin Patriquin
9828cbbfbe Update secret-versioning.mdx 2024-05-14 16:28:43 -03:00
Tuan Dang
cd910a2fac Update k8s auth impl to be able to test ca, tokenReviewerjwt locally 2024-05-14 11:42:26 -07:00
Maidul Islam
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
Maidul Islam
55f8198a2d Merge pull request #1821 from matthewaerose/patch-1
Fix: Correct typo from 'Halm' to 'Helm'
2024-05-14 11:46:49 -04:00
Maidul Islam
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
github-actions
19edf83dbc chore: renamed new migration files to latest timestamp (gh-action) 2024-05-14 04:16:49 +00:00
snyk-bot
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
Maidul Islam
8dee1f8fc7 Merge pull request #1800 from Infisical/gcp-iam-auth
GCP Native Authentication Method
2024-05-14 00:16:28 -04:00
Maidul Islam
3b23035dfb disable secret scanning 2024-05-13 23:12:36 -04:00
Matthew
0c8ef13d8d Fix: Correct typo from 'Halm' to 'Helm' 2024-05-13 13:38:09 -05:00
Maidul Islam
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
Maidul Islam
638208e9fa update secret scanning text 2024-05-13 13:48:23 -04:00
Maidul Islam
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
Tuan Dang
c45dae4137 Merge remote-tracking branch 'origin' into k8s-auth 2024-05-12 16:16:44 -07:00
vmatsiiako
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
Tuan Dang
c1fb8f47bf Add UntagResource IAM policy requirement for AWS SM integration docs 2024-05-12 08:57:41 -07:00
Tuan Dang
bd57a068d1 Fix merge conflicts 2024-05-12 08:43:29 -07:00
Maidul Islam
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
Maidul Islam
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
Tuan Dang
818b136836 Make app and appId optional in update integration endpoint 2024-05-10 19:17:40 -07:00
Tuan Dang
0cdade6a2d Update AWS SM integration to allow updating tags 2024-05-10 19:07:44 -07:00
Tuan Dang
bcf9b68e2b Update GCP auth method description 2024-05-10 10:28:29 -07:00
Tuan Dang
6aa9fb6ecd Updated docs 2024-05-10 10:24:29 -07:00
Tuan Dang
38e7382d85 Remove GCP audit log space 2024-05-10 10:15:43 -07:00
Tuan Dang
95e12287c2 Minor edits to renaming GCE -> ID Token 2024-05-10 10:14:13 -07:00
Tuan Dang
c6d14a4bea Update 2024-05-10 10:10:51 -07:00
Tuan Dang
0a91586904 Remove service account JSON requirement from GCP Auth 2024-05-10 09:56:35 -07:00
Sheen Capadngan
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
Daniel Hougaard
86aaa486b4 Update secret-folder-service.ts 2024-05-10 17:00:30 +02:00
Sheen Capadngan
9880977098 misc: addressed naming suggestion 2024-05-10 22:52:08 +08:00
Sheen Capadngan
b93aaffe77 adjustment: updated to use project slug 2024-05-10 22:34:16 +08:00
Maidul Islam
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
Sheen Capadngan
0866a90c8e misc: updated documentation for github integration 2024-05-10 16:29:12 +08:00
Sheen Capadngan
3fff272cb3 feat: added snapshot for batch 2024-05-10 15:46:31 +08:00
Sheen Capadngan
2559809eac misc: addressed formatting issues 2024-05-10 14:41:35 +08:00
Sheen Capadngan
f32abbdc25 feat: integrate overview folder rename with new batch endpoint 2024-05-10 14:00:49 +08:00
Sheen Capadngan
a6f750fafb feat: added batch update endpoint for folders 2024-05-10 13:57:00 +08:00
Tuan Dang
610f474ecc Rename migration file 2024-05-09 16:58:39 -07:00
Tuan Dang
03f4a699e6 Improve GCP docs 2024-05-09 16:53:08 -07:00
Tuan Dang
533d49304a Update GCP documentation 2024-05-09 15:35:50 -07:00
Tuan Dang
184b59ad1d Resolve merge conflicts 2024-05-09 12:51:24 -07:00
Maidul Islam
b4a2123fa3 Merge pull request #1812 from Infisical/delete-pg-migrator
Delete PG migrator folder
2024-05-09 15:19:04 -04:00
Tuan Dang
79cacfa89c Delete PG migrator folder 2024-05-09 12:16:13 -07:00
Maidul Islam
44531487d6 Merge pull request #1811 from Infisical/maidul-pacth233
revert schema name for memberships-unique-constraint
2024-05-09 13:46:32 -04:00
Maidul Islam
7c77a4f049 revert schema name 2024-05-09 13:42:23 -04:00
Maidul Islam
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
Tuan Dang
3952ad9a2e Update isEmailVerified field upon invite signups 2024-05-09 09:51:53 -07:00
Akhil Mohan
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
Maidul Islam
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
Maidul Islam
4adc2c4927 update api descriptions 2024-05-09 12:11:46 -04:00
Maidul Islam
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
Maidul Islam
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
BlackMagiq
29ea12f8b1 Merge pull request #1807 from Infisical/mermaid-universal-auth
Add mermaid diagram for Universal Auth
2024-05-08 22:05:12 -07:00
Tuan Dang
b4f1cce587 Add mermaid diagram for universal auth 2024-05-08 22:03:57 -07:00
Maidul Islam
5a92520ca3 Update build-staging-and-deploy-aws.yml 2024-05-09 00:53:42 -04:00
Tuan Dang
42471b22bb Finish AWS Auth mermaid diagram 2024-05-08 21:52:56 -07:00
Vladyslav Matsiiako
79704e9c98 add option to not delete secrets in parameter store 2024-05-08 21:49:09 -07:00
Maidul Islam
1165d11816 Update build-staging-and-deploy-aws.yml 2024-05-09 00:27:21 -04:00
Tuan Dang
15ea96815c Rename AWS IAM auth to AWS Auth with IAM type 2024-05-08 21:22:23 -07:00
Maidul Islam
86d4d88b58 package json lock 2024-05-09 00:19:44 -04:00
Maidul Islam
a12ad91e59 Update build-staging-and-deploy-aws.yml 2024-05-09 00:15:42 -04:00
Tuan Dang
3113e40d0b Add mermaid diagrams to gcp auth docs 2024-05-08 20:09:08 -07:00
Tuan Dang
2406d3d904 Update GCP auth docs 2024-05-08 17:03:26 -07:00
Tuan Dang
e99182c141 Complete adding GCP GCE auth 2024-05-08 15:51:09 -07:00
Sheen Capadngan
522dd0836e feat: added validation for folder name duplicates 2024-05-08 23:25:33 +08:00
Sheen Capadngan
e461787c78 feat: added support for renaming folders in the overview page 2024-05-08 23:24:33 +08:00
Sheen Capadngan
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
Sheen Capadngan
d0036a5656 Merge remote-tracking branch 'origin/main' into misc/improved-select-path-component-ux-1 2024-05-08 17:28:31 +08:00
Sheen Capadngan
e7f19421ef misc: resolved auto-popup of suggestions 2024-05-08 17:24:06 +08:00
Daniel Hougaard
e18d830fe8 Merge pull request #1801 from Infisical/daniel/k8-recursive
Feat: Recursive support for K8 operaetor
2024-05-08 00:44:07 +02:00
Daniel Hougaard
be2fc4fec4 Update Chart.yaml 2024-05-08 00:42:38 +02:00
Daniel Hougaard
829dbb9970 Update values.yaml 2024-05-08 00:41:53 +02:00
Daniel Hougaard
0b012c5dfb Chore: Helm 2024-05-08 00:23:50 +02:00
Daniel Hougaard
b0421ccad0 Docs: Add recursive to example 2024-05-08 00:21:08 +02:00
Daniel Hougaard
6b83326d00 Feat: Recursive mode support 2024-05-08 00:18:53 +02:00
Daniel Hougaard
1f6abc7f27 Feat: Recursive mode and fix error formatting 2024-05-08 00:18:40 +02:00
Daniel Hougaard
4a02520147 Update sample 2024-05-08 00:18:26 +02:00
Daniel Hougaard
14f38eb961 Feat: Recursive mode types 2024-05-08 00:16:51 +02:00
Tuan Dang
ac469dbe4f Update GCP auth docs 2024-05-07 14:58:14 -07:00
Tuan Dang
d98430fe07 Merge remote-tracking branch 'origin' into gcp-iam-auth 2024-05-07 14:29:08 -07:00
Tuan Dang
82bafd02bb Fix merge conflicts 2024-05-07 14:28:41 -07:00
BlackMagiq
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
github-actions
cebd22da8e chore: renamed new migration files to latest timestamp (gh-action) 2024-05-07 21:06:55 +00:00
BlackMagiq
d200405c6e Merge pull request #1778 from Infisical/aws-iam-auth
AWS IAM Authentication Method
2024-05-07 14:06:30 -07:00
Maidul Islam
3a1cdc4f44 Delete backend/src/db/migrations/20240507162149_test.ts 2024-05-07 15:41:09 -04:00
Tuan Dang
1d40d9e448 Begin frontend for GCP IAM Auth 2024-05-07 12:40:19 -07:00
Tuan Dang
e96ca8d355 Draft GCP IAM Auth docs 2024-05-07 12:15:18 -07:00
Maidul Islam
2929d94f0a Merge pull request #1797 from Infisical/maidul98-patch-10
test
2024-05-07 14:28:03 -04:00
Maidul Islam
0383ae9e8b Create 20240507162149_test.ts 2024-05-07 14:27:44 -04:00
Maidul Islam
00faa6257f Delete backend/src/db/migrations/20240507162149_test.ts 2024-05-07 14:27:33 -04:00
Maidul Islam
183bde55ca correctly fetch merged by user login 2024-05-07 14:26:56 -04:00
Maidul Islam
c96fc1f798 Merge pull request #1795 from Infisical/maidul98-patch-9
test
2024-05-07 14:09:49 -04:00
Maidul Islam
80f7ff1ea8 Create 20240507162149_test.ts 2024-05-07 14:09:38 -04:00
Maidul Islam
c87620109b Rename 20240507162141_access to 20240507162141_access.ts 2024-05-07 13:58:10 -04:00
Maidul Islam
02c158b4ed Delete backend/src/db/migrations/20240507162180_test 2024-05-07 13:47:25 -04:00
Tuan Dang
588f4bdb09 Fix merge conflict 2024-05-07 10:45:07 -07:00
Tuan Dang
4d74d264dd Finish preliminary backend endpoints for GCP IAM Auth method 2024-05-07 10:42:39 -07:00
Maidul Islam
ddfa64eb33 Merge pull request #1793 from Infisical/maidul98-patch-8
testing-ignore
2024-05-07 13:27:19 -04:00
Maidul Islam
7fdaa1543a Create 20240507162180_test 2024-05-07 13:26:52 -04:00
Maidul Islam
c8433f39ed Delete backend/src/db/migrations/20240507162180_test 2024-05-07 13:26:42 -04:00
Maidul Islam
ba238a8f3b get pr details by pr number 2024-05-07 13:25:35 -04:00
Sheen Capadngan
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
Maidul Islam
a1585db76a Merge pull request #1791 from Infisical/maidul98-patch-7
Create 20240507162180_test
2024-05-07 13:16:59 -04:00
Maidul Islam
f5f0bf3c83 Create 20240507162180_test 2024-05-07 13:16:42 -04:00
Maidul Islam
3638645b8a get closed by user 2024-05-07 13:15:15 -04:00
Sheen Capadngan
f957b9d970 misc: migrated to react-state 2024-05-08 01:03:41 +08:00
Maidul Islam
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
Akhil Mohan
3ce91b8a20 doc: fixed typo in api privilege documentation 2024-05-07 22:25:36 +05:30
Sheen Capadngan
8bab14a672 misc: added handling of input focus 2024-05-08 00:43:14 +08:00
Maidul Islam
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
Maidul Islam
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
github-actions
306cf8733e chore: renamed new migration files to latest timestamp (gh-action) 2024-05-07 16:21:40 +00:00
Maidul Islam
6e829516db Merge pull request #1652 from Infisical/daniel/request-access
Feat: Request Access
2024-05-07 12:21:17 -04:00
Sheen Capadngan
c08fcc6f5e adjustment: finalized notification text 2024-05-08 00:12:55 +08:00
Daniel Hougaard
9a585ad930 Fix: Rebase error 2024-05-07 17:30:36 +02:00
Daniel Hougaard
95c1fff7d3 Chore: Remove unused files 2024-05-07 17:30:36 +02:00
Daniel Hougaard
9c2591f3a6 Fix: Moved Divider to v2 2024-05-07 17:30:36 +02:00
Daniel Hougaard
a579598b6d Chore: Moved verifyApprovers 2024-05-07 17:30:36 +02:00
Daniel Hougaard
af0d31db2c Fix: Improved migrations 2024-05-07 17:30:36 +02:00
Daniel Hougaard
fb6c4acf31 Delete access-approval-request-secret-dal.ts 2024-05-07 17:30:36 +02:00
Daniel Hougaard
551ca0fa8c Migration improvements 2024-05-07 17:30:36 +02:00
Daniel Hougaard
4a0ccbe69e Fixed bugs 2024-05-07 17:30:36 +02:00
Daniel Hougaard
f5a463ddea Update SecretApprovalPage.tsx 2024-05-07 17:30:36 +02:00
Daniel Hougaard
ce1ad6f32e Fix: Rebase errors 2024-05-07 17:30:36 +02:00
Daniel Hougaard
56c8b4f5e5 Removed unnessecary types 2024-05-07 17:30:36 +02:00
Daniel Hougaard
29b26e3158 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
Daniel Hougaard
6e209bf099 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
Daniel Hougaard
949d210263 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
Vladyslav Matsiiako
1a2d8e96f3 style changes 2024-05-07 17:30:36 +02:00
Daniel Hougaard
9198eb5fba Update licence-fns.ts 2024-05-07 17:30:36 +02:00
Daniel Hougaard
0580f37c5e Update generate-schema-types.ts 2024-05-07 17:30:36 +02:00
Daniel Hougaard
e53d40f0e5 Update SecretApprovalPage.tsx 2024-05-07 17:30:36 +02:00
Daniel Hougaard
801c0c5ada Fix: Remove redundant code 2024-05-07 17:30:36 +02:00
Daniel Hougaard
7b8af89bee Fix: Validate approvers access 2024-05-07 17:30:36 +02:00
Daniel Hougaard
ef7f5c9eac Feat: Request access (new routes) 2024-05-07 17:30:36 +02:00
Daniel Hougaard
db0b4a5ad1 Feat: Request access 2024-05-07 17:30:36 +02:00
Daniel Hougaard
cb505d1525 Draft 2024-05-07 17:30:36 +02:00
Daniel Hougaard
c66476e2b4 Fix: Multiple approvers acceptance bug 2024-05-07 17:30:36 +02:00
Daniel Hougaard
60a06edd9b Style: Fix styling 2024-05-07 17:30:36 +02:00
Daniel Hougaard
e8e1d46f0e Capitalization 2024-05-07 17:30:36 +02:00
Daniel Hougaard
038fe3508c Removed unnessecary types 2024-05-07 17:30:36 +02:00
Daniel Hougaard
7d1dff9e5a Fix: Security vulnurbility making it possible to spoof env & secret path requested. 2024-05-07 17:30:36 +02:00
Daniel Hougaard
5117f5d3c1 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
Daniel Hougaard
350dd97b98 Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
Daniel Hougaard
121902e51f Update AccessApprovalRequest.tsx 2024-05-07 17:30:36 +02:00
Vladyslav Matsiiako
923bf02046 style changes 2024-05-07 17:30:36 +02:00
Daniel Hougaard
27447ddc88 Update licence-fns.ts 2024-05-07 17:30:36 +02:00
Daniel Hougaard
a3b4b650d1 Removed unused parameter 2024-05-07 17:30:36 +02:00
Daniel Hougaard
3f0f45e853 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:36 +02:00
Daniel Hougaard
3bb50b235d Update generate-schema-types.ts 2024-05-07 17:30:36 +02:00
Daniel Hougaard
1afd120e8e Feat: Request access 2024-05-07 17:30:36 +02:00
Daniel Hougaard
ab3593af37 Feat: Request access 2024-05-07 17:30:36 +02:00
Daniel Hougaard
2c2afbea7a Fix: Move to project slug 2024-05-07 17:30:36 +02:00
Daniel Hougaard
4eabbb3ac5 Fix: Added support for request access 2024-05-07 17:30:36 +02:00
Daniel Hougaard
1ccd74e1a5 Fix: Remove redundant code 2024-05-07 17:30:35 +02:00
Daniel Hougaard
812cced9d5 Feat: Request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
cd6be68461 Fix: Validate approvers access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
5c69bbf515 Feat: Request access (new routes) 2024-05-07 17:30:35 +02:00
Daniel Hougaard
448f89fd1c Feat: Request Access (migrations) 2024-05-07 17:30:35 +02:00
Daniel Hougaard
3331699f56 Feat: Request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
810f670e64 Feat: Request Access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
5894df4370 Draft 2024-05-07 17:30:35 +02:00
Daniel Hougaard
2aacd54116 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
73d9fcc0de Draft 2024-05-07 17:30:35 +02:00
Tuan Dang
7ac3bb20df Update instance recognition of offline license 2024-05-07 17:30:35 +02:00
Daniel Hougaard
d659b5a624 Fix: Duplicate access request check 2024-05-07 17:30:35 +02:00
Daniel Hougaard
0bbdf2a8f4 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
a8eba9cfbf Fix: Moved from email to username 2024-05-07 17:30:35 +02:00
Daniel Hougaard
a3d7c5f599 Cleanup 2024-05-07 17:30:35 +02:00
Daniel Hougaard
c325674da0 Fix: Move standalone components to individual files 2024-05-07 17:30:35 +02:00
Daniel Hougaard
3637152a6b Chore: Remove unused files 2024-05-07 17:30:35 +02:00
Daniel Hougaard
8ed3c0cd68 Fix: Use username instead of email 2024-05-07 17:30:35 +02:00
Daniel Hougaard
cdd836d58f Fix: Columns 2024-05-07 17:30:35 +02:00
Daniel Hougaard
3d3b1eb21a Fix: Use username instead of email 2024-05-07 17:30:35 +02:00
Daniel Hougaard
6aab28c4c7 Feat: Badge component 2024-05-07 17:30:35 +02:00
Daniel Hougaard
f038b28c1c Fix: Moved Divider to v2 2024-05-07 17:30:35 +02:00
Daniel Hougaard
24a286e898 Update index.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
0c1103e778 Fix: Pick 2024-05-07 17:30:35 +02:00
Daniel Hougaard
2c1eecaf85 Chore: Moved verifyApprovers 2024-05-07 17:30:35 +02:00
Daniel Hougaard
5884565de7 Fix: Make verifyApprovers independent on memberships 2024-05-07 17:30:35 +02:00
Daniel Hougaard
dd43268506 Fix: Made API endpoints more REST compliant 2024-05-07 17:30:35 +02:00
Daniel Hougaard
9d362b8597 Chore: Cleaned up models 2024-05-07 17:30:35 +02:00
Daniel Hougaard
972ecc3e92 Fix: Improved migrations 2024-05-07 17:30:35 +02:00
Daniel Hougaard
dc3014409f Delete access-approval-request-secret-dal.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
4e449f62c0 Fix: Don't display requested by when user has no access to read workspace members 2024-05-07 17:30:35 +02:00
Daniel Hougaard
c911a7cd81 Fix: Don't display requested by when user has no access to read workspace members 2024-05-07 17:30:35 +02:00
Daniel Hougaard
44370d49e3 Fix: Add tooltip for clarity and fix wording 2024-05-07 17:30:35 +02:00
Daniel Hougaard
c7d2dfd351 Fix: Requesting approvals on previously rejected resources 2024-05-07 17:30:35 +02:00
Daniel Hougaard
1785548a40 Fix: Sort by createdAt 2024-05-07 17:30:35 +02:00
Daniel Hougaard
2baf9e0739 Migration improvements 2024-05-07 17:30:35 +02:00
Daniel Hougaard
01e7ed23ba Fixed bugs 2024-05-07 17:30:35 +02:00
Daniel Hougaard
1f789110e3 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
c874c943c1 Fix: Rebase errors 2024-05-07 17:30:35 +02:00
Daniel Hougaard
dab69dcb51 Removed unnessecary types 2024-05-07 17:30:35 +02:00
Daniel Hougaard
8e82bfae86 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
bc810ea567 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
22470376d9 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
Vladyslav Matsiiako
bb9503471f style changes 2024-05-07 17:30:35 +02:00
Daniel Hougaard
a687b1d0db Update licence-fns.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
0aa77f90c8 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
5a04371fb0 Update generate-schema-types.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
70c06c91c8 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
926d324ae3 Fix: Added support for request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
e48377dea9 Fix: Remove redundant code 2024-05-07 17:30:35 +02:00
Daniel Hougaard
5e1484bd05 Fix: Validate approvers access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
6d9de752d7 Feat: Request access (new routes) 2024-05-07 17:30:35 +02:00
Daniel Hougaard
f9a9b1222e Feat: Request Access (migrations) 2024-05-07 17:30:35 +02:00
Daniel Hougaard
4326ce970a Feat: Request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
7a3a9ca9ea Draft 2024-05-07 17:30:35 +02:00
Daniel Hougaard
32a110e0ca Fix: Multiple approvers acceptance bug 2024-05-07 17:30:35 +02:00
Daniel Hougaard
da5278f6bf Fix: Rename change -> secret 2024-05-07 17:30:35 +02:00
Daniel Hougaard
7e765681cb Style: Fix styling 2024-05-07 17:30:35 +02:00
Daniel Hougaard
0990ce1f92 Capitalization 2024-05-07 17:30:35 +02:00
Daniel Hougaard
2369ff6813 Removed unnessecary types 2024-05-07 17:30:35 +02:00
Daniel Hougaard
478520f090 Remove unnessecary types and projectMembershipid 2024-05-07 17:30:35 +02:00
Daniel Hougaard
54313f9c08 Renaming 2024-05-07 17:30:35 +02:00
Daniel Hougaard
cb8763bc9c Update smtp-service.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
c5d11eee7f Feat: Find users by project membership ID's 2024-05-07 17:30:35 +02:00
Daniel Hougaard
8e1d19c041 Feat: access request emails 2024-05-07 17:30:35 +02:00
Daniel Hougaard
608c7a4dee Update index.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
c7b60bcf0e Update access-approval-request-types.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
6ae62675be Feat: Send emails for access requests 2024-05-07 17:30:35 +02:00
Daniel Hougaard
fb2ab200b9 Feat: Request access, extract permission details 2024-05-07 17:30:35 +02:00
Daniel Hougaard
f1428d72c2 Fix: Security vulnurbility making it possible to spoof env & secret path requested. 2024-05-07 17:30:35 +02:00
Daniel Hougaard
4cb51805f0 Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
8c40918cef Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
3a002b921a Update AccessApprovalRequest.tsx 2024-05-07 17:30:35 +02:00
Vladyslav Matsiiako
299653528c style changes 2024-05-07 17:30:35 +02:00
Daniel Hougaard
8c256bd9c8 Fix: Status filtering & query invalidation 2024-05-07 17:30:35 +02:00
Daniel Hougaard
f8e0e01bb8 Fix: Access request query invalidation 2024-05-07 17:30:35 +02:00
Vladyslav Matsiiako
b59413ded0 fix privilegeId issue 2024-05-07 17:30:35 +02:00
Daniel Hougaard
15c747e8e8 Fix: Request access permissions 2024-05-07 17:30:35 +02:00
Daniel Hougaard
073a9ee6a4 Update licence-fns.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
d371c568f1 Add count 2024-05-07 17:30:35 +02:00
Daniel Hougaard
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
Daniel Hougaard
890c8b89be Removed unused parameter 2024-05-07 17:30:35 +02:00
Daniel Hougaard
6f4b62cfbb Removed logs 2024-05-07 17:30:35 +02:00
Daniel Hougaard
076c70f6ff Removed logs 2024-05-07 17:30:35 +02:00
Daniel Hougaard
aedc1f2441 Update SpecificPrivilegeSection.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
352d363bd4 Update generate-schema-types.ts 2024-05-07 17:30:35 +02:00
Daniel Hougaard
ac92a916b4 Update SecretApprovalPage.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
17587ff1b8 Fix: Minor fixes 2024-05-07 17:30:35 +02:00
Daniel Hougaard
7f1c8d9ff6 Create index.tsx 2024-05-07 17:30:35 +02:00
Daniel Hougaard
ac24c0f760 Feat: Request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
0e95c1bcee Feat: Request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
447630135b Feat: Request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
ddd6adf804 Fix: Move to project slug 2024-05-07 17:30:35 +02:00
Daniel Hougaard
a4b6d2650a Fix: Move to project slug 2024-05-07 17:30:35 +02:00
Daniel Hougaard
2f5d6b11da Fix: Move to project slug 2024-05-07 17:30:35 +02:00
Daniel Hougaard
d380b7f788 Fix: Added support for request access 2024-05-07 17:30:35 +02:00
Daniel Hougaard
7aee4fdfcd Feat: Request access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
83bd3a0bf4 Update index.tsx 2024-05-07 17:30:27 +02:00
Daniel Hougaard
1f68730aa3 Fix: Improve disabled Select 2024-05-07 17:30:27 +02:00
Daniel Hougaard
7fd1d72985 Fix: Access Request setup 2024-05-07 17:30:27 +02:00
Daniel Hougaard
b298eec9db Fix: Danger color not working on disabled buttons 2024-05-07 17:30:27 +02:00
Daniel Hougaard
696479a2ef Fix: Remove redundant code 2024-05-07 17:30:27 +02:00
Daniel Hougaard
ad6e2aeb9e Feat: Request Access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
ad405109a0 Feat: Request access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
992a82015a Feat: Request access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
317956a038 Fix: Types mismatch 2024-05-07 17:30:27 +02:00
Daniel Hougaard
5255c4075a Fix: Validate approvers access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
eca36f1993 Feat: Request access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
7e29a6a656 Fix: Access Approval Policy DAL bugs 2024-05-07 17:30:27 +02:00
Daniel Hougaard
f458e34c37 Feat: Request access (new routes) 2024-05-07 17:30:27 +02:00
Daniel Hougaard
99f5ed1f4b Fix: Move to project slug 2024-05-07 17:30:27 +02:00
Daniel Hougaard
f981c59b5c Feat: Request access (models) 2024-05-07 17:30:27 +02:00
Daniel Hougaard
a528d011c0 Feat: Request Access (migrations) 2024-05-07 17:30:27 +02:00
Daniel Hougaard
d337118803 Feat: Request access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
68a11db1c6 Feat: Request access 2024-05-07 17:30:27 +02:00
Daniel Hougaard
91bf6a6dad Fix: Remove logs 2024-05-07 17:30:13 +02:00
Daniel Hougaard
12c655a152 Feat: Request Access 2024-05-07 17:30:13 +02:00
Daniel Hougaard
1d2f10178f Draft 2024-05-07 17:30:13 +02:00
Tuan Dang
c5cd5047d7 Update trusted email migration file with backfill 2024-05-07 07:59:37 -07:00
Sheen Capadngan
06c103c10a misc: added handling for no changes made 2024-05-07 22:19:20 +08:00
Sheen Capadngan
b6a73459a8 misc: addressed rbac for bulk delete in overview 2024-05-07 16:37:10 +08:00
Sheen Capadngan
536f51f6ba misc: added descriptive error message 2024-05-07 15:21:17 +08:00
Sheen Capadngan
a9b72b2da3 feat: added handling of folder/secret deletion 2024-05-07 15:16:37 +08:00
Tuan Dang
e3c80309c3 Move aws auth migration file to front 2024-05-06 23:03:45 -07:00
Tuan Dang
ec3d6c20e8 Merge remote-tracking branch 'origin' into aws-iam-auth 2024-05-06 22:58:47 -07:00
Tuan Dang
5d7c0f30c8 Fix typo universal auth 2024-05-06 22:58:35 -07:00
Sheen Capadngan
a3552d00d1 feat: add multi-select in secret overview 2024-05-07 13:52:42 +08:00
Maidul Islam
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
github-actions
308e605b6c chore: renamed new migration files to latest timestamp (gh-action) 2024-05-07 03:28:10 +00:00
Maidul Islam
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
Tuan Dang
0357e7c80e Put email-confirmation migration into trusted-saml-ldap-emails file 2024-05-06 19:58:58 -07:00
Tuan Dang
ba1b223655 Patch migration file hasTable ref 2024-05-06 19:44:43 -07:00
Tuan Dang
0b089e6fa6 Update aws iam auth fns filename 2024-05-06 18:35:34 -07:00
Tuan Dang
3b88a2759b Patch unsynchronized username/email for saml/scim 2024-05-06 18:27:36 -07:00
Maidul Islam
42383d5643 Merge pull request #1782 from akhilmhdh/feat/privilege-identity-api-change
Privilege identity api change
2024-05-06 15:01:02 -04:00
Akhil Mohan
d198ba1a79 feat: refactored the map unpack to a function 2024-05-06 23:27:51 +05:30
Maidul Islam
b3579cb271 rephrase text for permission schema zod 2024-05-06 13:44:39 -04:00
Tuan Dang
30ccb78c81 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-05-06 09:33:36 -07:00
Maidul Islam
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
Akhil Mohan
79e9b1b2ae feat: debounced main page search and rolled back to old input component 2024-05-06 20:43:23 +05:30
Akhil Mohan
86fd4d5fba feat: added a fixed sorted order to avoid jumps 2024-05-06 14:26:46 +05:30
Akhil Mohan
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
Akhil Mohan
61a0997adc fix(ui): secret path input showing / for a valid value that comes delayed 2024-05-06 14:00:32 +05:30
Tuan Dang
c276c44c08 Finish preliminary backend endpoints / db structure for k8s auth 2024-05-05 19:14:49 -07:00
Maidul Islam
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
Maidul Islam
ab79342743 rename to expandSecretReferences 2024-05-04 22:05:57 -04:00
Maidul Islam
1957531ac4 Update docker-compose.mdx 2024-05-04 21:01:19 -04:00
Sheen Capadngan
61ae0e2fc7 feat: added secret expand option in secrets get API 2024-05-04 14:42:22 +08:00
Tuan Dang
cbf8e041e9 Finish docs for AWS IAM Auth, update ARN regex 2024-05-03 17:20:44 -07:00
Tuan Dang
87b571d6ff Merge remote-tracking branch 'origin' 2024-05-03 09:52:48 -07:00
Tuan Dang
1e6af8ad8f Update email in beginEmailSignupProcess 2024-05-03 09:49:10 -07:00
Maidul Islam
a771ddf859 Merge pull request #1721 from akhilmhdh/feat/audit-log-stream
Audit log streams
2024-05-03 12:48:55 -04:00
Akhil Mohan
c4cd6909bb docs: improved datadog log stream doc 2024-05-03 20:09:57 +05:30
Akhil Mohan
49642480d3 fix: resolved headers not working in queue 2024-05-03 20:06:24 +05:30
Akhil Mohan
b667dccc0d docs: improved text audit log stream 2024-05-03 18:19:37 +05:30
Akhil Mohan
fdda247120 feat: added a catch and override error message for ping check 2024-05-03 18:18:57 +05:30
Maidul Islam
ee8a88d062 Update docker-swarm.mdx 2024-05-03 08:44:43 -04:00
Maidul Islam
33349839cd Merge pull request #1780 from Infisical/maidul-1221
Make migration notice visible
2024-05-03 08:24:07 -04:00
Maidul Islam
8f3883c7d4 update date 2024-05-03 08:20:20 -04:00
Maidul Islam
38cfb7fd41 patch migration notice bug 2024-05-03 08:19:27 -04:00
Akhil Mohan
a331eb8dc4 docs: updated docs with header inputs for audit log stream and datadog section added 2024-05-03 17:43:58 +05:30
Akhil Mohan
2dcb409d3b feat: changed from token to headers for audit log streams api 2024-05-03 17:43:14 +05:30
Akhil Mohan
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
Sheen Capadngan
52189111d7 adjustment: added slug to response 2024-05-03 18:03:21 +08:00
Tuan Dang
5c4d35e30a Merge remote-tracking branch 'origin' into aws-iam-auth 2024-05-02 22:53:14 -07:00
Tuan Dang
d5c74d558a Start docs for AWS IAM auth 2024-05-02 22:52:37 -07:00
Tuan Dang
9c002ad645 Finish preliminary AWS IAM Auth method 2024-05-02 22:42:02 -07:00
Akhil Mohan
f369761920 feat: rollback license-fns 2024-05-03 00:31:40 +05:30
Akhil Mohan
8eb22630b6 docs: added docs for audit log stream 2024-05-03 00:23:59 +05:30
Akhil Mohan
d650fd68c0 feat: improved api desc, added ping check before accepting stream 2024-05-03 00:23:59 +05:30
Maidul Islam
387c899193 add line breaks for readiblity 2024-05-03 00:23:59 +05:30
Maidul Islam
37882e6344 rephrase ui texts 2024-05-03 00:23:59 +05:30
Akhil Mohan
68a1aa6f46 feat: switched audit log stream from project level to org level 2024-05-03 00:23:59 +05:30
Akhil Mohan
fa18ca41ac feat(server): fixed if projectid is missing 2024-05-03 00:23:59 +05:30
Akhil Mohan
8485fdc1cd feat(ui): audit log page completed 2024-05-03 00:23:59 +05:30
Akhil Mohan
49ae2386c0 feat(ui): audit log api hooks 2024-05-03 00:23:59 +05:30
Akhil Mohan
f2b1f3f0e7 feat(server): audit log streams services and api routes 2024-05-03 00:23:58 +05:30
Akhil Mohan
69aa20e35c feat(server): audit log streams db schema changes 2024-05-03 00:23:58 +05:30
Maidul Islam
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
Akhil Mohan
e13f7a7486 doc: resolved missing bulk secret api operations 2024-05-02 21:40:52 +05:30
Maidul Islam
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
Maidul Islam
5dd144b97b update self host nav items 2024-05-01 22:06:26 -04:00
Maidul Islam
b1b430e003 add more steps and FAQ for docker swarm 2024-05-01 21:57:48 -04:00
Maidul Islam
fb09980413 Create .env.example 2024-05-01 21:42:45 -04:00
Maidul Islam
3b36cb8b3d rename_ha-proxy 2024-05-01 21:16:27 -04:00
Maidul Islam
be6a98d0bb update docker swarm stack 2024-05-01 19:24:05 -04:00
BlackMagiq
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
Tuan Dang
5c71116be6 Add deprecation notice banner to service token section 2024-05-01 09:17:07 -07:00
Vladyslav Matsiiako
07cc4fd1ab add company folder 2024-04-30 23:24:03 -07:00
Tuan Dang
ea4ef7f7ef Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-30 21:37:48 -07:00
Tuan Dang
0482424a1c Make merge user step automatic after email verification 2024-04-30 21:33:27 -07:00
Maidul Islam
74bdbc0724 Update mint.json 2024-04-30 23:30:58 -04:00
Maidul Islam
a0d5c67456 Merge pull request #1770 from Infisical/docker-swarm
add docker swarm guide
2024-04-30 22:10:56 -04:00
Maidul Islam
db4f4d8f28 add docker swarm guide 2024-04-30 22:10:11 -04:00
Maidul Islam
d6f6f51d16 Update stack.yaml 2024-04-30 21:45:00 -04:00
BlackMagiq
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
Tuan Dang
46912c4c3c Update docs 2024-04-30 16:44:06 -07:00
Tuan Dang
6636377cb5 Merge remote-tracking branch 'origin' 2024-04-30 15:50:08 -07:00
Tuan Dang
26320ddce4 Temp increase secretsLimit 2024-04-30 15:49:42 -07:00
Tuan Dang
f5964040d7 Update CLI usage page 2024-04-30 15:47:24 -07:00
Sheen Capadngan
dcaa7f1fce fix: address functional issues with secret input 2024-05-01 03:03:40 +08:00
Maidul Islam
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
Sheen Capadngan
74f866715f fix: address infisical secret input ux issue with enter and arrow keys 2024-05-01 02:10:54 +08:00
Tuan Dang
667f696d26 Start updating docs 2024-04-30 08:59:02 -07:00
vmatsiiako
5f3938c33d Update overview.mdx 2024-04-29 23:20:48 -07:00
Maidul Islam
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
Tuan Dang
17fa72be13 Merge remote-tracking branch 'origin' into fix-integration-sync-import-priority 2024-04-29 18:32:46 -07:00
Tuan Dang
bf3e93460a Update priority of integration sync secrets for imports to prioritize direct layer first 2024-04-29 18:16:52 -07:00
Maidul Islam
306709cde6 Merge pull request #1763 from Infisical/aws-sm-ps-check
Update implementation for AWS SM/PS integration KMS ID option
2024-04-29 20:44:54 -04:00
Maidul Islam
c41518c822 Merge pull request #1731 from akhilmhdh/dynamic-secret/aws-iam
Dynamic secret AWS IAM
2024-04-29 20:39:38 -04:00
Maidul Islam
f0f2905789 update iam dynamic secret docs 2024-04-29 20:34:36 -04:00
Tuan Dang
212a7b49f0 Add kms encrypt/decrypt to AWS SM docs 2024-04-29 16:56:27 -07:00
Tuan Dang
22e3fcb43c Remove try-catch block 2024-04-29 16:53:52 -07:00
Tuan Dang
93b65a1534 Update impl for AWS SM/PS integrations with KMS 2024-04-29 16:49:53 -07:00
Maidul Islam
039882e78b Merge pull request #1755 from gzuidhof/patch-1
Fix typo in docs
2024-04-29 19:21:36 -04:00
Maidul Islam
f0f51089fe Merge pull request #1756 from alvaroReina/alvaro/add-image-pull-secrets-support
added imagePullSecrets support to infisical-standalone-postgres chart
2024-04-29 19:12:09 -04:00
Maidul Islam
447141ab1f update chart version 2024-04-29 19:11:24 -04:00
Maidul Islam
d2ba436338 move imagePullSecrets under image 2024-04-29 19:07:26 -04:00
Maidul Islam
ad0d281629 Merge pull request #1759 from akhilmhdh/fix/index-audit-log
fix(server): added index for audit log to resolve high latency or timeout
2024-04-29 18:46:54 -04:00
Tuan Dang
ce2a9c8640 Rename migration file 2024-04-29 11:57:30 -07:00
Tuan Dang
ac97f273e3 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-29 11:55:53 -07:00
Tuan Dang
69c50af14e Move trust saml/ldap emails to server config 2024-04-29 11:53:28 -07:00
Maidul Islam
c8638479a8 Delete backend/src/db/migrations/20240424235843_user-search-filter-1.ts 2024-04-29 14:28:32 -04:00
Maidul Islam
8aa75484f3 Merge pull request #1760 from Infisical/maidul98-patch-6
Create 20240424235843_user-search-filter-1.ts
2024-04-29 14:25:09 -04:00
Maidul Islam
66d70f5a25 Create 20240424235843_user-search-filter-1.ts 2024-04-29 14:24:54 -04:00
Akhil Mohan
8e7cf5f9ac fix(server): added index for audit log to resolve high latency or timeout caused 2024-04-29 22:42:35 +05:30
Akhil Mohan
f9f79cb69e Merge pull request #1758 from Infisical/fix/secret-reference-auto-complete-spacing
fix: resolved truncation issue in secret reference auto-complete
2024-04-29 22:41:16 +05:30
Sheen Capadngan
4235be4be9 fix: resolved truncation issue in secret reference auto-complete 2024-04-30 01:01:59 +08:00
Alvaro Reina
5c3f2e66fd added imagePullSecrets support 2024-04-29 14:03:04 +02:00
Guido Zuidhof
a37b3ccede Fix typo 2024-04-29 13:22:56 +02:00
vmatsiiako
d64eb4b901 Merge pull request #1754 from Infisical/parameter-store-kms-key
added kms key selector for parameter store
2024-04-28 23:06:09 -07:00
Tuan Dang
519403023a Pick 2024-04-28 22:04:22 -07:00
Tuan Dang
b2a976f3d4 Update groups CRUD SCIM to use orgMembershipId 2024-04-28 21:58:24 -07:00
Tuan Dang
6e882aa46e Added kMS permissions to docs for parameter store 2024-04-28 20:53:03 -07:00
Vladyslav Matsiiako
bf4db0a9ff made paths scrollable 2024-04-28 19:44:39 -07:00
Vladyslav Matsiiako
3a3e3a7afc updated integrations page 2024-04-28 19:36:14 -07:00
Tuan Dang
a7af3a48d9 Continue moving SCIM userId refs to orgMembershipId 2024-04-28 19:09:12 -07:00
Maidul Islam
cdba78b51d add docker swarm 2024-04-28 20:16:15 -04:00
Vladyslav Matsiiako
0c324e804c added kms key delector for parameter store 2024-04-28 15:12:50 -07:00
vmatsiiako
47aca3f3e2 Update overview.mdx 2024-04-27 19:05:24 -07:00
Tuan Dang
80da2a19aa Add TRUST_SAML_EMAILS and TRUST_LDAP_EMAILS opts 2024-04-26 22:30:07 -07:00
Tuan Dang
858a35812a Finish preliminary email validation, merge user flow w saml/ldap 2024-04-26 20:19:43 -07:00
Maidul Islam
31ef1a2183 Delete backend/src/db/migrations/20240426171026_test.ts 2024-04-26 20:33:13 -04:00
Maidul Islam
66a6f9de71 Merge pull request #1753 from Infisical/maidul98-patch-5
Create 20240426171026_test.ts
2024-04-26 17:52:11 -04:00
Maidul Islam
6333eccc4a Create 20240426171026_test.ts 2024-04-26 17:52:02 -04:00
Maidul Islam
0af2b113df Delete backend/src/db/migrations/20240426171026_test.ts 2024-04-26 17:51:52 -04:00
Maidul Islam
63a7941047 Update update-be-new-migration-latest-timestamp.yml 2024-04-26 17:51:20 -04:00
Maidul Islam
edeac08cb5 Merge pull request #1752 from Infisical/maidul98-patch-4
Update 20240426171026_test.ts
2024-04-26 14:54:26 -04:00
Maidul Islam
019b0ae09a Update 20240426171026_test.ts 2024-04-26 14:54:15 -04:00
Maidul Islam
1d00bb0a64 Update update-be-new-migration-latest-timestamp.yml 2024-04-26 14:52:47 -04:00
Maidul Islam
d96f1320ed Merge pull request #1751 from Infisical/revert-1750-revert-1749-revert-1748-revert-1747-revert-1746-revert-1745-revert-1744-revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename""""""""""""
2024-04-26 14:44:10 -04:00
Maidul Islam
50dbefeb48 Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename"""""""""""" 2024-04-26 14:43:57 -04:00
Maidul Islam
56ac2c6780 Merge pull request #1750 from Infisical/revert-1749-revert-1748-revert-1747-revert-1746-revert-1745-revert-1744-revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename"""""""""""
2024-04-26 14:43:54 -04:00
Maidul Islam
c2f16da411 Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename""""""""""" 2024-04-26 14:43:46 -04:00
Maidul Islam
8223aee2ef Update update-be-new-migration-latest-timestamp.yml 2024-04-26 14:43:38 -04:00
Maidul Islam
5bd2af9621 Merge pull request #1749 from Infisical/revert-1748-revert-1747-revert-1746-revert-1745-revert-1744-revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename""""""""""
2024-04-26 14:28:44 -04:00
Maidul Islam
b3df6ce6b5 Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename"""""""""" 2024-04-26 14:28:34 -04:00
Maidul Islam
e12eb5347d Merge pull request #1748 from Infisical/revert-1747-revert-1746-revert-1745-revert-1744-revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename"""""""""
2024-04-26 14:28:31 -04:00
Maidul Islam
83a4426d31 Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename""""""""" 2024-04-26 14:28:22 -04:00
Maidul Islam
3fd1fbc355 Update update-be-new-migration-latest-timestamp.yml 2024-04-26 14:28:13 -04:00
Maidul Islam
306d2b4bd9 Merge pull request #1747 from Infisical/revert-1746-revert-1745-revert-1744-revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename""""""""
2024-04-26 14:17:42 -04:00
Maidul Islam
c2c66af1f9 Revert "Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename"""""""" 2024-04-26 14:17:30 -04:00
Maidul Islam
7ae65478aa Merge pull request #1746 from Infisical/revert-1745-revert-1744-revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename"""""""
2024-04-26 14:17:26 -04:00
Maidul Islam
b1594e65c6 Revert "Revert "Revert "Revert "Revert "Revert "Revert "test migration rename""""""" 2024-04-26 14:17:17 -04:00
Maidul Islam
0bce5b1daa Update update-be-new-migration-latest-timestamp.yml 2024-04-26 14:16:29 -04:00
Maidul Islam
207db93483 Merge pull request #1745 from Infisical/revert-1744-revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "Revert "test migration rename""""""
2024-04-26 14:10:22 -04:00
Maidul Islam
972f6a4887 Revert "Revert "Revert "Revert "Revert "Revert "test migration rename"""""" 2024-04-26 14:09:58 -04:00
Maidul Islam
6e1bece9d9 Merge pull request #1744 from Infisical/revert-1743-revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "Revert "test migration rename"""""
2024-04-26 14:09:41 -04:00
Maidul Islam
63e8bc1845 Revert "Revert "Revert "Revert "Revert "test migration rename""""" 2024-04-26 14:09:26 -04:00
Maidul Islam
4f92663b66 Update update-be-new-migration-latest-timestamp.yml 2024-04-26 14:09:15 -04:00
Maidul Islam
a66a6790c0 Merge pull request #1743 from Infisical/revert-1742-revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "Revert "test migration rename""""
2024-04-26 14:02:29 -04:00
Maidul Islam
bde853d280 Update update-be-new-migration-latest-timestamp.yml 2024-04-26 14:01:47 -04:00
Maidul Islam
acda627236 Revert "Revert "Revert "Revert "test migration rename"""" 2024-04-26 14:01:09 -04:00
Maidul Islam
875afbb4d6 Merge pull request #1742 from Infisical/revert-1741-revert-1740-revert-1739-test-db-rename
Revert "Revert "Revert "test migration rename"""
2024-04-26 14:01:06 -04:00
Maidul Islam
56f50a18dc Revert "Revert "Revert "test migration rename""" 2024-04-26 14:00:50 -04:00
Maidul Islam
801c438d05 Merge pull request #1741 from Infisical/revert-1740-revert-1739-test-db-rename
Revert "Revert "test migration rename""
2024-04-26 13:58:18 -04:00
Maidul Islam
baba411502 Update update-be-new-migration-latest-timestamp.yml 2024-04-26 13:58:01 -04:00
Maidul Islam
4c20ac6564 Revert "Revert "test migration rename"" 2024-04-26 13:56:03 -04:00
Maidul Islam
4e8556dec2 Merge pull request #1740 from Infisical/revert-1739-test-db-rename
Revert "test migration rename"
2024-04-26 13:55:57 -04:00
Maidul Islam
2d7b9ec1e4 Revert "test migration rename" 2024-04-26 13:55:43 -04:00
Maidul Islam
8bb9ed4394 Merge pull request #1739 from Infisical/test-db-rename
test migration rename
2024-04-26 13:50:48 -04:00
Maidul Islam
e4246ae85f Update update-be-new-migration-latest-timestamp.yml 2024-04-26 13:50:22 -04:00
Tuan Dang
d0cb06d875 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-26 09:08:30 -07:00
Tuan Dang
d42f620e1b Continue user aliases 2024-04-26 09:02:10 -07:00
Daniel Hougaard
5c0e5a8ae0 Feat: API Docs revamp (Service Token Deprecation) 2024-04-26 05:08:27 +02:00
Tuan Dang
71e309bbcb Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-25 17:03:23 -07:00
Tuan Dang
8ff407927c Continue merge user 2024-04-25 17:02:55 -07:00
Tuan Dang
d9005e8665 Merge remote-tracking branch 'origin' into groups-phase-2c 2024-04-25 06:50:02 -07:00
Akhil Mohan
5e0d64525f feat(server): fixed ts error 2024-04-24 19:32:46 +05:30
Akhil Mohan
8bcf936b91 docs: dynamic secret aws iam guide 2024-04-24 18:46:42 +05:30
Akhil Mohan
1a2508d91a feat(ui): dynamic secret aws iam ui implemented 2024-04-24 18:46:01 +05:30
Akhil Mohan
e81a77652f feat(server): dynamic secret aws iam implemented 2024-04-24 18:45:40 +05:30
snyk-bot
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
Tuan Dang
54fcc23a6c Begin groups phase 2b 2024-04-19 16:16:16 -07:00
593 changed files with 20446 additions and 17490 deletions

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

@@ -2,8 +2,7 @@ name: Rename Migrations
on: on:
pull_request: pull_request:
types: types: [closed]
- closed
paths: paths:
- 'backend/src/db/migrations/**' - 'backend/src/db/migrations/**'
@@ -11,26 +10,50 @@ jobs:
rename: rename:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event.pull_request.merged == true if: github.event.pull_request.merged == true
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@v2 uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get list of newly added files in migration folder - name: Get list of newly added files in migration folder
run: git diff --name-status HEAD^ HEAD backend/src/db/migrations | grep '^A' | cut -f2 | xargs -n1 basename > added_files.txt run: |
git diff --name-status HEAD^ HEAD backend/src/db/migrations | grep '^A' | cut -f2 | xargs -n1 basename > added_files.txt
- name: Script to rename migrations if [ ! -s added_files.txt ]; then
echo "No new files added. Skipping"
echo "SKIP_RENAME=true" >> $GITHUB_ENV
fi
- name: Script to rename migrations
if: env.SKIP_RENAME != 'true'
run: python .github/resources/rename_migration_files.py run: python .github/resources/rename_migration_files.py
- name: Commit and push changes - name: Commit and push changes
if: env.SKIP_RENAME != 'true'
run: | run: |
git config user.name github-actions git config user.name github-actions
git config user.email github-actions@github.com git config user.email github-actions@github.com
git add ./backend/src/db/migrations git add ./backend/src/db/migrations
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: Push changes
env:
TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
run: |
git push https://$GITHUB_ACTOR:$TOKEN@github.com/${{ github.repository }}.git HEAD:main
- 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
if: env.SKIP_RENAME != 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: renamed new migration files to latest UTC (gh-action)'
title: 'GH Action: rename new migration file 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

@@ -1,10 +0,0 @@
import { Knex } from "knex";
export async function up(knex: Knex): Promise<void> {
}
export async function down(knex: Knex): Promise<void> {
}

View File

@@ -0,0 +1,28 @@
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");
const doesCreatedAtExist = await knex.schema.hasColumn(TableName.AuditLog, "createdAt");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesProjectIdExist && doesCreatedAtExist) t.index(["projectId", "createdAt"]);
if (doesOrgIdExist && doesCreatedAtExist) t.index(["orgId", "createdAt"]);
});
}
}
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");
const doesCreatedAtExist = await knex.schema.hasColumn(TableName.AuditLog, "createdAt");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesProjectIdExist && doesCreatedAtExist) t.dropIndex(["projectId", "createdAt"]);
if (doesOrgIdExist && doesCreatedAtExist) t.dropIndex(["orgId", "createdAt"]);
});
}
}

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,20 @@
import { logger } from "@app/lib/logger"; 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 { 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 +22,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 +58,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,37 +70,49 @@ export const auditLogQueueServiceFactory = ({
eventMetadata: event.metadata, eventMetadata: event.metadata,
userAgentType userAgentType
}); });
});
queueService.start(QueueName.AuditLogPrune, async () => { const logStreams = orgId ? await auditLogStreamDAL.find({ orgId }) : [];
logger.info(`${QueueName.AuditLogPrune}: queue task started`); await Promise.allSettled(
await auditLogDAL.pruneAuditLog(); logStreams.map(
logger.info(`${QueueName.AuditLogPrune}: queue task completed`); 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[])
: [];
// we do a repeat cron job in utc timezone at 12 Midnight each day const headers: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
const startAuditLogPruneJob = async () => {
// clear previous job if (streamHeaders.length)
await queueService.stopRepeatableJob( streamHeaders.forEach(({ key, value }) => {
QueueName.AuditLogPrune, headers[key] = value;
QueueJobs.AuditLogPrune, });
{ pattern: "0 0 * * *", utc: true },
QueueName.AuditLogPrune // just a job id return request.post(url, auditLog, {
headers,
// request timeout
timeout: AUDIT_LOG_STREAM_TIMEOUT,
// connection timeout
signal: AbortSignal.timeout(AUDIT_LOG_STREAM_TIMEOUT)
});
}
)
); );
await queueService.queue(QueueName.AuditLogPrune, QueueJobs.AuditLogPrune, undefined, {
delay: 5000,
jobId: QueueName.AuditLogPrune,
repeat: { pattern: "0 0 * * *", utc: true }
});
};
queueService.listen(QueueName.AuditLogPrune, "failed", (err) => {
logger.error(err?.failedReason, `${QueueName.AuditLogPrune}: log pruning failed`);
}); });
return { return {
pushToLog, pushToLog
startAuditLogPruneJob
}; };
}; };

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

@@ -0,0 +1,194 @@
import {
AddUserToGroupCommand,
AttachUserPolicyCommand,
CreateAccessKeyCommand,
CreateUserCommand,
DeleteAccessKeyCommand,
DeleteUserCommand,
DeleteUserPolicyCommand,
DetachUserPolicyCommand,
GetUserCommand,
IAMClient,
ListAccessKeysCommand,
ListAttachedUserPoliciesCommand,
ListGroupsForUserCommand,
ListUserPoliciesCommand,
PutUserPolicyCommand,
RemoveUserFromGroupCommand
} from "@aws-sdk/client-iam";
import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { DynamicSecretAwsIamSchema, TDynamicProviderFns } from "./models";
const generateUsername = () => {
return alphaNumericNanoId(32);
};
export const AwsIamProvider = (): TDynamicProviderFns => {
const validateProviderInputs = async (inputs: unknown) => {
const providerInputs = await DynamicSecretAwsIamSchema.parseAsync(inputs);
return providerInputs;
};
const getClient = async (providerInputs: z.infer<typeof DynamicSecretAwsIamSchema>) => {
const client = new IAMClient({
region: providerInputs.region,
credentials: {
accessKeyId: providerInputs.accessKey,
secretAccessKey: providerInputs.secretAccessKey
}
});
return client;
};
const validateConnection = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs);
const client = await getClient(providerInputs);
const isConnected = await client.send(new GetUserCommand({})).then(() => true);
return isConnected;
};
const create = async (inputs: unknown) => {
const providerInputs = await validateProviderInputs(inputs);
const client = await getClient(providerInputs);
const username = generateUsername();
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
const createUserRes = await client.send(
new CreateUserCommand({
Path: awsPath,
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }],
UserName: username
})
);
if (!createUserRes.User) throw new BadRequestError({ message: "Failed to create AWS IAM User" });
if (userGroups) {
await Promise.all(
userGroups
.split(",")
.filter(Boolean)
.map((group) =>
client.send(new AddUserToGroupCommand({ UserName: createUserRes?.User?.UserName, GroupName: group }))
)
);
}
if (policyArns) {
await Promise.all(
policyArns
.split(",")
.filter(Boolean)
.map((policyArn) =>
client.send(new AttachUserPolicyCommand({ UserName: createUserRes?.User?.UserName, PolicyArn: policyArn }))
)
);
}
if (policyDocument) {
await client.send(
new PutUserPolicyCommand({
UserName: createUserRes.User.UserName,
PolicyName: `infisical-dynamic-policy-${alphaNumericNanoId(4)}`,
PolicyDocument: policyDocument
})
);
}
const createAccessKeyRes = await client.send(
new CreateAccessKeyCommand({
UserName: createUserRes.User.UserName
})
);
if (!createAccessKeyRes.AccessKey)
throw new BadRequestError({ message: "Failed to create AWS IAM User access key" });
return {
entityId: username,
data: {
ACCESS_KEY: createAccessKeyRes.AccessKey.AccessKeyId,
SECRET_ACCESS_KEY: createAccessKeyRes.AccessKey.SecretAccessKey,
USERNAME: username
}
};
};
const revoke = async (inputs: unknown, entityId: string) => {
const providerInputs = await validateProviderInputs(inputs);
const client = await getClient(providerInputs);
const username = entityId;
// remove user from groups
const userGroups = await client.send(new ListGroupsForUserCommand({ UserName: username }));
await Promise.all(
(userGroups.Groups || []).map(({ GroupName }) =>
client.send(
new RemoveUserFromGroupCommand({
GroupName,
UserName: username
})
)
)
);
// remove user access keys
const userAccessKeys = await client.send(new ListAccessKeysCommand({ UserName: username }));
await Promise.all(
(userAccessKeys.AccessKeyMetadata || []).map(({ AccessKeyId }) =>
client.send(
new DeleteAccessKeyCommand({
AccessKeyId,
UserName: username
})
)
)
);
// remove user inline policies
const userInlinePolicies = await client.send(new ListUserPoliciesCommand({ UserName: username }));
await Promise.all(
(userInlinePolicies.PolicyNames || []).map((policyName) =>
client.send(
new DeleteUserPolicyCommand({
PolicyName: policyName,
UserName: username
})
)
)
);
// remove user attached policies
const userAttachedPolicies = await client.send(new ListAttachedUserPoliciesCommand({ UserName: username }));
await Promise.all(
(userAttachedPolicies.AttachedPolicies || []).map((policy) =>
client.send(
new DetachUserPolicyCommand({
PolicyArn: policy.PolicyArn,
UserName: username
})
)
)
);
await client.send(new DeleteUserCommand({ UserName: username }));
return { entityId: username };
};
const renew = async (_inputs: unknown, entityId: string) => {
// do nothing
const username = entityId;
return { entityId: username };
};
return {
validateProviderInputs,
validateConnection,
create,
revoke,
renew
};
};

View File

@@ -1,8 +1,10 @@
import { AwsIamProvider } from "./aws-iam";
import { CassandraProvider } from "./cassandra"; import { CassandraProvider } from "./cassandra";
import { DynamicSecretProviders } from "./models"; import { DynamicSecretProviders } from "./models";
import { SqlDatabaseProvider } from "./sql-database"; import { SqlDatabaseProvider } from "./sql-database";
export const buildDynamicSecretProviders = () => ({ export const buildDynamicSecretProviders = () => ({
[DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider(), [DynamicSecretProviders.SqlDatabase]: SqlDatabaseProvider(),
[DynamicSecretProviders.Cassandra]: CassandraProvider() [DynamicSecretProviders.Cassandra]: CassandraProvider(),
[DynamicSecretProviders.AwsIam]: AwsIamProvider()
}); });

View File

@@ -8,38 +8,51 @@ export enum SqlProviders {
export const DynamicSecretSqlDBSchema = z.object({ export const DynamicSecretSqlDBSchema = z.object({
client: z.nativeEnum(SqlProviders), client: z.nativeEnum(SqlProviders),
host: z.string().toLowerCase(), host: z.string().trim().toLowerCase(),
port: z.number(), port: z.number(),
database: z.string(), database: z.string().trim(),
username: z.string(), username: z.string().trim(),
password: z.string(), password: z.string().trim(),
creationStatement: z.string(), creationStatement: z.string().trim(),
revocationStatement: z.string(), revocationStatement: z.string().trim(),
renewStatement: z.string().optional(), renewStatement: z.string().trim().optional(),
ca: z.string().optional() ca: z.string().optional()
}); });
export const DynamicSecretCassandraSchema = z.object({ export const DynamicSecretCassandraSchema = z.object({
host: z.string().toLowerCase(), host: z.string().trim().toLowerCase(),
port: z.number(), port: z.number(),
localDataCenter: z.string().min(1), localDataCenter: z.string().trim().min(1),
keyspace: z.string().optional(), keyspace: z.string().trim().optional(),
username: z.string(), username: z.string().trim(),
password: z.string(), password: z.string().trim(),
creationStatement: z.string(), creationStatement: z.string().trim(),
revocationStatement: z.string(), revocationStatement: z.string().trim(),
renewStatement: z.string().optional(), renewStatement: z.string().trim().optional(),
ca: z.string().optional() ca: z.string().optional()
}); });
export const DynamicSecretAwsIamSchema = z.object({
accessKey: z.string().trim().min(1),
secretAccessKey: z.string().trim().min(1),
region: z.string().trim().min(1),
awsPath: z.string().trim().optional(),
permissionBoundaryPolicyArn: z.string().trim().optional(),
policyDocument: z.string().trim().optional(),
userGroups: z.string().trim().optional(),
policyArns: z.string().trim().optional()
});
export enum DynamicSecretProviders { export enum DynamicSecretProviders {
SqlDatabase = "sql-database", SqlDatabase = "sql-database",
Cassandra = "cassandra" Cassandra = "cassandra",
AwsIam = "aws-iam"
} }
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }), z.object({ type: z.literal(DynamicSecretProviders.SqlDatabase), inputs: DynamicSecretSqlDBSchema }),
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }) z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }),
z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema })
]); ]);
export type TDynamicProviderFns = { export type TDynamicProviderFns = {

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",
@@ -592,10 +662,12 @@ export const INTEGRATION = {
secretPrefix: "The prefix for the saved secret. Used by GCP.", secretPrefix: "The prefix for the saved secret. Used by GCP.",
secretSuffix: "The suffix for the saved secret. Used by GCP.", secretSuffix: "The suffix for the saved secret. Used by GCP.",
initialSyncBehavoir: "Type of syncing behavoir with the integration.", initialSyncBehavoir: "Type of syncing behavoir with the integration.",
mappingBehavior: "The mapping behavior of the 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 +684,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

@@ -104,24 +104,68 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
throw new DatabaseError({ error, name: "Create" }); throw new DatabaseError({ error, name: "Create" });
} }
}, },
updateById: async (id: string, data: Tables[Tname]["update"], tx?: Knex) => { updateById: async (
id: string,
{
$incr,
$decr,
...data
}: Tables[Tname]["update"] & {
$incr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
$decr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
},
tx?: Knex
) => {
try { try {
const [res] = await (tx || db)(tableName) const query = (tx || db)(tableName)
.where({ id } as never) .where({ id } as never)
.update(data as never) .update(data as never)
.returning("*"); .returning("*");
return res; if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
const [docs] = await query;
return docs;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Update by id" }); throw new DatabaseError({ error, name: "Update by id" });
} }
}, },
update: async (filter: TFindFilter<Tables[Tname]["base"]>, data: Tables[Tname]["update"], tx?: Knex) => { update: async (
filter: TFindFilter<Tables[Tname]["base"]>,
{
$incr,
$decr,
...data
}: Tables[Tname]["update"] & {
$incr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
$decr?: { [x in keyof Partial<Tables[Tname]["base"]>]: number };
},
tx?: Knex
) => {
try { try {
const res = await (tx || db)(tableName) const query = (tx || db)(tableName)
.where(buildFindFilter(filter)) .where(buildFindFilter(filter))
.update(data as never) .update(data as never)
.returning("*"); .returning("*");
return res; // increment and decrement operation in update
if ($incr) {
Object.entries($incr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
if ($decr) {
Object.entries($decr).forEach(([incrementField, incrementValue]) => {
void query.increment(incrementField, incrementValue);
});
}
return await query;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Update" }); throw new DatabaseError({ error, name: "Update" });
} }

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

@@ -12,7 +12,9 @@ export enum QueueName {
SecretRotation = "secret-rotation", SecretRotation = "secret-rotation",
SecretReminder = "secret-reminder", SecretReminder = "secret-reminder",
AuditLog = "audit-log", AuditLog = "audit-log",
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune", AuditLogPrune = "audit-log-prune",
DailyResourceCleanUp = "daily-resource-cleanup",
TelemetryInstanceStats = "telemtry-self-hosted-stats", TelemetryInstanceStats = "telemtry-self-hosted-stats",
IntegrationSync = "sync-integrations", IntegrationSync = "sync-integrations",
SecretWebhook = "secret-webhook", SecretWebhook = "secret-webhook",
@@ -26,7 +28,9 @@ export enum QueueJobs {
SecretReminder = "secret-reminder-job", SecretReminder = "secret-reminder-job",
SecretRotation = "secret-rotation-job", SecretRotation = "secret-rotation-job",
AuditLog = "audit-log-job", AuditLog = "audit-log-job",
// TODO(akhilmhdh): This will get removed later. For now this is kept to stop the repeatable queue
AuditLogPrune = "audit-log-prune-job", AuditLogPrune = "audit-log-prune-job",
DailyResourceCleanUp = "daily-resource-cleanup-job",
SecWebhook = "secret-webhook-trigger", SecWebhook = "secret-webhook-trigger",
TelemetryInstanceStats = "telemetry-self-hosted-stats", TelemetryInstanceStats = "telemetry-self-hosted-stats",
IntegrationSync = "secret-integration-pull", IntegrationSync = "secret-integration-pull",
@@ -55,6 +59,10 @@ export type TQueueJobTypes = {
name: QueueJobs.AuditLog; name: QueueJobs.AuditLog;
payload: TCreateAuditLogDTO; payload: TCreateAuditLogDTO;
}; };
[QueueName.DailyResourceCleanUp]: {
name: QueueJobs.DailyResourceCleanUp;
payload: undefined;
};
[QueueName.AuditLogPrune]: { [QueueName.AuditLogPrune]: {
name: QueueJobs.AuditLogPrune; name: QueueJobs.AuditLogPrune;
payload: undefined; payload: undefined;
@@ -65,7 +73,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;
@@ -166,7 +180,9 @@ export const queueServiceFactory = (redisUrl: string) => {
jobId?: string jobId?: string
) => { ) => {
const q = queueContainer[name]; const q = queueContainer[name];
return q.removeRepeatable(job, repeatOpt, jobId); if (q) {
return q.removeRepeatable(job, repeatOpt, jobId);
}
}; };
const stopRepeatableJobByJobId = async <T extends QueueName>(name: T, jobId: string) => { const stopRepeatableJobByJobId = async <T extends QueueName>(name: T, jobId: string) => {

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

@@ -6,6 +6,7 @@ const headersOrder = [
"cf-connecting-ip", // Cloudflare "cf-connecting-ip", // Cloudflare
"Cf-Pseudo-IPv4", // Cloudflare "Cf-Pseudo-IPv4", // Cloudflare
"x-client-ip", // Most common "x-client-ip", // Most common
"x-envoy-external-address", // for envoy
"x-forwarded-for", // Mostly used by proxies "x-forwarded-for", // Mostly used by proxies
"fastly-client-ip", "fastly-client-ip",
"true-client-ip", // Akamai and Cloudflare "true-client-ip", // Akamai and Cloudflare
@@ -23,7 +24,21 @@ export const fastifyIp = fp(async (fastify) => {
const forwardedIpHeader = headersOrder.find((header) => Boolean(req.headers[header])); const forwardedIpHeader = headersOrder.find((header) => Boolean(req.headers[header]));
const forwardedIp = forwardedIpHeader ? req.headers[forwardedIpHeader] : undefined; const forwardedIp = forwardedIpHeader ? req.headers[forwardedIpHeader] : undefined;
if (forwardedIp) { if (forwardedIp) {
req.realIp = Array.isArray(forwardedIp) ? forwardedIp[0] : forwardedIp; if (Array.isArray(forwardedIp)) {
// eslint-disable-next-line
req.realIp = forwardedIp[0];
return;
}
if (forwardedIp.includes(",")) {
// the ip header when placed with load balancers that proxy request
// will attach the internal ips to header by appending with comma
// https://github.com/go-chi/chi/blob/master/middleware/realip.go
const clientIPFromProxy = forwardedIp.slice(0, forwardedIp.indexOf(",")).trim();
req.realIp = clientIPFromProxy;
return;
}
req.realIp = forwardedIp;
} else { } else {
req.realIp = req.ip; req.realIp = req.ip;
} }

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";
@@ -100,6 +115,7 @@ import { projectMembershipServiceFactory } from "@app/services/project-membershi
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal"; import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal";
import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal";
import { secretQueueFactory } from "@app/services/secret/secret-queue"; import { secretQueueFactory } from "@app/services/secret/secret-queue";
import { secretServiceFactory } from "@app/services/secret/secret-service"; import { secretServiceFactory } from "@app/services/secret/secret-service";
@@ -145,7 +161,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 +172,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 +210,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 +227,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 +274,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 +290,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 +331,9 @@ export const registerRoutes = async (
licenseService, licenseService,
scimDAL, scimDAL,
userDAL, userDAL,
userAliasDAL,
orgDAL, orgDAL,
orgMembershipDAL,
projectDAL, projectDAL,
projectMembershipDAL, projectMembershipDAL,
groupDAL, groupDAL,
@@ -304,6 +349,7 @@ export const registerRoutes = async (
ldapConfigDAL, ldapConfigDAL,
ldapGroupMapDAL, ldapGroupMapDAL,
orgDAL, orgDAL,
orgMembershipDAL,
orgBotDAL, orgBotDAL,
groupDAL, groupDAL,
groupProjectDAL, groupProjectDAL,
@@ -327,8 +373,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 +388,7 @@ export const registerRoutes = async (
userDAL userDAL
}); });
const orgService = orgServiceFactory({ const orgService = orgServiceFactory({
userAliasDAL,
licenseService, licenseService,
samlConfigDAL, samlConfigDAL,
orgRoleDAL, orgRoleDAL,
@@ -497,8 +549,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 +611,7 @@ export const registerRoutes = async (
}); });
const sarService = secretApprovalRequestServiceFactory({ const sarService = secretApprovalRequestServiceFactory({
permissionService, permissionService,
projectBotService,
folderDAL, folderDAL,
secretDAL, secretDAL,
secretTagDAL, secretTagDAL,
@@ -571,6 +626,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 +716,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({
@@ -665,14 +770,19 @@ export const registerRoutes = async (
folderDAL, folderDAL,
licenseService licenseService
}); });
const dailyResourceCleanUp = dailyResourceCleanUpQueueServiceFactory({
auditLogDAL,
queueService,
identityAccessTokenDAL
});
await superAdminService.initServerCfg(); await superAdminService.initServerCfg();
// //
// setup the communication with license key server // setup the communication with license key server
await licenseService.init(); await licenseService.init();
await auditLogQueue.startAuditLogPruneJob();
await telemetryQueue.startTelemetryCheck(); await telemetryQueue.startTelemetryCheck();
await dailyResourceCleanUp.startCleanUp();
// inject all services // inject all services
server.decorate<FastifyZodProvider["services"]>("services", { server.decorate<FastifyZodProvider["services"]>("services", {
@@ -706,7 +816,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 +830,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) };
}
});
};

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