Compare commits

..

162 Commits

Author SHA1 Message Date
ArshBallagan
0ccc279805 Update terraform.mdx 2025-08-01 15:56:06 -07:00
ArshBallagan
7b160e0014 Update terraform.mdx 2025-08-01 15:55:09 -07:00
ArshBallagan
c31ede99b1 Updating incorrect resource fields 2025-07-29 15:08:49 -07:00
ArshBallagan
71f609fa4f Adding production examples 2025-07-29 14:07:56 -07:00
ArshBallagan
8b963479a9 Update terraform.mdx 2025-07-28 16:39:48 -07:00
ArshBallagan
932e87f3e4 Update terraform.mdx 2025-07-28 16:25:44 -07:00
ArshBallagan
4f51ade2cd Adding remaining accordion sections and best practices 2025-07-28 15:59:13 -07:00
ArshBallagan
f3216800eb Initial commit, creating comprehensive guide for terraform usage. 2025-07-28 14:18:12 -07:00
Scott Wilson
7d1bc86702 Merge pull request #4236 from Infisical/improve-access-denied-banner-design
improvement(frontend): revise access restricted banner and refactor/update relevant locations
2025-07-28 10:31:14 -07:00
Scott Wilson
975b621bc8 fix: remove passthrough on banner guard for kms pages 2025-07-28 10:26:22 -07:00
Daniel Hougaard
ba9da3e6ec Merge pull request #4254 from Infisical/allow-click-outside-close-rotation-modal
improvement(frontend): remove click outside moda tol close disabling on various modals
2025-07-28 21:06:33 +04:00
carlosmonastyrski
d2274a622a Merge pull request #4251 from Infisical/fix/azureOAuthSeparateEnvVars
Separate Azure OAuth env vars to different env variables for each app connection
2025-07-28 14:06:01 -03:00
Scott Wilson
41ba7edba2 improvement: remove click outside modal close disabling on sync/data source/rotation modals 2025-07-28 09:50:18 -07:00
carlosmonastyrski
7acefbca29 Merge pull request #4220 from Infisical/feat/multipleApprovalEnvs
Allow multiple environments on secret and access policies
2025-07-28 12:22:40 -03:00
Daniel Hougaard
e246f6bbfe Merge pull request #4252 from Infisical/daniel/form-data-cve
Daniel/form data CVE
2025-07-28 19:01:27 +04:00
Carlos Monastyrski
f265fa6d37 Minor improvements to azure multi env variables 2025-07-28 10:14:21 -03:00
Daniel Hougaard
8eebd7228f Update package.json 2025-07-28 16:43:13 +04:00
Daniel Hougaard
2a5593ea30 update axios in oidc sink server 2025-07-28 16:42:21 +04:00
Daniel Hougaard
17af33372c uninstall axios in root 2025-07-28 16:40:58 +04:00
Daniel Hougaard
27da14df9d Fix CVE's 2025-07-28 16:40:20 +04:00
Carlos Monastyrski
cd4b9cd03a Improve azure client secrets env var name 2025-07-28 09:30:37 -03:00
Carlos Monastyrski
0779091d1f Separate Azure OAuth env vars to different env variables for each app connection 2025-07-28 09:14:43 -03:00
Maidul Islam
c421057cf1 Merge pull request #4250 from Infisical/fix/oracle-db-rotation-failing
fix: potential fix for oracle db rotation failing
2025-07-27 14:47:08 -04:00
Akhil Mohan
8df4616265 Update backend/src/ee/services/secret-rotation-v2/shared/sql-credentials/sql-credentials-rotation-fns.ts
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-07-28 00:09:30 +05:30
=
484f34a257 fix: potential fix for oracle db rotation failing 2025-07-28 00:03:01 +05:30
carlosmonastyrski
32851565a7 Merge pull request #4247 from Infisical/fix/azureClientSecretsPermissions
Fix/azure client secrets permissions
2025-07-25 20:52:04 -03:00
Carlos Monastyrski
68401a799e Fix env variables name on doc 2025-07-25 20:48:18 -03:00
Carlos Monastyrski
0adf2c830d Fix azure client secrets OAuth URL to use graph instead of vault 2025-07-25 20:47:17 -03:00
Carlos Monastyrski
3400a8f911 Small UI fix for environments label 2025-07-25 17:24:15 -03:00
Carlos Monastyrski
e6588b5d0e Set correct environmentName on listApprovalRequests 2025-07-25 17:00:11 -03:00
Daniel Hougaard
c68138ac21 Merge pull request #4245 from Infisical/daniel/fips-improvements
fix(fips): increased image size and migrations
2025-07-25 23:40:27 +04:00
Carlos Monastyrski
608979efa7 Merge branch 'main' into feat/multipleApprovalEnvs 2025-07-25 16:29:04 -03:00
Daniel Hougaard
d4f0301104 Update Dockerfile.fips.standalone-infisical 2025-07-25 23:13:26 +04:00
Daniel Hougaard
253c46f21d fips improvements 2025-07-25 23:09:23 +04:00
Maidul Islam
d8e39aed16 Merge pull request #4243 from Infisical/fix/secretReminderMigration
Add manual migration to secret imports rework
2025-07-25 15:01:04 -04:00
Carlos Monastyrski
72ee468208 Remove previous queue running the migration 2025-07-25 15:20:23 -03:00
carlosmonastyrski
18238b46a7 Merge pull request #4229 from Infisical/feat/azureClientSecretsNewAuth
Add client secrets authentication on Azure CS app connection
2025-07-25 15:00:49 -03:00
Carlos Monastyrski
d0ffae2c10 Add uuid validation to Azure client secrets 2025-07-25 14:53:46 -03:00
Carlos Monastyrski
7ce11cde95 Add cycle logic to next reminder migration 2025-07-25 14:47:57 -03:00
Carlos Monastyrski
af32948a05 Minor improvements on reminders migration 2025-07-25 13:35:06 -03:00
Daniel Hougaard
25753fc995 Merge pull request #4242 from Infisical/daniel/render-sync-auto-redeploy
feat(secret-sync/render): auto redeploy on sync
2025-07-25 20:31:47 +04:00
Carlos Monastyrski
cd71848800 Avoid migrating existing reminders 2025-07-25 13:10:54 -03:00
Carlos Monastyrski
4afc7a1981 Add manual migration to secret imports rework 2025-07-25 13:06:29 -03:00
Daniel Hougaard
11ca76ccca fix: restructure and requested changes 2025-07-25 20:05:20 +04:00
Daniel Hougaard
418aca8af0 feat(secret-sync/render): auto redeploy on sync 2025-07-25 19:50:28 +04:00
Carlos Monastyrski
99e8bdef58 Minor fixes on policies multi env migration 2025-07-25 01:37:25 -03:00
Carlos Monastyrski
7365f60835 Small code improvements 2025-07-25 01:23:01 -03:00
Scott Wilson
929822514e Merge pull request #4230 from Infisical/secret-dashboard-sing-env-col-resize
improvement(frontend): add col resize to secret dashboard env view
2025-07-24 20:08:18 -07:00
Daniel Hougaard
616ccb97f2 Merge pull request #4238 from Infisical/daniel/docs-fix
Update docs.json
2025-07-25 04:59:32 +04:00
Daniel Hougaard
7917a767e6 Update docs.json 2025-07-25 04:57:15 +04:00
carlosmonastyrski
ccff675e0d Merge pull request #4237 from Infisical/fix/remindersMigrationFix
Fix secret reminders migration job
2025-07-24 21:25:47 -03:00
Carlos Monastyrski
ad905b2ff7 Fix secret reminders migration job 2025-07-24 20:42:39 -03:00
Scott Wilson
4e960445a4 chore: remove unused tw css 2025-07-24 15:56:14 -07:00
Scott Wilson
7af5a4ad8d improvement: revise access restricted banner and refactor/update relevant locations 2025-07-24 15:52:29 -07:00
carlosmonastyrski
2ada753527 Merge pull request #4235 from Infisical/fix/renderRateLimit
Improve render retries and rate limits
2025-07-24 19:07:17 -03:00
Carlos Monastyrski
c031736701 Improve render api usage 2025-07-24 18:51:44 -03:00
Daniel Hougaard
91a1c34637 Merge pull request #4211 from Infisical/daniel/vault-import
feat(external-migrations): vault migrations
2025-07-25 01:16:50 +04:00
Carlos Monastyrski
eadb1a63fa Improve render retries and rate limits 2025-07-24 17:49:28 -03:00
Scott Wilson
f70a1e3db6 Merge pull request #4233 from Infisical/fix-identity-role-invalidation
fix(frontend): correct org identity mutation table invalidation
2025-07-24 12:17:03 -07:00
Scott Wilson
fc6ab94a06 fix: correct org identity mutation table invalidation 2025-07-24 12:08:41 -07:00
Scott Wilson
4feb3314e7 Merge pull request #4232 from Infisical/create-project-modal-dropdown
improvement(frontend): Adjust select dropdown styling in add project modal
2025-07-24 11:57:23 -07:00
Scott Wilson
d9a57d1391 fix: make side prop optional 2025-07-24 11:50:05 -07:00
Scott Wilson
2c99d41592 improvement: adjust select dropdown styling in add project modal 2025-07-24 11:42:04 -07:00
Scott Wilson
2535d1bc4b Merge pull request #4228 from Infisical/project-audit-logs-page
feature(project-audit-logs): add project audit logs pages
2025-07-24 10:49:02 -07:00
Scott Wilson
83e59ae160 feature: add col resize to secret dashboard env view 2025-07-24 10:18:57 -07:00
x032205
a8a1bc5f4a Merge pull request #4227 from Infisical/ENG-3345
feat(machine-identity): Add AWS attributes for ABAC
2025-07-24 11:59:17 -04:00
Daniel Hougaard
d2a4f265de Update ExternalMigrationsTab.tsx 2025-07-24 19:58:29 +04:00
x032205
3483f185a8 Doc tweaks 2025-07-24 11:44:10 -04:00
Scott Wilson
9bc24487b3 Merge pull request #4216 from Infisical/dashboard-filter-improvements
improvement(frontend): improve dashboard filter behavior and design
2025-07-24 08:33:24 -07:00
Daniel Hougaard
4af872e504 fix: ui state 2025-07-24 19:14:50 +04:00
Daniel Hougaard
716b88fa49 requested changes and docs 2025-07-24 19:09:24 +04:00
Carlos Monastyrski
b05ea8a69a Fix migration 2025-07-24 12:07:01 -03:00
Carlos Monastyrski
0d97bb4c8c Merge branch 'main' into feat/multipleApprovalEnvs 2025-07-24 12:03:07 -03:00
Maidul Islam
cb700c5124 Merge pull request #4183 from Infisical/fix/oracle-app-connection
fix: resolved oracle failing in app connection
2025-07-24 09:57:10 -04:00
=
8e829bdf85 fix: resolved oracle failing in app connection 2025-07-24 19:23:52 +05:30
Daniel Hougaard
716f061c01 Merge branch 'heads/main' into daniel/vault-import 2025-07-24 17:29:55 +04:00
Carlos Monastyrski
5af939992c Update docs 2025-07-24 10:04:25 -03:00
Carlos Monastyrski
aec4ee905e Add client secrets authentication on Azure CS app connection 2025-07-24 09:40:54 -03:00
Scott Wilson
dd008724fb fix type error 2025-07-23 18:26:01 -07:00
Scott Wilson
dd0c07fb95 improvements: remove fixed css 2025-07-23 18:18:59 -07:00
Scott Wilson
d935b28925 feature: add project audit logs 2025-07-23 16:48:54 -07:00
x032205
60620840f2 Tweaks 2025-07-23 16:48:06 -04:00
x032205
e798eb2a4e feat(machine-identity): Add AWS attributes for ABAC 2025-07-23 16:30:55 -04:00
Scott Wilson
e96e7b835d improvements: address feedback 2025-07-23 12:43:48 -07:00
carlosmonastyrski
75622ed03e Merge pull request #3926 from Infisical/feat/remindersImprovement
feat(secret-reminders): rework secret reminders logic
2025-07-23 16:07:04 -03:00
Scott Wilson
a7041fcade Merge pull request #4199 from Infisical/search-by-tags-metadata
improvement(dashboard): add secret tag/metadata search functionality to single env view dashboard
2025-07-23 11:27:11 -07:00
Scott Wilson
0b38fc7843 Merge pull request #4181 from Infisical/org-policy-edit-page-revisions
improvements(frontend): org and project policy page ui improvements
2025-07-23 11:26:38 -07:00
Maidul Islam
e678c19874 Merge pull request #4225 from Infisical/fix/secret-scanning-delete
feat: updated invalid url
2025-07-23 13:38:45 -04:00
=
878e12ea5c feat: updated invalid url 2025-07-23 23:06:38 +05:30
x032205
485a90bde1 Merge pull request #4224 from Infisical/fix-secret-rotation-defaults
Fix secret rotation defaults
2025-07-23 12:45:39 -04:00
x032205
98b6bdad76 Fix secret rotation defaults 2025-07-23 12:44:23 -04:00
Carlos Monastyrski
f490ca22ac Small fix on new permission field actionProjectType missin on reminders service 2025-07-23 13:07:58 -03:00
Maidul Islam
2d8de9e782 update product names for project templates 2025-07-23 10:50:46 -04:00
Maidul Islam
14d4cfdbe4 Merge pull request #4222 from Infisical/fix/secret-scanning-delete
fix: resolved project deletion not working for secret scanning on missing plan
2025-07-23 10:47:18 -04:00
x032205
e8bd73c0d0 Merge pull request #4201 from Infisical/check-gateway-license-in-service
License check on fnGetGatewayClientTlsByGatewayId
2025-07-23 10:41:58 -04:00
Akhil Mohan
3406457c08 Merge pull request #4218 from dcs-soni/bug/banner-flicker
fix: redis banner appears only when it is not configured
2025-07-23 20:10:18 +05:30
=
c16764b62b fix: resolved project deletion not working for secret scanning on missing plan 2025-07-23 20:07:45 +05:30
Sid
ab56a69d59 feat: Digital Ocean App connection and App Platform secret sync (#4203)
* fix: save wip

* feat: final impl

* feat: docs

* Update backend/src/services/app-connection/digital-ocean/digital-ocean-connection-service.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* chore: remove empty conflict files

* Update backend/src/server/routes/v1/app-connection-routers/app-connection-router.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update frontend/src/components/secret-syncs/forms/schemas/digital-ocean-app-platform-sync-destination-schema.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update frontend/src/components/secret-syncs/forms/schemas/digital-ocean-app-platform-sync-destination-schema.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update frontend/src/components/secret-syncs/forms/SecretSyncDestinationFields/DigitalOceanAppPlatformSyncFields.tsx

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* Update backend/src/services/secret-sync/digital-ocean-app-platform/digital-ocean-app-platform-sync-schemas.ts

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

* fix: lint

* fix: api client

* fix: lint and types

* fix: typecheck lint

* fix: docs

* fix: docs

* fix: linting

---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-07-23 19:59:29 +05:30
Carlos Monastyrski
8520ca98c7 Merge branch 'main' into feat/remindersImprovement 2025-07-23 11:27:31 -03:00
carlosmonastyrski
95b997c100 Merge pull request #4214 from Infisical/fix/confirmCommitButtonFix
Redo confirm changes box
2025-07-23 11:08:38 -03:00
carlosmonastyrski
b433582ca6 Merge pull request #4210 from Infisical/feat/bitbucketSecretSync
Add Bitbucket Secret Sync
2025-07-23 11:07:07 -03:00
Maidul Islam
242cfe82c5 update product names 2025-07-23 09:38:08 -04:00
Carlos Monastyrski
60657f0bc6 Addressed PR suggestions 2025-07-23 10:37:23 -03:00
Maidul Islam
af4f7ec4f3 Merge pull request #4207 from Infisical/feat/split-back
feat: Move products out of projects
2025-07-23 09:27:53 -04:00
Maidul Islam
454e75cfd0 remove consoles 2025-07-23 09:26:32 -04:00
Carlos Monastyrski
05408bc151 Allow multiple environments on secret and access policies 2025-07-23 09:54:41 -03:00
=
95f8ae1cf8 fix: resolved migration issue 2025-07-23 14:31:55 +05:30
Maidul Islam
feb773152e update migration 2025-07-23 14:31:55 +05:30
=
7f35ff119e feat: resolved dual filter in all view 2025-07-23 14:31:54 +05:30
Scott Wilson
cb4cb922b9 improvement: design revisions and various overflow handling 2025-07-23 14:31:54 +05:30
=
dfecaae560 feat: added timeout function to migration 2025-07-23 14:31:54 +05:30
=
53bec6bc3e feat: resolved merge issue and fixed callback url 2025-07-23 14:31:54 +05:30
=
af48e7ce99 feat: more review changes 2025-07-23 14:31:54 +05:30
=
9f35b573d1 feat: resolved template bug 2025-07-23 14:31:54 +05:30
=
bcb1f35606 feat: hide environment in other products 2025-07-23 14:31:54 +05:30
=
67ab16aff3 feat: resolved lint fail 2025-07-23 14:31:53 +05:30
=
354aed5e8a feat: resolved broken ui for templates 2025-07-23 14:31:53 +05:30
=
e2e9dbc8aa feat: reverted license-fn 2025-07-23 14:31:53 +05:30
=
f38b8eac2b feat: made review changes 2025-07-23 14:31:53 +05:30
=
7c87feb546 feat: added defaultProduct as null 2025-07-23 14:31:53 +05:30
=
e0cbfe8865 feat: added card truncation 2025-07-23 14:31:53 +05:30
=
abda494374 feat: brought back assume privilege banner 2025-07-23 14:31:53 +05:30
=
272207c580 feat: resolved migration failing 2025-07-23 14:31:52 +05:30
=
4cf66a8bfd feat: completed migration script for project split revert 2025-07-23 14:31:52 +05:30
=
30ef7f395a feat: removed type default value in get projects 2025-07-23 14:31:52 +05:30
=
ec8ea76e2c feat: completed all layout changes needed for frontend 2025-07-23 14:31:52 +05:30
=
cc9f4fb5b3 feat: reverted template changes to project type based 2025-07-23 14:31:52 +05:30
=
33256c3462 feat: resolved frontend url changes and resolved ts error 2025-07-23 14:31:52 +05:30
=
864be1deb7 feat: revert back action project type 2025-07-23 14:31:51 +05:30
=
f10ab58d74 Revert "feat: removed all action project type check"
This reverts commit e028b4e26d.
2025-07-23 14:31:51 +05:30
dcs-soni
9ec4419d83 fix testing vars 2025-07-23 13:04:10 +05:30
dcs-soni
7ff7e5882a fix: redis banner appears only when it is not configured 2025-07-23 12:44:05 +05:30
Scott Wilson
e76e0f7bcc improvement: improve dashboard filter behavior and design 2025-07-22 17:14:45 -07:00
Daniel Hougaard
cb4999c1b4 Merge pull request #4215 from Infisical/daniel/rust-sdk-docs
Daniel/rust sdk docs
2025-07-23 04:01:58 +04:00
Scott Wilson
d4bdf04061 improvement: responsive and border color 2025-07-22 09:34:43 -07:00
Scott Wilson
4dcb3938e0 improvements: minor adjustments 2025-07-22 08:58:12 -07:00
Carlos Monastyrski
f992535812 Redo confirm changes box 2025-07-22 11:03:25 -03:00
Daniel Hougaard
464e32b0e9 Update VaultPlatformModal.tsx 2025-07-22 13:04:00 +04:00
Scott Wilson
4547b61d8f improvement: add metadata support to deep search 2025-07-21 18:18:04 -07:00
Carlos Monastyrski
047fd9371f Fix bitbucket iterationCount limit 2025-07-21 21:39:57 -03:00
Daniel Hougaard
bfd8b64871 requested changes 2025-07-22 02:15:21 +04:00
Daniel Hougaard
185cc4efba Update VaultPlatformModal copy.tsx 2025-07-22 01:50:28 +04:00
Daniel Hougaard
7150b9314d feat(external-migrations): vault migrations 2025-07-22 01:35:02 +04:00
Carlos Monastyrski
328f929a29 Addressed PR comments 2025-07-21 18:24:48 -03:00
Carlos Monastyrski
5019918516 Add secret sync app connection permission set 2025-07-21 11:44:21 -03:00
Carlos Monastyrski
ce877cd352 Addressed PR suggestions 2025-07-21 11:01:22 -03:00
Carlos Monastyrski
d44b3293b6 Add Bitbucket Secret Sync 2025-07-21 10:28:31 -03:00
x032205
4d8000e331 License check on fnGetGatewayClientTlsByGatewayId 2025-07-19 02:41:41 -04:00
Scott Wilson
90c341cf53 improvement: add secret tag/metadata search functionality to single env view dashboard 2025-07-18 18:22:11 -07:00
Scott Wilson
8df53dde3b improvements: address feedback 2025-07-17 15:27:28 -07:00
Carlos Monastyrski
394ecd24a0 Merge branch 'main' into feat/remindersImprovement 2025-07-17 17:35:41 -03:00
Daniel Hougaard
6d3acb5514 Update models.ts 2025-07-18 00:28:15 +04:00
Scott Wilson
1e08b3cdc2 chore: remove unused export 2025-07-16 15:05:10 -07:00
Scott Wilson
844f2bb72c improvements: org and project policy page ui improvements 2025-07-16 14:48:57 -07:00
Carlos Monastyrski
bd4968b60d Minor improvements on new reminders api 2025-07-14 16:48:05 -03:00
Carlos Monastyrski
6449699f03 Merge branch 'main' into feat/remindersImprovement 2025-07-14 10:19:33 -03:00
Carlos Monastyrski
0e680e366b Improve reminders router 2025-07-11 15:26:09 -03:00
Carlos Monastyrski
0af00ce82d Minor fix on add reminder table migration 2025-07-11 09:21:57 -03:00
Carlos Monastyrski
3153450dc5 Merge branch 'main' into feat/remindersImprovement 2025-07-11 08:59:21 -03:00
Carlos Monastyrski
50ba2e543c Minor improvements on new reminders logic 2025-07-11 08:02:18 -03:00
Carlos Monastyrski
e2559f10bc feat(secret-reminders): addressed PR suggestions and improvements 2025-07-04 11:58:09 -03:00
Carlos Monastyrski
0efc314f33 feat(secret-reminders): rework secret reminders logic 2025-07-04 09:47:36 -03:00
707 changed files with 18827 additions and 7644 deletions

View File

@@ -123,8 +123,17 @@ INF_APP_CONNECTION_GITHUB_RADAR_APP_WEBHOOK_SECRET=
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL= INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
# azure app connection # azure app connection
INF_APP_CONNECTION_AZURE_CLIENT_ID= INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID=
INF_APP_CONNECTION_AZURE_CLIENT_SECRET= INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET=
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID=
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET=
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID=
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET=
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID=
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET=
# datadog # datadog
SHOULD_USE_DATADOG_TRACER= SHOULD_USE_DATADOG_TRACER=

View File

@@ -145,7 +145,11 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
&& cd openssl-3.1.2 \ && cd openssl-3.1.2 \
&& ./Configure enable-fips \ && ./Configure enable-fips \
&& make \ && make \
&& make install_fips && make install_fips \
&& cd / \
&& rm -rf /openssl-build \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Install Infisical CLI # Install Infisical CLI
RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \ RUN curl -1sLf 'https://artifacts-cli.infisical.com/setup.deb.sh' | bash \
@@ -186,12 +190,11 @@ ENV NODE_ENV production
ENV STANDALONE_BUILD true ENV STANDALONE_BUILD true
ENV STANDALONE_MODE true ENV STANDALONE_MODE true
ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/ ENV ChrystokiConfigurationPath=/usr/safenet/lunaclient/
ENV NODE_OPTIONS="--max-old-space-size=1024" ENV NODE_OPTIONS="--max-old-space-size=8192 --force-fips"
# FIPS mode of operation: # FIPS mode of operation:
ENV OPENSSL_CONF=/backend/nodejs.fips.cnf ENV OPENSSL_CONF=/backend/nodejs.fips.cnf
ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules ENV OPENSSL_MODULES=/usr/local/lib/ossl-modules
ENV NODE_OPTIONS=--force-fips
ENV FIPS_ENABLED=true ENV FIPS_ENABLED=true

View File

@@ -59,7 +59,11 @@ RUN wget https://www.openssl.org/source/openssl-3.1.2.tar.gz \
&& cd openssl-3.1.2 \ && cd openssl-3.1.2 \
&& ./Configure enable-fips \ && ./Configure enable-fips \
&& make \ && make \
&& make install_fips && make install_fips \
&& cd / \
&& rm -rf /openssl-build \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# ? App setup # ? App setup

View File

@@ -24,6 +24,7 @@ export const mockQueue = (): TQueueServiceFactory => {
events[name] = event; events[name] = event;
}, },
getRepeatableJobs: async () => [], getRepeatableJobs: async () => [],
getDelayedJobs: async () => [],
clearQueue: async () => {}, clearQueue: async () => {},
stopJobById: async () => {}, stopJobById: async () => {},
stopJobByIdPg: async () => {}, stopJobByIdPg: async () => {},

View File

@@ -7,6 +7,7 @@
"": { "": {
"name": "backend", "name": "backend",
"version": "1.0.0", "version": "1.0.0",
"hasInstallScript": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@aws-sdk/client-elasticache": "^3.637.0", "@aws-sdk/client-elasticache": "^3.637.0",
@@ -61,7 +62,7 @@
"ajv": "^8.12.0", "ajv": "^8.12.0",
"argon2": "^0.31.2", "argon2": "^0.31.2",
"aws-sdk": "^2.1553.0", "aws-sdk": "^2.1553.0",
"axios": "^1.6.7", "axios": "^1.11.0",
"axios-retry": "^4.0.0", "axios-retry": "^4.0.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"botbuilder": "^4.23.2", "botbuilder": "^4.23.2",
@@ -13699,14 +13700,16 @@
} }
}, },
"node_modules/@types/request/node_modules/form-data": { "node_modules/@types/request/node_modules/form-data": {
"version": "2.5.2", "version": "2.5.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz",
"integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.6", "combined-stream": "^1.0.8",
"mime-types": "^2.1.12", "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.35",
"safe-buffer": "^5.2.1" "safe-buffer": "^5.2.1"
}, },
"engines": { "engines": {
@@ -15230,13 +15233,13 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.9", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.4",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
@@ -18761,13 +18764,15 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.2", "version": "4.0.4",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"license": "MIT",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0", "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
}, },
"engines": { "engines": {

View File

@@ -181,7 +181,7 @@
"ajv": "^8.12.0", "ajv": "^8.12.0",
"argon2": "^0.31.2", "argon2": "^0.31.2",
"aws-sdk": "^2.1553.0", "aws-sdk": "^2.1553.0",
"axios": "^1.6.7", "axios": "^1.11.0",
"axios-retry": "^4.0.0", "axios-retry": "^4.0.0",
"bcrypt": "^5.1.1", "bcrypt": "^5.1.1",
"botbuilder": "^4.23.2", "botbuilder": "^4.23.2",

View File

@@ -93,6 +93,7 @@ import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env
import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service"; import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key-service";
import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service";
import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service";
import { TReminderServiceFactory } from "@app/services/reminder/reminder-types";
import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service";
import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service";
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
@@ -125,6 +126,15 @@ declare module "@fastify/request-context" {
namespace: string; namespace: string;
name: string; name: string;
}; };
aws?: {
accountId: string;
arn: string;
userId: string;
partition: string;
service: string;
resourceType: string;
resourceName: string;
};
}; };
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string }; assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
@@ -285,6 +295,7 @@ declare module "fastify" {
secretScanningV2: TSecretScanningV2ServiceFactory; secretScanningV2: TSecretScanningV2ServiceFactory;
internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory; internalCertificateAuthority: TInternalCertificateAuthorityServiceFactory;
pkiTemplate: TPkiTemplatesServiceFactory; pkiTemplate: TPkiTemplatesServiceFactory;
reminder: TReminderServiceFactory;
}; };
// this is exclusive use for middlewares in which we need to inject data // this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer // everywhere else access using service layer

View File

@@ -489,6 +489,11 @@ import {
TWorkflowIntegrationsInsert, TWorkflowIntegrationsInsert,
TWorkflowIntegrationsUpdate TWorkflowIntegrationsUpdate
} from "@app/db/schemas"; } from "@app/db/schemas";
import {
TAccessApprovalPoliciesEnvironments,
TAccessApprovalPoliciesEnvironmentsInsert,
TAccessApprovalPoliciesEnvironmentsUpdate
} from "@app/db/schemas/access-approval-policies-environments";
import { import {
TIdentityLdapAuths, TIdentityLdapAuths,
TIdentityLdapAuthsInsert, TIdentityLdapAuthsInsert,
@@ -504,6 +509,17 @@ import {
TProjectMicrosoftTeamsConfigsInsert, TProjectMicrosoftTeamsConfigsInsert,
TProjectMicrosoftTeamsConfigsUpdate TProjectMicrosoftTeamsConfigsUpdate
} from "@app/db/schemas/project-microsoft-teams-configs"; } from "@app/db/schemas/project-microsoft-teams-configs";
import { TReminders, TRemindersInsert, TRemindersUpdate } from "@app/db/schemas/reminders";
import {
TRemindersRecipients,
TRemindersRecipientsInsert,
TRemindersRecipientsUpdate
} from "@app/db/schemas/reminders-recipients";
import {
TSecretApprovalPoliciesEnvironments,
TSecretApprovalPoliciesEnvironmentsInsert,
TSecretApprovalPoliciesEnvironmentsUpdate
} from "@app/db/schemas/secret-approval-policies-environments";
import { import {
TSecretReminderRecipients, TSecretReminderRecipients,
TSecretReminderRecipientsInsert, TSecretReminderRecipientsInsert,
@@ -881,6 +897,12 @@ declare module "knex/types/tables" {
TAccessApprovalPoliciesBypassersUpdate TAccessApprovalPoliciesBypassersUpdate
>; >;
[TableName.AccessApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType<
TAccessApprovalPoliciesEnvironments,
TAccessApprovalPoliciesEnvironmentsInsert,
TAccessApprovalPoliciesEnvironmentsUpdate
>;
[TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType< [TableName.AccessApprovalRequest]: KnexOriginal.CompositeTableType<
TAccessApprovalRequests, TAccessApprovalRequests,
TAccessApprovalRequestsInsert, TAccessApprovalRequestsInsert,
@@ -929,6 +951,11 @@ declare module "knex/types/tables" {
TSecretApprovalRequestSecretTagsInsert, TSecretApprovalRequestSecretTagsInsert,
TSecretApprovalRequestSecretTagsUpdate TSecretApprovalRequestSecretTagsUpdate
>; >;
[TableName.SecretApprovalPolicyEnvironment]: KnexOriginal.CompositeTableType<
TSecretApprovalPoliciesEnvironments,
TSecretApprovalPoliciesEnvironmentsInsert,
TSecretApprovalPoliciesEnvironmentsUpdate
>;
[TableName.SecretRotation]: KnexOriginal.CompositeTableType< [TableName.SecretRotation]: KnexOriginal.CompositeTableType<
TSecretRotations, TSecretRotations,
TSecretRotationsInsert, TSecretRotationsInsert,
@@ -1211,5 +1238,11 @@ declare module "knex/types/tables" {
TSecretScanningConfigsInsert, TSecretScanningConfigsInsert,
TSecretScanningConfigsUpdate TSecretScanningConfigsUpdate
>; >;
[TableName.Reminder]: KnexOriginal.CompositeTableType<TReminders, TRemindersInsert, TRemindersUpdate>;
[TableName.ReminderRecipient]: KnexOriginal.CompositeTableType<
TRemindersRecipients,
TRemindersRecipientsInsert,
TRemindersRecipientsUpdate
>;
} }
} }

View File

@@ -0,0 +1,43 @@
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.Reminder))) {
await knex.schema.createTable(TableName.Reminder, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("secretId").nullable();
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
t.string("message", 1024).nullable();
t.integer("repeatDays").checkPositive().nullable();
t.timestamp("nextReminderDate").notNullable();
t.timestamps(true, true, true);
t.unique("secretId");
});
}
if (!(await knex.schema.hasTable(TableName.ReminderRecipient))) {
await knex.schema.createTable(TableName.ReminderRecipient, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("reminderId").notNullable();
t.foreign("reminderId").references("id").inTable(TableName.Reminder).onDelete("CASCADE");
t.uuid("userId").notNullable();
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.timestamps(true, true, true);
t.index("reminderId");
t.index("userId");
t.unique(["reminderId", "userId"]);
});
}
await createOnUpdateTrigger(knex, TableName.Reminder);
await createOnUpdateTrigger(knex, TableName.ReminderRecipient);
}
export async function down(knex: Knex): Promise<void> {
await dropOnUpdateTrigger(knex, TableName.Reminder);
await dropOnUpdateTrigger(knex, TableName.ReminderRecipient);
await knex.schema.dropTableIfExists(TableName.ReminderRecipient);
await knex.schema.dropTableIfExists(TableName.Reminder);
}

View File

@@ -0,0 +1,432 @@
import slugify from "@sindresorhus/slugify";
import { Knex } from "knex";
import { v4 as uuidV4 } from "uuid";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { ProjectType, TableName } from "../schemas";
/* eslint-disable no-await-in-loop,@typescript-eslint/ban-ts-comment */
// Single query to get all projects that need any kind of kickout
const getProjectsNeedingKickouts = async (
knex: Knex
): Promise<
Array<{
id: string;
defaultProduct: string;
needsSecretManager: boolean;
needsCertManager: boolean;
needsSecretScanning: boolean;
needsKms: boolean;
needsSsh: boolean;
}>
> => {
const result = await knex.raw(
`
SELECT DISTINCT
p.id,
p."defaultProduct",
-- Use CASE with direct joins instead of EXISTS subqueries
CASE WHEN p."defaultProduct" != 'secret-manager' AND s.secret_exists IS NOT NULL THEN true ELSE false END AS "needsSecretManager",
CASE WHEN p."defaultProduct" != 'cert-manager' AND ca.ca_exists IS NOT NULL THEN true ELSE false END AS "needsCertManager",
CASE WHEN p."defaultProduct" != 'secret-scanning' AND ssds.ssds_exists IS NOT NULL THEN true ELSE false END AS "needsSecretScanning",
CASE WHEN p."defaultProduct" != 'kms' AND kk.kms_exists IS NOT NULL THEN true ELSE false END AS "needsKms",
CASE WHEN p."defaultProduct" != 'ssh' AND sc.ssh_exists IS NOT NULL THEN true ELSE false END AS "needsSsh"
FROM projects p
LEFT JOIN (
SELECT DISTINCT e."projectId", 1 as secret_exists
FROM secrets_v2 s
JOIN secret_folders sf ON sf.id = s."folderId"
JOIN project_environments e ON e.id = sf."envId"
) s ON s."projectId" = p.id AND p."defaultProduct" != 'secret-manager'
LEFT JOIN (
SELECT DISTINCT "projectId", 1 as ca_exists
FROM certificate_authorities
) ca ON ca."projectId" = p.id AND p."defaultProduct" != 'cert-manager'
LEFT JOIN (
SELECT DISTINCT "projectId", 1 as ssds_exists
FROM secret_scanning_data_sources
) ssds ON ssds."projectId" = p.id AND p."defaultProduct" != 'secret-scanning'
LEFT JOIN (
SELECT DISTINCT "projectId", 1 as kms_exists
FROM kms_keys
WHERE "isReserved" = false
) kk ON kk."projectId" = p.id AND p."defaultProduct" != 'kms'
LEFT JOIN (
SELECT DISTINCT sca."projectId", 1 as ssh_exists
FROM ssh_certificates sc
JOIN ssh_certificate_authorities sca ON sca.id = sc."sshCaId"
) sc ON sc."projectId" = p.id AND p."defaultProduct" != 'ssh'
WHERE p."defaultProduct" IS NOT NULL
AND (
(p."defaultProduct" != 'secret-manager' AND s.secret_exists IS NOT NULL) OR
(p."defaultProduct" != 'cert-manager' AND ca.ca_exists IS NOT NULL) OR
(p."defaultProduct" != 'secret-scanning' AND ssds.ssds_exists IS NOT NULL) OR
(p."defaultProduct" != 'kms' AND kk.kms_exists IS NOT NULL) OR
(p."defaultProduct" != 'ssh' AND sc.ssh_exists IS NOT NULL)
)
`
);
return result.rows;
};
const newProject = async (knex: Knex, projectId: string, projectType: ProjectType) => {
const newProjectId = uuidV4();
const project = await knex(TableName.Project).where("id", projectId).first();
await knex(TableName.Project).insert({
...project,
type: projectType,
defaultProduct: null,
// @ts-ignore id is required
id: newProjectId,
slug: slugify(`${project?.name}-${alphaNumericNanoId(8)}`)
});
const customRoleMapping: Record<string, string> = {};
const projectCustomRoles = await knex(TableName.ProjectRoles).where("projectId", projectId);
if (projectCustomRoles.length) {
await knex.batchInsert(
TableName.ProjectRoles,
projectCustomRoles.map((el) => {
const id = uuidV4();
customRoleMapping[el.id] = id;
return {
...el,
id,
projectId: newProjectId,
permissions: el.permissions ? JSON.stringify(el.permissions) : el.permissions
};
})
);
}
const groupMembershipMapping: Record<string, string> = {};
const groupMemberships = await knex(TableName.GroupProjectMembership).where("projectId", projectId);
if (groupMemberships.length) {
await knex.batchInsert(
TableName.GroupProjectMembership,
groupMemberships.map((el) => {
const id = uuidV4();
groupMembershipMapping[el.id] = id;
return { ...el, id, projectId: newProjectId };
})
);
}
const groupMembershipRoles = await knex(TableName.GroupProjectMembershipRole).whereIn(
"projectMembershipId",
groupMemberships.map((el) => el.id)
);
if (groupMembershipRoles.length) {
await knex.batchInsert(
TableName.GroupProjectMembershipRole,
groupMembershipRoles.map((el) => {
const id = uuidV4();
const projectMembershipId = groupMembershipMapping[el.projectMembershipId];
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return { ...el, id, projectMembershipId, customRoleId };
})
);
}
const identityProjectMembershipMapping: Record<string, string> = {};
const identities = await knex(TableName.IdentityProjectMembership).where("projectId", projectId);
if (identities.length) {
await knex.batchInsert(
TableName.IdentityProjectMembership,
identities.map((el) => {
const id = uuidV4();
identityProjectMembershipMapping[el.id] = id;
return { ...el, id, projectId: newProjectId };
})
);
}
const identitiesRoles = await knex(TableName.IdentityProjectMembershipRole).whereIn(
"projectMembershipId",
identities.map((el) => el.id)
);
if (identitiesRoles.length) {
await knex.batchInsert(
TableName.IdentityProjectMembershipRole,
identitiesRoles.map((el) => {
const id = uuidV4();
const projectMembershipId = identityProjectMembershipMapping[el.projectMembershipId];
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return { ...el, id, projectMembershipId, customRoleId };
})
);
}
const projectMembershipMapping: Record<string, string> = {};
const projectUserMembers = await knex(TableName.ProjectMembership).where("projectId", projectId);
if (projectUserMembers.length) {
await knex.batchInsert(
TableName.ProjectMembership,
projectUserMembers.map((el) => {
const id = uuidV4();
projectMembershipMapping[el.id] = id;
return { ...el, id, projectId: newProjectId };
})
);
}
const membershipRoles = await knex(TableName.ProjectUserMembershipRole).whereIn(
"projectMembershipId",
projectUserMembers.map((el) => el.id)
);
if (membershipRoles.length) {
await knex.batchInsert(
TableName.ProjectUserMembershipRole,
membershipRoles.map((el) => {
const id = uuidV4();
const projectMembershipId = projectMembershipMapping[el.projectMembershipId];
const customRoleId = el.customRoleId ? customRoleMapping[el.customRoleId] : el.customRoleId;
return { ...el, id, projectMembershipId, customRoleId };
})
);
}
const kmsKeys = await knex(TableName.KmsKey).where("projectId", projectId).andWhere("isReserved", true);
if (kmsKeys.length) {
await knex.batchInsert(
TableName.KmsKey,
kmsKeys.map((el) => {
const id = uuidV4();
const slug = slugify(alphaNumericNanoId(8).toLowerCase());
return { ...el, id, slug, projectId: newProjectId };
})
);
}
const projectBot = await knex(TableName.ProjectBot).where("projectId", projectId).first();
if (projectBot) {
const newProjectBot = { ...projectBot, id: uuidV4(), projectId: newProjectId };
await knex(TableName.ProjectBot).insert(newProjectBot);
}
const projectKeys = await knex(TableName.ProjectKeys).where("projectId", projectId);
if (projectKeys.length) {
await knex.batchInsert(
TableName.ProjectKeys,
projectKeys.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const projectGateways = await knex(TableName.ProjectGateway).where("projectId", projectId);
if (projectGateways.length) {
await knex.batchInsert(
TableName.ProjectGateway,
projectGateways.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const projectSlackConfigs = await knex(TableName.ProjectSlackConfigs).where("projectId", projectId);
if (projectSlackConfigs.length) {
await knex.batchInsert(
TableName.ProjectSlackConfigs,
projectSlackConfigs.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const projectMicrosoftTeamsConfigs = await knex(TableName.ProjectMicrosoftTeamsConfigs).where("projectId", projectId);
if (projectMicrosoftTeamsConfigs.length) {
await knex.batchInsert(
TableName.ProjectMicrosoftTeamsConfigs,
projectMicrosoftTeamsConfigs.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
const trustedIps = await knex(TableName.TrustedIps).where("projectId", projectId);
if (trustedIps.length) {
await knex.batchInsert(
TableName.TrustedIps,
trustedIps.map((el) => {
const id = uuidV4();
return { ...el, id, projectId: newProjectId };
})
);
}
return newProjectId;
};
const kickOutSecretManagerProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretManager);
await knex(TableName.IntegrationAuth).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.Environment).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretBlindIndex).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretSync).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretTag).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretReminderRecipients).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.ServiceToken).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutCertManagerProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.CertificateManager);
await knex(TableName.CertificateAuthority).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.Certificate).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.PkiSubscriber).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.PkiCollection).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.PkiAlert).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutSecretScanningProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SecretScanning);
await knex(TableName.SecretScanningConfig).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretScanningDataSource).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SecretScanningFinding).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutKmsProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.KMS);
await knex(TableName.KmsKey)
.where("projectId", oldProjectId)
.andWhere("isReserved", false)
.update("projectId", newProjectId);
await knex(TableName.KmipClient).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const kickOutSshProject = async (knex: Knex, oldProjectId: string) => {
const newProjectId = await newProject(knex, oldProjectId, ProjectType.SSH);
await knex(TableName.SshHost).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.ProjectSshConfig).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SshCertificateAuthority).where("projectId", oldProjectId).update("projectId", newProjectId);
await knex(TableName.SshHostGroup).where("projectId", oldProjectId).update("projectId", newProjectId);
};
const BATCH_SIZE = 1000;
const MIGRATION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
export async function up(knex: Knex): Promise<void> {
const result = await knex.raw("SHOW statement_timeout");
const originalTimeout = result.rows[0].statement_timeout;
try {
await knex.raw(`SET statement_timeout = ${MIGRATION_TIMEOUT}`);
const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type");
if (hasTemplateTypeColumn) {
await knex(TableName.ProjectTemplates).whereNull("type").update({
type: ProjectType.SecretManager
});
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
t.string("type").notNullable().defaultTo(ProjectType.SecretManager).alter();
});
}
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
if (hasTypeColumn && hasDefaultTypeColumn) {
await knex(TableName.Project).update({
// eslint-disable-next-line
// @ts-ignore this is because this field is created later
type: knex.raw(`"defaultProduct"`)
});
await knex.schema.alterTable(TableName.Project, (t) => {
t.string("type").notNullable().alter();
t.string("defaultProduct").nullable().alter();
});
// Get all projects that need kickouts in a single query
const projectsNeedingKickouts = await getProjectsNeedingKickouts(knex);
// Process projects in batches to avoid overwhelming the database
for (let i = 0; i < projectsNeedingKickouts.length; i += projectsNeedingKickouts.length) {
const batch = projectsNeedingKickouts.slice(i, i + BATCH_SIZE);
const processedIds: string[] = [];
for (const project of batch) {
const kickoutPromises: Promise<void>[] = [];
// Only add kickouts that are actually needed (flags are pre-computed)
if (project.needsSecretManager) {
kickoutPromises.push(kickOutSecretManagerProject(knex, project.id));
}
if (project.needsCertManager) {
kickoutPromises.push(kickOutCertManagerProject(knex, project.id));
}
if (project.needsKms) {
kickoutPromises.push(kickOutKmsProject(knex, project.id));
}
if (project.needsSsh) {
kickoutPromises.push(kickOutSshProject(knex, project.id));
}
if (project.needsSecretScanning) {
kickoutPromises.push(kickOutSecretScanningProject(knex, project.id));
}
// Execute all kickouts in parallel and handle any failures gracefully
if (kickoutPromises.length > 0) {
const results = await Promise.allSettled(kickoutPromises);
// Log any failures for debugging
results.forEach((res) => {
if (res.status === "rejected") {
throw new Error(`Migration failed for project ${project.id}: ${res.reason}`);
}
});
}
processedIds.push(project.id);
}
// Clear defaultProduct for the processed batch
if (processedIds.length > 0) {
await knex(TableName.Project).whereIn("id", processedIds).update("defaultProduct", null);
}
}
}
} finally {
await knex.raw(`SET statement_timeout = '${originalTimeout}'`);
}
}
export async function down(knex: Knex): Promise<void> {
const hasTypeColumn = await knex.schema.hasColumn(TableName.Project, "type");
const hasDefaultTypeColumn = await knex.schema.hasColumn(TableName.Project, "defaultProduct");
if (hasTypeColumn && hasDefaultTypeColumn) {
await knex(TableName.Project).update({
// eslint-disable-next-line
// @ts-ignore this is because this field is created later
defaultProduct: knex.raw(`
CASE
WHEN "type" IS NULL OR "type" = '' THEN 'secret-manager'
ELSE "type"
END
`)
});
await knex.schema.alterTable(TableName.Project, (t) => {
t.string("type").nullable().alter();
t.string("defaultProduct").notNullable().alter();
});
}
const hasTemplateTypeColumn = await knex.schema.hasColumn(TableName.ProjectTemplates, "type");
if (hasTemplateTypeColumn) {
await knex.schema.alterTable(TableName.ProjectTemplates, (t) => {
t.string("type").nullable().alter();
});
}
}

View File

@@ -0,0 +1,96 @@
import { Knex } from "knex";
import { selectAllTableCols } from "@app/lib/knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment))) {
await knex.schema.createTable(TableName.AccessApprovalPolicyEnvironment, (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("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment);
t.timestamps(true, true, true);
t.unique(["policyId", "envId"]);
});
await createOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
const existingAccessApprovalPolicies = await knex(TableName.AccessApprovalPolicy)
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
.whereNotNull(`${TableName.AccessApprovalPolicy}.envId`);
const accessApprovalPolicies = existingAccessApprovalPolicies.map(async (policy) => {
await knex(TableName.AccessApprovalPolicyEnvironment).insert({
policyId: policy.id,
envId: policy.envId
});
});
await Promise.all(accessApprovalPolicies);
}
if (!(await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment))) {
await knex.schema.createTable(TableName.SecretApprovalPolicyEnvironment, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.SecretApprovalPolicy).onDelete("CASCADE");
t.uuid("envId").notNullable();
t.foreign("envId").references("id").inTable(TableName.Environment);
t.timestamps(true, true, true);
t.unique(["policyId", "envId"]);
});
await createOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
const existingSecretApprovalPolicies = await knex(TableName.SecretApprovalPolicy)
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
.whereNotNull(`${TableName.SecretApprovalPolicy}.envId`);
const secretApprovalPolicies = existingSecretApprovalPolicies.map(async (policy) => {
await knex(TableName.SecretApprovalPolicyEnvironment).insert({
policyId: policy.id,
envId: policy.envId
});
});
await Promise.all(secretApprovalPolicies);
}
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
// Add the new foreign key constraint with ON DELETE SET NULL
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
});
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
// Add the new foreign key constraint with ON DELETE SET NULL
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("SET NULL");
});
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyEnvironment)) {
await knex.schema.dropTableIfExists(TableName.AccessApprovalPolicyEnvironment);
await dropOnUpdateTrigger(knex, TableName.AccessApprovalPolicyEnvironment);
}
if (await knex.schema.hasTable(TableName.SecretApprovalPolicyEnvironment)) {
await knex.schema.dropTableIfExists(TableName.SecretApprovalPolicyEnvironment);
await dropOnUpdateTrigger(knex, TableName.SecretApprovalPolicyEnvironment);
}
await knex.schema.alterTable(TableName.AccessApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
});
await knex.schema.alterTable(TableName.SecretApprovalPolicy, (t) => {
t.dropForeign(["envId"]);
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
});
}

View File

@@ -0,0 +1,111 @@
/* eslint-disable no-await-in-loop */
import { Knex } from "knex";
import { chunkArray } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { TableName } from "../schemas";
import { TReminders, TRemindersInsert } from "../schemas/reminders";
export async function up(knex: Knex): Promise<void> {
logger.info("Initializing secret reminders migration");
const hasReminderTable = await knex.schema.hasTable(TableName.Reminder);
if (hasReminderTable) {
const secretsWithLatestVersions = await knex(TableName.SecretV2)
.whereNotNull(`${TableName.SecretV2}.reminderRepeatDays`)
.whereRaw(`"${TableName.SecretV2}"."reminderRepeatDays" > 0`)
.innerJoin(TableName.SecretVersionV2, (qb) => {
void qb
.on(`${TableName.SecretVersionV2}.secretId`, "=", `${TableName.SecretV2}.id`)
.andOn(`${TableName.SecretVersionV2}.reminderRepeatDays`, "=", `${TableName.SecretV2}.reminderRepeatDays`);
})
.whereIn([`${TableName.SecretVersionV2}.secretId`, `${TableName.SecretVersionV2}.version`], (qb) => {
void qb
.select(["v2.secretId", knex.raw("MIN(v2.version) as version")])
.from(`${TableName.SecretVersionV2} as v2`)
.innerJoin(`${TableName.SecretV2} as s2`, "v2.secretId", "s2.id")
.whereRaw(`v2."reminderRepeatDays" = s2."reminderRepeatDays"`)
.whereNotNull("v2.reminderRepeatDays")
.whereRaw(`v2."reminderRepeatDays" > 0`)
.groupBy("v2.secretId");
})
// Add LEFT JOIN with Reminder table to check for existing reminders
.leftJoin(TableName.Reminder, `${TableName.Reminder}.secretId`, `${TableName.SecretV2}.id`)
// Only include secrets that don't already have reminders
.whereNull(`${TableName.Reminder}.secretId`)
.select(
knex.ref("id").withSchema(TableName.SecretV2).as("secretId"),
knex.ref("reminderRepeatDays").withSchema(TableName.SecretV2).as("reminderRepeatDays"),
knex.ref("reminderNote").withSchema(TableName.SecretV2).as("reminderNote"),
knex.ref("createdAt").withSchema(TableName.SecretVersionV2).as("createdAt")
);
logger.info(`Found ${secretsWithLatestVersions.length} reminders to migrate`);
const reminderInserts: TRemindersInsert[] = [];
if (secretsWithLatestVersions.length > 0) {
secretsWithLatestVersions.forEach((secret) => {
if (!secret.reminderRepeatDays) return;
const now = new Date();
const createdAt = new Date(secret.createdAt);
let nextReminderDate = new Date(createdAt);
nextReminderDate.setDate(nextReminderDate.getDate() + secret.reminderRepeatDays);
// If the next reminder date is in the past, calculate the proper next occurrence
if (nextReminderDate < now) {
const daysSinceCreation = Math.floor((now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24));
const daysIntoCurrentCycle = daysSinceCreation % secret.reminderRepeatDays;
const daysUntilNextReminder = secret.reminderRepeatDays - daysIntoCurrentCycle;
nextReminderDate = new Date(now);
nextReminderDate.setDate(now.getDate() + daysUntilNextReminder);
}
reminderInserts.push({
secretId: secret.secretId,
message: secret.reminderNote,
repeatDays: secret.reminderRepeatDays,
nextReminderDate
});
});
const commitBatches = chunkArray(reminderInserts, 2000);
for (const commitBatch of commitBatches) {
const insertedReminders = (await knex
.batchInsert(TableName.Reminder, commitBatch)
.returning("*")) as TReminders[];
const insertedReminderSecretIds = insertedReminders.map((reminder) => reminder.secretId).filter(Boolean);
const recipients = await knex(TableName.SecretReminderRecipients)
.whereRaw(`??.?? IN (${insertedReminderSecretIds.map(() => "?").join(",")})`, [
TableName.SecretReminderRecipients,
"secretId",
...insertedReminderSecretIds
])
.select(
knex.ref("userId").withSchema(TableName.SecretReminderRecipients).as("userId"),
knex.ref("secretId").withSchema(TableName.SecretReminderRecipients).as("secretId")
);
const reminderRecipients = recipients.map((recipient) => ({
reminderId: insertedReminders.find((reminder) => reminder.secretId === recipient.secretId)?.id,
userId: recipient.userId
}));
const filteredRecipients = reminderRecipients.filter((recipient) => Boolean(recipient.reminderId));
await knex.batchInsert(TableName.ReminderRecipient, filteredRecipients);
}
logger.info(`Successfully migrated ${reminderInserts.length} secret reminders`);
}
logger.info("Secret reminders migration completed");
} else {
logger.warn("Reminder table does not exist, skipping migration");
}
}
export async function down(): Promise<void> {
logger.info("Rollback not implemented for secret reminders fix migration");
}

View File

@@ -53,7 +53,7 @@ export const getMigrationEnvConfig = async (superAdminDAL: TSuperAdminDALFactory
let envCfg = Object.freeze(parsedEnv.data); let envCfg = Object.freeze(parsedEnv.data);
const fipsEnabled = await crypto.initialize(superAdminDAL); const fipsEnabled = await crypto.initialize(superAdminDAL, envCfg);
// Fix for 128-bit entropy encryption key expansion issue: // Fix for 128-bit entropy encryption key expansion issue:
// In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY. // In FIPS it is not ideal to expand a 128-bit key into 256-bit. We solved this issue in the past by creating the ROOT_ENCRYPTION_KEY.

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 AccessApprovalPoliciesEnvironmentsSchema = z.object({
id: z.string().uuid(),
policyId: z.string().uuid(),
envId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TAccessApprovalPoliciesEnvironments = z.infer<typeof AccessApprovalPoliciesEnvironmentsSchema>;
export type TAccessApprovalPoliciesEnvironmentsInsert = Omit<
z.input<typeof AccessApprovalPoliciesEnvironmentsSchema>,
TImmutableDBKeys
>;
export type TAccessApprovalPoliciesEnvironmentsUpdate = Partial<
Omit<z.input<typeof AccessApprovalPoliciesEnvironmentsSchema>, TImmutableDBKeys>
>;

View File

@@ -100,6 +100,7 @@ export enum TableName {
AccessApprovalPolicyBypasser = "access_approval_policies_bypassers", AccessApprovalPolicyBypasser = "access_approval_policies_bypassers",
AccessApprovalRequest = "access_approval_requests", AccessApprovalRequest = "access_approval_requests",
AccessApprovalRequestReviewer = "access_approval_requests_reviewers", AccessApprovalRequestReviewer = "access_approval_requests_reviewers",
AccessApprovalPolicyEnvironment = "access_approval_policies_environments",
SecretApprovalPolicy = "secret_approval_policies", SecretApprovalPolicy = "secret_approval_policies",
SecretApprovalPolicyApprover = "secret_approval_policies_approvers", SecretApprovalPolicyApprover = "secret_approval_policies_approvers",
SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers", SecretApprovalPolicyBypasser = "secret_approval_policies_bypassers",
@@ -107,6 +108,7 @@ export enum TableName {
SecretApprovalRequestReviewer = "secret_approval_requests_reviewers", SecretApprovalRequestReviewer = "secret_approval_requests_reviewers",
SecretApprovalRequestSecret = "secret_approval_requests_secrets", SecretApprovalRequestSecret = "secret_approval_requests_secrets",
SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags", SecretApprovalRequestSecretTag = "secret_approval_request_secret_tags",
SecretApprovalPolicyEnvironment = "secret_approval_policies_environments",
SecretRotation = "secret_rotations", SecretRotation = "secret_rotations",
SecretRotationOutput = "secret_rotation_outputs", SecretRotationOutput = "secret_rotation_outputs",
SamlConfig = "saml_configs", SamlConfig = "saml_configs",
@@ -160,7 +162,7 @@ export enum TableName {
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings", SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
MicrosoftTeamsIntegrations = "microsoft_teams_integrations", MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs", ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs",
SecretReminderRecipients = "secret_reminder_recipients", SecretReminderRecipients = "secret_reminder_recipients", // TODO(Carlos): Remove this in the future after migrating to the new reminder recipients table
GithubOrgSyncConfig = "github_org_sync_configs", GithubOrgSyncConfig = "github_org_sync_configs",
FolderCommit = "folder_commits", FolderCommit = "folder_commits",
FolderCommitChanges = "folder_commit_changes", FolderCommitChanges = "folder_commit_changes",
@@ -172,7 +174,10 @@ export enum TableName {
SecretScanningResource = "secret_scanning_resources", SecretScanningResource = "secret_scanning_resources",
SecretScanningScan = "secret_scanning_scans", SecretScanningScan = "secret_scanning_scans",
SecretScanningFinding = "secret_scanning_findings", SecretScanningFinding = "secret_scanning_findings",
SecretScanningConfig = "secret_scanning_configs" SecretScanningConfig = "secret_scanning_configs",
// reminders
Reminder = "reminders",
ReminderRecipient = "reminders_recipients"
} }
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId"; export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt" | "commitId";
@@ -267,6 +272,16 @@ export enum ProjectType {
SecretScanning = "secret-scanning" SecretScanning = "secret-scanning"
} }
export enum ActionProjectType {
SecretManager = ProjectType.SecretManager,
CertificateManager = ProjectType.CertificateManager,
KMS = ProjectType.KMS,
SSH = ProjectType.SSH,
SecretScanning = ProjectType.SecretScanning,
// project operations that happen on all types
Any = "any"
}
export enum SortDirection { export enum SortDirection {
ASC = "asc", ASC = "asc",
DESC = "desc" DESC = "desc"

View File

@@ -16,7 +16,7 @@ export const ProjectTemplatesSchema = z.object({
orgId: z.string().uuid(), orgId: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
type: z.string().nullable().optional() type: z.string().default("secret-manager")
}); });
export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>; export type TProjectTemplates = z.infer<typeof ProjectTemplatesSchema>;

View File

@@ -25,12 +25,12 @@ export const ProjectsSchema = z.object({
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(), kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(), kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
description: z.string().nullable().optional(), description: z.string().nullable().optional(),
type: z.string().nullable().optional(), type: z.string(),
enforceCapitalization: z.boolean().default(false), enforceCapitalization: z.boolean().default(false),
hasDeleteProtection: z.boolean().default(false).nullable().optional(), hasDeleteProtection: z.boolean().default(false).nullable().optional(),
secretSharing: z.boolean().default(true), secretSharing: z.boolean().default(true),
showSnapshotsLegacy: z.boolean().default(false), showSnapshotsLegacy: z.boolean().default(false),
defaultProduct: z.string().default("secret-manager") defaultProduct: z.string().nullable().optional()
}); });
export type TProjects = z.infer<typeof ProjectsSchema>; export type TProjects = z.infer<typeof ProjectsSchema>;

View File

@@ -0,0 +1,20 @@
// 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 RemindersRecipientsSchema = z.object({
id: z.string().uuid(),
reminderId: z.string().uuid(),
userId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TRemindersRecipients = z.infer<typeof RemindersRecipientsSchema>;
export type TRemindersRecipientsInsert = Omit<z.input<typeof RemindersRecipientsSchema>, TImmutableDBKeys>;
export type TRemindersRecipientsUpdate = Partial<Omit<z.input<typeof RemindersRecipientsSchema>, TImmutableDBKeys>>;

View File

@@ -0,0 +1,22 @@
// 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 RemindersSchema = z.object({
id: z.string().uuid(),
secretId: z.string().uuid().nullable().optional(),
message: z.string().nullable().optional(),
repeatDays: z.number().nullable().optional(),
nextReminderDate: z.date(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TReminders = z.infer<typeof RemindersSchema>;
export type TRemindersInsert = Omit<z.input<typeof RemindersSchema>, TImmutableDBKeys>;
export type TRemindersUpdate = Partial<Omit<z.input<typeof RemindersSchema>, 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 SecretApprovalPoliciesEnvironmentsSchema = z.object({
id: z.string().uuid(),
policyId: z.string().uuid(),
envId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TSecretApprovalPoliciesEnvironments = z.infer<typeof SecretApprovalPoliciesEnvironmentsSchema>;
export type TSecretApprovalPoliciesEnvironmentsInsert = Omit<
z.input<typeof SecretApprovalPoliciesEnvironmentsSchema>,
TImmutableDBKeys
>;
export type TSecretApprovalPoliciesEnvironmentsUpdate = Partial<
Omit<z.input<typeof SecretApprovalPoliciesEnvironmentsSchema>, TImmutableDBKeys>
>;

View File

@@ -17,52 +17,66 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
rateLimit: writeLimit rateLimit: writeLimit
}, },
schema: { schema: {
body: z.object({ body: z
projectSlug: z.string().trim(), .object({
name: z.string().optional(), projectSlug: z.string().trim(),
secretPath: z.string().trim().min(1, { message: "Secret path cannot be empty" }).transform(removeTrailingSlash), name: z.string().optional(),
environment: z.string(), secretPath: z
approvers: z .string()
.discriminatedUnion("type", [ .trim()
z.object({ .min(1, { message: "Secret path cannot be empty" })
type: z.literal(ApproverType.Group), .transform(removeTrailingSlash),
id: z.string(), environment: z.string().optional(),
sequence: z.number().int().default(1) environments: z.string().array().optional(),
}), approvers: z
z.object({ .discriminatedUnion("type", [
type: z.literal(ApproverType.User), z.object({
id: z.string().optional(), type: z.literal(ApproverType.Group),
username: z.string().optional(), id: z.string(),
sequence: z.number().int().default(1) sequence: z.number().int().default(1)
}),
z.object({
type: z.literal(ApproverType.User),
id: z.string().optional(),
username: z.string().optional(),
sequence: z.number().int().default(1)
})
])
.array()
.max(100, "Cannot have more than 100 approvers")
.min(1, { message: "At least one approver should be provided" })
.refine(
// @ts-expect-error this is ok
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
"Must provide either username or id"
),
bypassers: z
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({
type: z.literal(BypasserType.User),
id: z.string().optional(),
username: z.string().optional()
})
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvalsRequired: z
.object({
numberOfApprovals: z.number().int(),
stepNumber: z.number().int()
}) })
]) .array()
.array() .optional(),
.max(100, "Cannot have more than 100 approvers") approvals: z.number().min(1).default(1),
.min(1, { message: "At least one approver should be provided" }) enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
.refine( allowedSelfApprovals: z.boolean().default(true)
// @ts-expect-error this is ok })
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)), .refine(
"Must provide either username or id" (val) => Boolean(val.environment) || Boolean(val.environments),
), "Must provide either environment or environments"
bypassers: z ),
.discriminatedUnion("type", [
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() })
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvalsRequired: z
.object({
numberOfApprovals: z.number().int(),
stepNumber: z.number().int()
})
.array()
.optional(),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
}),
response: { response: {
200: z.object({ 200: z.object({
approval: sapPubSchema approval: sapPubSchema
@@ -78,7 +92,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
...req.body, ...req.body,
projectSlug: req.body.projectSlug, projectSlug: req.body.projectSlug,
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`, name:
req.body.name ?? `${req.body.environment || req.body.environments?.join("-").substring(0, 250)}-${nanoid(3)}`,
enforcementLevel: req.body.enforcementLevel enforcementLevel: req.body.enforcementLevel
}); });
return { approval }; return { approval };
@@ -211,6 +226,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
approvals: z.number().min(1).optional(), approvals: z.number().min(1).optional(),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true), allowedSelfApprovals: z.boolean().default(true),
environments: z.array(z.string()).optional(),
approvalsRequired: z approvalsRequired: z
.object({ .object({
numberOfApprovals: z.number().int(), numberOfApprovals: z.number().int(),

View File

@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { ProjectMembershipRole, ProjectTemplatesSchema } from "@app/db/schemas"; import { ProjectMembershipRole, ProjectTemplatesSchema, ProjectType } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types"; import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns"; import { isInfisicalProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
@@ -104,6 +104,9 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
hide: false, hide: false,
tags: [ApiDocsTags.ProjectTemplates], tags: [ApiDocsTags.ProjectTemplates],
description: "List project templates for the current organization.", description: "List project templates for the current organization.",
querystring: z.object({
type: z.nativeEnum(ProjectType).optional().describe(ProjectTemplates.LIST.type)
}),
response: { response: {
200: z.object({ 200: z.object({
projectTemplates: SanitizedProjectTemplateSchema.array() projectTemplates: SanitizedProjectTemplateSchema.array()
@@ -112,7 +115,10 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(req.permission); const projectTemplates = await server.services.projectTemplate.listProjectTemplatesByOrg(
req.permission,
req.query.type
);
const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name)); const auditTemplates = projectTemplates.filter((template) => !isInfisicalProjectTemplate(template.name));
@@ -191,6 +197,7 @@ export const registerProjectTemplateRouter = async (server: FastifyZodProvider)
.describe(ProjectTemplates.CREATE.name), .describe(ProjectTemplates.CREATE.name),
description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description), description: z.string().max(256).trim().optional().describe(ProjectTemplates.CREATE.description),
roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles), roles: ProjectTemplateRolesSchema.default([]).describe(ProjectTemplates.CREATE.roles),
type: z.nativeEnum(ProjectType).describe(ProjectTemplates.CREATE.type),
environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional() environments: ProjectTemplateEnvironmentsSchema.describe(ProjectTemplates.CREATE.environments).optional()
}), }),
response: { response: {

View File

@@ -17,34 +17,45 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
rateLimit: writeLimit rateLimit: writeLimit
}, },
schema: { schema: {
body: z.object({ body: z
workspaceId: z.string(), .object({
name: z.string().optional(), workspaceId: z.string(),
environment: z.string(), name: z.string().optional(),
secretPath: z environment: z.string().optional(),
.string() environments: z.string().array().optional(),
.min(1, { message: "Secret path cannot be empty" }) secretPath: z
.transform((val) => removeTrailingSlash(val)), .string()
approvers: z .min(1, { message: "Secret path cannot be empty" })
.discriminatedUnion("type", [ .transform((val) => removeTrailingSlash(val)),
z.object({ type: z.literal(ApproverType.Group), id: z.string() }), approvers: z
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() }) .discriminatedUnion("type", [
]) z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
.array() z.object({
.min(1, { message: "At least one approver should be provided" }) type: z.literal(ApproverType.User),
.max(100, "Cannot have more than 100 approvers"), id: z.string().optional(),
bypassers: z username: z.string().optional()
.discriminatedUnion("type", [ })
z.object({ type: z.literal(BypasserType.Group), id: z.string() }), ])
z.object({ type: z.literal(BypasserType.User), id: z.string().optional(), username: z.string().optional() }) .array()
]) .min(1, { message: "At least one approver should be provided" })
.array() .max(100, "Cannot have more than 100 approvers"),
.max(100, "Cannot have more than 100 bypassers") bypassers: z
.optional(), .discriminatedUnion("type", [
approvals: z.number().min(1).default(1), z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard), z.object({
allowedSelfApprovals: z.boolean().default(true) type: z.literal(BypasserType.User),
}), id: z.string().optional(),
username: z.string().optional()
})
])
.array()
.max(100, "Cannot have more than 100 bypassers")
.optional(),
approvals: z.number().min(1).default(1),
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
allowedSelfApprovals: z.boolean().default(true)
})
.refine((data) => data.environment || data.environments, "At least one environment should be provided"),
response: { response: {
200: z.object({ 200: z.object({
approval: sapPubSchema approval: sapPubSchema
@@ -60,7 +71,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
actorOrgId: req.permission.orgId, actorOrgId: req.permission.orgId,
projectId: req.body.workspaceId, projectId: req.body.workspaceId,
...req.body, ...req.body,
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`, name: req.body.name ?? `${req.body.environment || req.body.environments?.join(",")}-${nanoid(3)}`,
enforcementLevel: req.body.enforcementLevel enforcementLevel: req.body.enforcementLevel
}); });
return { approval }; return { approval };
@@ -103,7 +114,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
.optional() .optional()
.transform((val) => (val ? removeTrailingSlash(val) : undefined)), .transform((val) => (val ? removeTrailingSlash(val) : undefined)),
enforcementLevel: z.nativeEnum(EnforcementLevel).optional(), enforcementLevel: z.nativeEnum(EnforcementLevel).optional(),
allowedSelfApprovals: z.boolean().default(true) allowedSelfApprovals: z.boolean().default(true),
environments: z.array(z.string()).optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({

View File

@@ -315,10 +315,12 @@ export const registerSecretRotationEndpoints = <
querystring: z.object({ querystring: z.object({
deleteSecrets: z deleteSecrets: z
.enum(["true", "false"]) .enum(["true", "false"])
.optional()
.transform((value) => value === "true") .transform((value) => value === "true")
.describe(SecretRotations.DELETE(type).deleteSecrets), .describe(SecretRotations.DELETE(type).deleteSecrets),
revokeGeneratedCredentials: z revokeGeneratedCredentials: z
.enum(["true", "false"]) .enum(["true", "false"])
.optional()
.transform((value) => value === "true") .transform((value) => value === "true")
.describe(SecretRotations.DELETE(type).revokeGeneratedCredentials) .describe(SecretRotations.DELETE(type).revokeGeneratedCredentials)
}), }),

View File

@@ -26,6 +26,7 @@ export interface TAccessApprovalPolicyDALFactory
>, >,
customFilter?: { customFilter?: {
policyId?: string; policyId?: string;
envId?: string;
}, },
tx?: Knex tx?: Knex
) => Promise< ) => Promise<
@@ -55,11 +56,6 @@ export interface TAccessApprovalPolicyDALFactory
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath: string; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: {
id: string;
name: string;
slug: string;
};
projectId: string; projectId: string;
bypassers: ( bypassers: (
| { | {
@@ -72,6 +68,11 @@ export interface TAccessApprovalPolicyDALFactory
type: BypasserType.Group; type: BypasserType.Group;
} }
)[]; )[];
environments: {
id: string;
name: string;
slug: string;
}[];
}[] }[]
>; >;
findById: ( findById: (
@@ -95,11 +96,11 @@ export interface TAccessApprovalPolicyDALFactory
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
secretPath: string; secretPath: string;
deletedAt?: Date | null | undefined; deletedAt?: Date | null | undefined;
environment: { environments: {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
}; }[];
projectId: string; projectId: string;
} }
| undefined | undefined
@@ -143,6 +144,26 @@ export interface TAccessApprovalPolicyDALFactory
} }
| undefined | undefined
>; >;
findPolicyByEnvIdAndSecretPath: (
{ envIds, secretPath }: { envIds: string[]; secretPath: string },
tx?: Knex
) => Promise<{
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
approvals: number;
enforcementLevel: string;
allowedSelfApprovals: boolean;
secretPath: string;
deletedAt?: Date | null | undefined;
environments: {
id: string;
name: string;
slug: string;
}[];
projectId: string;
}>;
} }
export interface TAccessApprovalPolicyServiceFactory { export interface TAccessApprovalPolicyServiceFactory {
@@ -367,6 +388,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
customFilter?: { customFilter?: {
policyId?: string; policyId?: string;
envId?: string;
} }
) => { ) => {
const result = await tx(TableName.AccessApprovalPolicy) const result = await tx(TableName.AccessApprovalPolicy)
@@ -377,7 +399,17 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId); void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId);
} }
}) })
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`) .join(
TableName.AccessApprovalPolicyEnvironment,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyEnvironment}.policyId`
)
.join(TableName.Environment, `${TableName.AccessApprovalPolicyEnvironment}.envId`, `${TableName.Environment}.id`)
.where((qb) => {
if (customFilter?.envId) {
void qb.where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", customFilter.envId);
}
})
.leftJoin( .leftJoin(
TableName.AccessApprovalPolicyApprover, TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`, `${TableName.AccessApprovalPolicy}.id`,
@@ -404,7 +436,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser)) .select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName")) .select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug")) .select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId")) .select(tx.ref("id").withSchema(TableName.Environment).as("environmentId"))
.select(tx.ref("projectId").withSchema(TableName.Environment)) .select(tx.ref("projectId").withSchema(TableName.Environment))
.select(selectAllTableCols(TableName.AccessApprovalPolicy)); .select(selectAllTableCols(TableName.AccessApprovalPolicy));
@@ -448,6 +480,15 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
sequence: approverSequence, sequence: approverSequence,
approvalsRequired approvalsRequired
}) })
},
{
key: "environmentId",
label: "environments" as const,
mapper: ({ environmentId: id, envName, envSlug }) => ({
id,
name: envName,
slug: envSlug
})
} }
] ]
}); });
@@ -470,11 +511,6 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
data: docs, data: docs,
key: "id", key: "id",
parentMapper: (data) => ({ parentMapper: (data) => ({
environment: {
id: data.envId,
name: data.envName,
slug: data.envSlug
},
projectId: data.projectId, projectId: data.projectId,
...AccessApprovalPoliciesSchema.parse(data) ...AccessApprovalPoliciesSchema.parse(data)
// secretPath: data.secretPath || undefined, // secretPath: data.secretPath || undefined,
@@ -517,6 +553,15 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
id, id,
type: BypasserType.Group as const type: BypasserType.Group as const
}) })
},
{
key: "environmentId",
label: "environments" as const,
mapper: ({ environmentId: id, envName, envSlug }) => ({
id,
name: envName,
slug: envSlug
})
} }
] ]
}); });
@@ -545,14 +590,20 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
buildFindFilter( buildFindFilter(
{ {
envId,
secretPath secretPath
}, },
TableName.AccessApprovalPolicy TableName.AccessApprovalPolicy
) )
) )
.join(
TableName.AccessApprovalPolicyEnvironment,
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
`${TableName.AccessApprovalPolicy}.id`
)
.where(`${TableName.AccessApprovalPolicyEnvironment}.envId`, "=", envId)
.orderBy("deletedAt", "desc") .orderBy("deletedAt", "desc")
.orderByRaw(`"deletedAt" IS NULL`) .orderByRaw(`"deletedAt" IS NULL`)
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
.first(); .first();
return result; return result;
@@ -561,5 +612,81 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPo
} }
}; };
return { ...accessApprovalPolicyOrm, find, findById, softDeleteById, findLastValidPolicy }; const findPolicyByEnvIdAndSecretPath: TAccessApprovalPolicyDALFactory["findPolicyByEnvIdAndSecretPath"] = async (
{ envIds, secretPath },
tx
) => {
try {
const docs = await (tx || db.replicaNode())(TableName.AccessApprovalPolicy)
.join(
TableName.AccessApprovalPolicyEnvironment,
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
`${TableName.AccessApprovalPolicy}.id`
)
.join(
TableName.Environment,
`${TableName.AccessApprovalPolicyEnvironment}.envId`,
`${TableName.Environment}.id`
)
.where(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
buildFindFilter(
{
$in: {
envId: envIds
}
},
TableName.AccessApprovalPolicyEnvironment
)
)
.where(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
buildFindFilter(
{
secretPath
},
TableName.AccessApprovalPolicy
)
)
.whereNull(`${TableName.AccessApprovalPolicy}.deletedAt`)
.orderBy("deletedAt", "desc")
.orderByRaw(`"deletedAt" IS NULL`)
.select(selectAllTableCols(TableName.AccessApprovalPolicy))
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(db.ref("id").withSchema(TableName.Environment).as("environmentId"))
.select(db.ref("projectId").withSchema(TableName.Environment));
const formattedDocs = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (data) => ({
projectId: data.projectId,
...AccessApprovalPoliciesSchema.parse(data)
}),
childrenMapper: [
{
key: "environmentId",
label: "environments" as const,
mapper: ({ environmentId: id, envName, envSlug }) => ({
id,
name: envName,
slug: envSlug
})
}
]
});
return formattedDocs?.[0];
} catch (error) {
throw new DatabaseError({ error, name: "findPolicyByEnvIdAndSecretPath" });
}
};
return {
...accessApprovalPolicyOrm,
find,
findById,
softDeleteById,
findLastValidPolicy,
findPolicyByEnvIdAndSecretPath
};
}; };

View File

@@ -0,0 +1,32 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
export type TAccessApprovalPolicyEnvironmentDALFactory = ReturnType<typeof accessApprovalPolicyEnvironmentDALFactory>;
export const accessApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => {
const accessApprovalPolicyEnvironmentOrm = ormify(db, TableName.AccessApprovalPolicyEnvironment);
const findAvailablePoliciesByEnvId = async (envId: string, tx?: Knex) => {
try {
const docs = await (tx || db.replicaNode())(TableName.AccessApprovalPolicyEnvironment)
.join(
TableName.AccessApprovalPolicy,
`${TableName.AccessApprovalPolicyEnvironment}.policyId`,
`${TableName.AccessApprovalPolicy}.id`
)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter({ envId }, TableName.AccessApprovalPolicyEnvironment))
.whereNull(`${TableName.AccessApprovalPolicy}.deletedAt`)
.select(selectAllTableCols(TableName.AccessApprovalPolicyEnvironment));
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "findAvailablePoliciesByEnvId" });
}
};
return { ...accessApprovalPolicyEnvironmentOrm, findAvailablePoliciesByEnvId };
};

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -20,6 +21,7 @@ import {
TAccessApprovalPolicyBypasserDALFactory TAccessApprovalPolicyBypasserDALFactory
} from "./access-approval-policy-approver-dal"; } from "./access-approval-policy-approver-dal";
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal"; import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
import { TAccessApprovalPolicyEnvironmentDALFactory } from "./access-approval-policy-environment-dal";
import { import {
ApproverType, ApproverType,
BypasserType, BypasserType,
@@ -44,12 +46,14 @@ type TAccessApprovalPolicyServiceFactoryDep = {
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">; additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">; accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">; orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
accessApprovalPolicyEnvironmentDAL: TAccessApprovalPolicyEnvironmentDALFactory;
}; };
export const accessApprovalPolicyServiceFactory = ({ export const accessApprovalPolicyServiceFactory = ({
accessApprovalPolicyDAL, accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL, accessApprovalPolicyApproverDAL,
accessApprovalPolicyBypasserDAL, accessApprovalPolicyBypasserDAL,
accessApprovalPolicyEnvironmentDAL,
groupDAL, groupDAL,
permissionService, permissionService,
projectEnvDAL, projectEnvDAL,
@@ -62,21 +66,22 @@ export const accessApprovalPolicyServiceFactory = ({
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => { }: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
const $policyExists = async ({ const $policyExists = async ({
envId, envId,
envIds,
secretPath, secretPath,
policyId policyId
}: { }: {
envId: string; envId?: string;
envIds?: string[];
secretPath: string; secretPath: string;
policyId?: string; policyId?: string;
}) => { }) => {
const policy = await accessApprovalPolicyDAL if (!envId && !envIds) {
.findOne({ throw new BadRequestError({ message: "Must provide either envId or envIds" });
envId, }
secretPath, const policy = await accessApprovalPolicyDAL.findPolicyByEnvIdAndSecretPath({
deletedAt: null secretPath,
}) envIds: envId ? [envId] : (envIds as string[])
.catch(() => null); });
return policyId ? policy && policy.id !== policyId : Boolean(policy); return policyId ? policy && policy.id !== policyId : Boolean(policy);
}; };
@@ -92,6 +97,7 @@ export const accessApprovalPolicyServiceFactory = ({
bypassers, bypassers,
projectSlug, projectSlug,
environment, environment,
environments,
enforcementLevel, enforcementLevel,
allowedSelfApprovals, allowedSelfApprovals,
approvalsRequired approvalsRequired
@@ -116,20 +122,31 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
ProjectPermissionSub.SecretApproval ProjectPermissionSub.SecretApproval
); );
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id }); const mergedEnvs = (environment ? [environment] : environments) || [];
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` }); if (mergedEnvs.length === 0) {
throw new BadRequestError({ message: "Must provide either environment or environments" });
}
const envs = await projectEnvDAL.find({ $in: { slug: mergedEnvs }, projectId: project.id });
if (!envs.length || envs.length !== mergedEnvs.length) {
const notFoundEnvs = mergedEnvs.filter((env) => !envs.find((el) => el.slug === env));
throw new NotFoundError({ message: `One or more environments not found: ${notFoundEnvs.join(", ")}` });
}
if (await $policyExists({ envId: env.id, secretPath })) { for (const env of envs) {
throw new BadRequestError({ // eslint-disable-next-line no-await-in-loop
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'` if (await $policyExists({ envId: env.id, secretPath })) {
}); throw new BadRequestError({
message: `A policy for secret path '${secretPath}' already exists in environment '${env.slug}'`
});
}
} }
let approverUserIds = userApprovers; let approverUserIds = userApprovers;
@@ -197,7 +214,7 @@ export const accessApprovalPolicyServiceFactory = ({
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => { const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
const doc = await accessApprovalPolicyDAL.create( const doc = await accessApprovalPolicyDAL.create(
{ {
envId: env.id, envId: envs[0].id,
approvals, approvals,
secretPath, secretPath,
name, name,
@@ -206,6 +223,10 @@ export const accessApprovalPolicyServiceFactory = ({
}, },
tx tx
); );
await accessApprovalPolicyEnvironmentDAL.insertMany(
envs.map((el) => ({ policyId: doc.id, envId: el.id })),
tx
);
if (approverUserIds.length) { if (approverUserIds.length) {
await accessApprovalPolicyApproverDAL.insertMany( await accessApprovalPolicyApproverDAL.insertMany(
@@ -258,7 +279,7 @@ export const accessApprovalPolicyServiceFactory = ({
return doc; return doc;
}); });
return { ...accessApproval, environment: env, projectId: project.id }; return { ...accessApproval, environments: envs, projectId: project.id, environment: envs[0] };
}; };
const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] = const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] =
@@ -272,11 +293,15 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null }); const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
return accessApprovalPolicies; return accessApprovalPolicies.map((policy) => ({
...policy,
environment: policy.environments[0]
}));
}; };
const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({ const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({
@@ -292,7 +317,8 @@ export const accessApprovalPolicyServiceFactory = ({
approvals, approvals,
enforcementLevel, enforcementLevel,
allowedSelfApprovals, allowedSelfApprovals,
approvalsRequired approvalsRequired,
environments
}: TUpdateAccessApprovalPolicy) => { }: TUpdateAccessApprovalPolicy) => {
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group); const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
@@ -320,16 +346,27 @@ export const accessApprovalPolicyServiceFactory = ({
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
} }
let envs = accessApprovalPolicy.environments;
if ( if (
await $policyExists({ environments &&
envId: accessApprovalPolicy.envId, (environments.length !== envs.length || environments.some((env) => !envs.find((el) => el.slug === env)))
secretPath: secretPath || accessApprovalPolicy.secretPath,
policyId: accessApprovalPolicy.id
})
) { ) {
throw new BadRequestError({ envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: accessApprovalPolicy.projectId });
message: `A policy for secret path '${secretPath}' already exists in environment '${accessApprovalPolicy.environment.slug}'` }
});
for (const env of envs) {
if (
// eslint-disable-next-line no-await-in-loop
await $policyExists({
envId: env.id,
secretPath: secretPath || accessApprovalPolicy.secretPath,
policyId: accessApprovalPolicy.id
})
) {
throw new BadRequestError({
message: `A policy for secret path '${secretPath || accessApprovalPolicy.secretPath}' already exists in environment '${env.slug}'`
});
}
} }
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
@@ -337,7 +374,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: accessApprovalPolicy.projectId, projectId: accessApprovalPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@@ -484,6 +522,14 @@ export const accessApprovalPolicyServiceFactory = ({
); );
} }
if (environments) {
await accessApprovalPolicyEnvironmentDAL.delete({ policyId: doc.id }, tx);
await accessApprovalPolicyEnvironmentDAL.insertMany(
envs.map((env) => ({ policyId: doc.id, envId: env.id })),
tx
);
}
await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx); await accessApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
if (bypasserUserIds.length) { if (bypasserUserIds.length) {
@@ -513,7 +559,8 @@ export const accessApprovalPolicyServiceFactory = ({
return { return {
...updatedPolicy, ...updatedPolicy,
environment: accessApprovalPolicy.environment, environments: accessApprovalPolicy.environments,
environment: accessApprovalPolicy.environments[0],
projectId: accessApprovalPolicy.projectId projectId: accessApprovalPolicy.projectId
}; };
}; };
@@ -533,7 +580,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: policy.projectId, projectId: policy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
@@ -563,7 +611,10 @@ export const accessApprovalPolicyServiceFactory = ({
} }
}); });
return policy; return {
...policy,
environment: policy.environments[0]
};
}; };
const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({ const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({
@@ -583,7 +634,8 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@@ -592,11 +644,13 @@ export const accessApprovalPolicyServiceFactory = ({
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug }); const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` }); if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
const policies = await accessApprovalPolicyDAL.find({ const policies = await accessApprovalPolicyDAL.find(
envId: environment.id, {
projectId: project.id, projectId: project.id,
deletedAt: null deletedAt: null
}); },
{ envId: environment.id }
);
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` }); if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
return { count: policies.length }; return { count: policies.length };
@@ -622,12 +676,16 @@ export const accessApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: policy.projectId, projectId: policy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
return policy; return {
...policy,
environment: policy.environments[0]
};
}; };
return { return {

View File

@@ -26,7 +26,8 @@ export enum BypasserType {
export type TCreateAccessApprovalPolicy = { export type TCreateAccessApprovalPolicy = {
approvals: number; approvals: number;
secretPath: string; secretPath: string;
environment: string; environment?: string;
environments?: string[];
approvers: ( approvers: (
| { type: ApproverType.Group; id: string; sequence?: number } | { type: ApproverType.Group; id: string; sequence?: number }
| { type: ApproverType.User; id?: string; username?: string; sequence?: number } | { type: ApproverType.User; id?: string; username?: string; sequence?: number }
@@ -58,6 +59,7 @@ export type TUpdateAccessApprovalPolicy = {
enforcementLevel?: EnforcementLevel; enforcementLevel?: EnforcementLevel;
allowedSelfApprovals: boolean; allowedSelfApprovals: boolean;
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[]; approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
environments?: string[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TDeleteAccessApprovalPolicy = { export type TDeleteAccessApprovalPolicy = {
@@ -113,6 +115,15 @@ export interface TAccessApprovalPolicyServiceFactory {
slug: string; slug: string;
position: number; position: number;
}; };
environments: {
name: string;
id: string;
createdAt: Date;
updatedAt: Date;
projectId: string;
slug: string;
position: number;
}[];
projectId: string; projectId: string;
name: string; name: string;
id: string; id: string;
@@ -153,6 +164,11 @@ export interface TAccessApprovalPolicyServiceFactory {
name: string; name: string;
slug: string; slug: string;
}; };
environments: {
id: string;
name: string;
slug: string;
}[];
projectId: string; projectId: string;
}>; }>;
updateAccessApprovalPolicy: ({ updateAccessApprovalPolicy: ({
@@ -168,13 +184,19 @@ export interface TAccessApprovalPolicyServiceFactory {
approvals, approvals,
enforcementLevel, enforcementLevel,
allowedSelfApprovals, allowedSelfApprovals,
approvalsRequired approvalsRequired,
environments
}: TUpdateAccessApprovalPolicy) => Promise<{ }: TUpdateAccessApprovalPolicy) => Promise<{
environment: { environment: {
id: string; id: string;
name: string; name: string;
slug: string; slug: string;
}; };
environments: {
id: string;
name: string;
slug: string;
}[];
projectId: string; projectId: string;
name: string; name: string;
id: string; id: string;
@@ -225,6 +247,11 @@ export interface TAccessApprovalPolicyServiceFactory {
name: string; name: string;
slug: string; slug: string;
}; };
environments: {
id: string;
name: string;
slug: string;
}[];
projectId: string; projectId: string;
bypassers: ( bypassers: (
| { | {
@@ -276,6 +303,11 @@ export interface TAccessApprovalPolicyServiceFactory {
name: string; name: string;
slug: string; slug: string;
}; };
environments: {
id: string;
name: string;
slug: string;
}[];
projectId: string; projectId: string;
bypassers: ( bypassers: (
| { | {

View File

@@ -65,7 +65,7 @@ export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName
deletedAt: Date | null | undefined; deletedAt: Date | null | undefined;
}; };
projectId: string; projectId: string;
environment: string; environments: string[];
requestedByUser: { requestedByUser: {
userId: string; userId: string;
email: string | null | undefined; email: string | null | undefined;
@@ -515,7 +515,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
`accessApprovalReviewerUser.id` `accessApprovalReviewerUser.id`
) )
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`) .leftJoin(
TableName.AccessApprovalPolicyEnvironment,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyEnvironment}.policyId`
)
.leftJoin(
TableName.Environment,
`${TableName.AccessApprovalPolicyEnvironment}.envId`,
`${TableName.Environment}.id`
)
.select(selectAllTableCols(TableName.AccessApprovalRequest)) .select(selectAllTableCols(TableName.AccessApprovalRequest))
.select( .select(
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover), tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
@@ -683,6 +693,11 @@ export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalR
lastName, lastName,
username username
}) })
},
{
key: "environment",
label: "environments" as const,
mapper: ({ environment }) => environment
} }
] ]
}); });

View File

@@ -1,7 +1,7 @@
import slugify from "@sindresorhus/slugify"; import slugify from "@sindresorhus/slugify";
import msFn from "ms"; import msFn from "ms";
import { ProjectMembershipRole } from "@app/db/schemas"; import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
@@ -86,6 +86,25 @@ export const accessApprovalRequestServiceFactory = ({
projectMicrosoftTeamsConfigDAL, projectMicrosoftTeamsConfigDAL,
projectSlackConfigDAL projectSlackConfigDAL
}: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => { }: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => {
const $getEnvironmentFromPermissions = (permissions: unknown): string | null => {
if (!Array.isArray(permissions) || permissions.length === 0) {
return null;
}
const firstPermission = permissions[0] as unknown[];
if (!Array.isArray(firstPermission) || firstPermission.length < 3) {
return null;
}
const metadata = firstPermission[2] as Record<string, unknown>;
if (typeof metadata === "object" && metadata !== null && "environment" in metadata) {
const env = metadata.environment;
return typeof env === "string" ? env : null;
}
return null;
};
const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({ const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({
isTemporary, isTemporary,
temporaryRange, temporaryRange,
@@ -107,7 +126,8 @@ export const accessApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@@ -216,7 +236,7 @@ export const accessApprovalRequestServiceFactory = ({
); );
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`; const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
const approvalUrl = `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`; const approvalUrl = `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`;
await triggerWorkflowIntegrationNotification({ await triggerWorkflowIntegrationNotification({
input: { input: {
@@ -289,7 +309,8 @@ export const accessApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });
@@ -306,6 +327,15 @@ export const accessApprovalRequestServiceFactory = ({
requests = requests.filter((request) => request.environment === envSlug); requests = requests.filter((request) => request.environment === envSlug);
} }
requests = requests.map((request) => {
const permissionEnvironment = $getEnvironmentFromPermissions(request.permissions);
if (permissionEnvironment) {
request.environmentName = permissionEnvironment;
}
return request;
});
return { requests }; return { requests };
}; };
@@ -323,19 +353,34 @@ export const accessApprovalRequestServiceFactory = ({
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` }); throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
} }
const { policy, environment } = accessApprovalRequest; const { policy, environments, permissions } = accessApprovalRequest;
if (policy.deletedAt) { if (policy.deletedAt) {
throw new BadRequestError({ throw new BadRequestError({
message: "The policy associated with this access request has been deleted." message: "The policy associated with this access request has been deleted."
}); });
} }
const permissionEnvironment = $getEnvironmentFromPermissions(permissions);
if (
!permissionEnvironment ||
(!environments.includes(permissionEnvironment) && status === ApprovalStatus.APPROVED)
) {
throw new BadRequestError({
message: `The original policy ${policy.name} is not attached to environment '${permissionEnvironment}'.`
});
}
const environment = await projectEnvDAL.findOne({
projectId: accessApprovalRequest.projectId,
slug: permissionEnvironment
});
const { membership, hasRole } = await permissionService.getProjectPermission({ const { membership, hasRole } = await permissionService.getProjectPermission({
actor, actor,
actorId, actorId,
projectId: accessApprovalRequest.projectId, projectId: accessApprovalRequest.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
@@ -550,8 +595,8 @@ export const accessApprovalRequestServiceFactory = ({
requesterEmail: actingUser.email, requesterEmail: actingUser.email,
bypassReason: bypassReason || "No reason provided", bypassReason: bypassReason || "No reason provided",
secretPath: policy.secretPath || "/", secretPath: policy.secretPath || "/",
environment, environment: environment?.name || permissionEnvironment,
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval`, approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`,
requestType: "access" requestType: "access"
}, },
template: SmtpTemplates.AccessSecretRequestBypassed template: SmtpTemplates.AccessSecretRequestBypassed
@@ -582,7 +627,8 @@ export const accessApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if (!membership) { if (!membership) {
throw new ForbiddenRequestError({ message: "You are not a member of this project" }); throw new ForbiddenRequestError({ message: "You are not a member of this project" });

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
@@ -37,7 +38,8 @@ export const assumePrivilegeServiceFactory = ({
actorId: actorPermissionDetails.id, actorId: actorPermissionDetails.id,
projectId, projectId,
actorAuthMethod: actorPermissionDetails.authMethod, actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
}); });
if (targetActorType === ActorType.USER) { if (targetActorType === ActorType.USER) {
@@ -58,7 +60,8 @@ export const assumePrivilegeServiceFactory = ({
actorId: targetActorId, actorId: targetActorId,
projectId, projectId,
actorAuthMethod: actorPermissionDetails.authMethod, actorAuthMethod: actorPermissionDetails.authMethod,
actorOrgId: actorPermissionDetails.orgId actorOrgId: actorPermissionDetails.orgId,
actionProjectType: ActionProjectType.Any
}); });
const appCfg = getConfig(); const appCfg = getConfig();

View File

@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { requestContext } from "@fastify/request-context"; import { requestContext } from "@fastify/request-context";
import { ActionProjectType } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ActorType } from "@app/services/auth/auth-type"; import { ActorType } from "@app/services/auth/auth-type";
@@ -37,7 +38,8 @@ export const auditLogServiceFactory = ({
actorId, actorId,
projectId: filter.projectId, projectId: filter.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
} else { } else {

View File

@@ -468,7 +468,11 @@ export enum EventType {
CREATE_PROJECT = "create-project", CREATE_PROJECT = "create-project",
UPDATE_PROJECT = "update-project", UPDATE_PROJECT = "update-project",
DELETE_PROJECT = "delete-project" DELETE_PROJECT = "delete-project",
CREATE_SECRET_REMINDER = "create-secret-reminder",
GET_SECRET_REMINDER = "get-secret-reminder",
DELETE_SECRET_REMINDER = "delete-secret-reminder"
} }
export const filterableSecretEvents: EventType[] = [ export const filterableSecretEvents: EventType[] = [
@@ -3326,6 +3330,31 @@ interface SecretScanningConfigUpdateEvent {
}; };
} }
interface SecretReminderCreateEvent {
type: EventType.CREATE_SECRET_REMINDER;
metadata: {
secretId: string;
message?: string | null;
repeatDays?: number | null;
nextReminderDate?: string | null;
recipients?: string[] | null;
};
}
interface SecretReminderGetEvent {
type: EventType.GET_SECRET_REMINDER;
metadata: {
secretId: string;
};
}
interface SecretReminderDeleteEvent {
type: EventType.DELETE_SECRET_REMINDER;
metadata: {
secretId: string;
};
}
interface SecretScanningConfigReadEvent { interface SecretScanningConfigReadEvent {
type: EventType.SECRET_SCANNING_CONFIG_GET; type: EventType.SECRET_SCANNING_CONFIG_GET;
metadata?: Record<string, never>; // not needed, based off projectId metadata?: Record<string, never>; // not needed, based off projectId
@@ -3689,4 +3718,7 @@ export type Event =
| OrgUpdateEvent | OrgUpdateEvent
| ProjectCreateEvent | ProjectCreateEvent
| ProjectUpdateEvent | ProjectUpdateEvent
| ProjectDeleteEvent; | ProjectDeleteEvent
| SecretReminderCreateEvent
| SecretReminderGetEvent
| SecretReminderDeleteEvent;

View File

@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@@ -77,7 +78,8 @@ export const certificateAuthorityCrlServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(

View File

@@ -1,6 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import RE2 from "re2"; import RE2 from "re2";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -84,7 +85,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -200,7 +202,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
@@ -297,7 +300,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
@@ -385,7 +389,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -432,7 +437,8 @@ export const dynamicSecretLeaseServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);

View File

@@ -1,5 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -78,7 +79,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -207,7 +209,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const plan = await licenseService.getPlan(actorOrgId); const plan = await licenseService.getPlan(actorOrgId);
@@ -358,7 +361,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -423,7 +427,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -487,7 +492,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
// verify user has access to each env in request // verify user has access to each env in request
@@ -530,7 +536,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionDynamicSecretActions.ReadRootCredential,
@@ -578,7 +585,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path); const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
@@ -615,7 +623,8 @@ export const dynamicSecretServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId, projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) => const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
@@ -659,7 +668,8 @@ export const dynamicSecretServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path); const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);

View File

@@ -566,6 +566,14 @@ export const gatewayServiceFactory = ({
if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` }); if (!gateway) throw new NotFoundError({ message: `Gateway with ID ${gatewayId} not found.` });
const orgGatewayConfig = await orgGatewayConfigDAL.findById(gateway.orgGatewayRootCaId); const orgGatewayConfig = await orgGatewayConfigDAL.findById(gateway.orgGatewayRootCaId);
const orgLicensePlan = await licenseService.getPlan(orgGatewayConfig.orgId);
if (!orgLicensePlan.gateway) {
throw new BadRequestError({
message: "Please upgrade your instance to Infisical's Enterprise plan to use gateways."
});
}
const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({ const { decryptor: orgKmsDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization, type: KmsDataKey.Organization,
orgId: orgGatewayConfig.orgId orgId: orgGatewayConfig.orgId

View File

@@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import { TableName } from "@app/db/schemas"; import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -61,7 +61,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -72,7 +73,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId: identityId, actorId: identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -158,7 +160,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -169,7 +172,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -256,7 +260,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -267,7 +272,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem, membership.shouldUseNewPrivilegeSystem,
@@ -315,7 +321,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,
@@ -349,7 +356,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,
@@ -384,7 +392,8 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,

View File

@@ -1,6 +1,7 @@
import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"; import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -72,7 +73,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -85,7 +87,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId: identityId, actorId: identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -172,7 +175,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -185,7 +189,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -288,7 +293,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Edit, ProjectPermissionIdentityActions.Edit,
@@ -300,7 +306,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId: identityProjectMembership.identityId, actorId: identityProjectMembership.identityId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
const permissionBoundary = validatePrivilegeChangeOperation( const permissionBoundary = validatePrivilegeChangeOperation(
membership.shouldUseNewPrivilegeSystem, membership.shouldUseNewPrivilegeSystem,
@@ -359,7 +366,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionIdentityActions.Read, ProjectPermissionIdentityActions.Read,
@@ -401,7 +409,8 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: identityProjectMembership.projectId, projectId: identityProjectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(

View File

@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import { ActionProjectType } from "@app/db/schemas";
import { crypto } from "@app/lib/crypto/cryptography"; import { crypto } from "@app/lib/crypto/cryptography";
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { isValidIp } from "@app/lib/ip"; import { isValidIp } from "@app/lib/ip";
@@ -78,7 +79,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -131,7 +133,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -162,7 +165,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -195,7 +199,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
@@ -216,7 +221,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
@@ -252,7 +258,8 @@ export const kmipServiceFactory = ({
actorId, actorId,
projectId: kmipClient.projectId, projectId: kmipClient.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.KMS
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(

View File

@@ -1,6 +1,7 @@
import { MongoAbility, RawRuleOf } from "@casl/ability"; import { MongoAbility, RawRuleOf } from "@casl/ability";
import { MongoQuery } from "@ucast/mongo2js"; import { MongoQuery } from "@ucast/mongo2js";
import { ActionProjectType } from "@app/db/schemas";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type"; import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { OrgPermissionSet } from "./org-permission"; import { OrgPermissionSet } from "./org-permission";
@@ -20,6 +21,7 @@ export type TGetUserProjectPermissionArg = {
userId: string; userId: string;
projectId: string; projectId: string;
authMethod: ActorAuthMethod; authMethod: ActorAuthMethod;
actionProjectType: ActionProjectType;
userOrgId?: string; userOrgId?: string;
}; };
@@ -27,12 +29,14 @@ export type TGetIdentityProjectPermissionArg = {
identityId: string; identityId: string;
projectId: string; projectId: string;
identityOrgId?: string; identityOrgId?: string;
actionProjectType: ActionProjectType;
}; };
export type TGetServiceTokenProjectPermissionArg = { export type TGetServiceTokenProjectPermissionArg = {
serviceTokenId: string; serviceTokenId: string;
projectId: string; projectId: string;
actorOrgId?: string; actorOrgId?: string;
actionProjectType: ActionProjectType;
}; };
export type TGetProjectPermissionArg = { export type TGetProjectPermissionArg = {
@@ -41,6 +45,7 @@ export type TGetProjectPermissionArg = {
projectId: string; projectId: string;
actorAuthMethod: ActorAuthMethod; actorAuthMethod: ActorAuthMethod;
actorOrgId?: string; actorOrgId?: string;
actionProjectType: ActionProjectType;
}; };
export type TPermissionServiceFactory = { export type TPermissionServiceFactory = {
@@ -138,7 +143,13 @@ export type TPermissionServiceFactory = {
}; };
} }
>; >;
getUserProjectPermission: ({ userId, projectId, authMethod, userOrgId }: TGetUserProjectPermissionArg) => Promise<{ getUserProjectPermission: ({
userId,
projectId,
authMethod,
userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg) => Promise<{
permission: MongoAbility<ProjectPermissionSet, MongoQuery>; permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
membership: { membership: {
id: string; id: string;

View File

@@ -5,6 +5,7 @@ import { MongoQuery } from "@ucast/mongo2js";
import handlebars from "handlebars"; import handlebars from "handlebars";
import { import {
ActionProjectType,
OrgMembershipRole, OrgMembershipRole,
ProjectMembershipRole, ProjectMembershipRole,
ServiceTokenScopes, ServiceTokenScopes,
@@ -213,7 +214,8 @@ export const permissionServiceFactory = ({
userId, userId,
projectId, projectId,
authMethod, authMethod,
userOrgId userOrgId,
actionProjectType
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => { }: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId); const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" }); if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
@@ -240,6 +242,12 @@ export const permissionServiceFactory = ({
userProjectPermission.orgRole userProjectPermission.orgRole
); );
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
// join two permissions and pass to build the final permission set // join two permissions and pass to build the final permission set
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges = const additionalPrivileges =
@@ -287,7 +295,8 @@ export const permissionServiceFactory = ({
const getIdentityProjectPermission = async ({ const getIdentityProjectPermission = async ({
identityId, identityId,
projectId, projectId,
identityOrgId identityOrgId,
actionProjectType
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => { }: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId); const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
if (!identityProjectPermission) if (!identityProjectPermission)
@@ -307,6 +316,12 @@ export const permissionServiceFactory = ({
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" }); throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
} }
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
throw new BadRequestError({
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
});
}
const rolePermissions = const rolePermissions =
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || []; identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
const additionalPrivileges = const additionalPrivileges =
@@ -361,7 +376,8 @@ export const permissionServiceFactory = ({
const getServiceTokenProjectPermission = async ({ const getServiceTokenProjectPermission = async ({
serviceTokenId, serviceTokenId,
projectId, projectId,
actorOrgId actorOrgId,
actionProjectType
}: TGetServiceTokenProjectPermissionArg) => { }: TGetServiceTokenProjectPermissionArg) => {
const serviceToken = await serviceTokenDAL.findById(serviceTokenId); const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` }); if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
@@ -386,6 +402,12 @@ export const permissionServiceFactory = ({
}); });
} }
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
throw new BadRequestError({
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
});
}
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []); const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
return { return {
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions), permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
@@ -537,7 +559,8 @@ export const permissionServiceFactory = ({
actorId: inputActorId, actorId: inputActorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => { }: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
let actor = inputActor; let actor = inputActor;
let actorId = inputActorId; let actorId = inputActorId;
@@ -558,19 +581,22 @@ export const permissionServiceFactory = ({
userId: actorId, userId: actorId,
projectId, projectId,
authMethod: actorAuthMethod, authMethod: actorAuthMethod,
userOrgId: actorOrgId userOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>; }) as Promise<TProjectPermissionRT<T>>;
case ActorType.SERVICE: case ActorType.SERVICE:
return getServiceTokenProjectPermission({ return getServiceTokenProjectPermission({
serviceTokenId: actorId, serviceTokenId: actorId,
projectId, projectId,
actorOrgId actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>; }) as Promise<TProjectPermissionRT<T>>;
case ActorType.IDENTITY: case ActorType.IDENTITY:
return getIdentityProjectPermission({ return getIdentityProjectPermission({
identityId: actorId, identityId: actorId,
projectId, projectId,
identityOrgId: actorOrgId identityOrgId: actorOrgId,
actionProjectType
}) as Promise<TProjectPermissionRT<T>>; }) as Promise<TProjectPermissionRT<T>>;
default: default:
throw new BadRequestError({ throw new BadRequestError({

View File

@@ -1,6 +1,7 @@
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types"; import { Event, EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionCommitsActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -348,7 +349,8 @@ export const pitServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(userPermission).throwUnlessCan( ForbiddenError.from(userPermission).throwUnlessCan(

View File

@@ -1,3 +1,4 @@
import { ProjectType } from "@app/db/schemas";
import { import {
InfisicalProjectTemplate, InfisicalProjectTemplate,
TUnpackedPermission TUnpackedPermission
@@ -6,18 +7,21 @@ import { getPredefinedRoles } from "@app/services/project-role/project-role-fns"
import { ProjectTemplateDefaultEnvironments } from "./project-template-constants"; import { ProjectTemplateDefaultEnvironments } from "./project-template-constants";
export const getDefaultProjectTemplate = (orgId: string) => ({ export const getDefaultProjectTemplate = (orgId: string, type: ProjectType) => ({
id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod id: "b11b49a9-09a9-4443-916a-4246f9ff2c69", // random ID to appease zod
type,
name: InfisicalProjectTemplate.Default, name: InfisicalProjectTemplate.Default,
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
description: `Infisical's default project template`, description: `Infisical's ${type} default project template`,
environments: ProjectTemplateDefaultEnvironments, environments: type === ProjectType.SecretManager ? ProjectTemplateDefaultEnvironments : null,
roles: getPredefinedRoles({ projectId: "project-template" }) as Array<{ roles: [...getPredefinedRoles({ projectId: "project-template", projectType: type })].map(
name: string; ({ name, slug, permissions }) => ({
slug: string; name,
permissions: TUnpackedPermission[]; slug,
}>, permissions: permissions as TUnpackedPermission[]
})
),
orgId orgId
}); });

View File

@@ -1,7 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { packRules } from "@casl/ability/extra"; import { packRules } from "@casl/ability/extra";
import { TProjectTemplates } from "@app/db/schemas"; import { ProjectType, TProjectTemplates } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
@@ -29,11 +29,13 @@ const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTempla
...rest, ...rest,
environments: environments as TProjectTemplateEnvironment[], environments: environments as TProjectTemplateEnvironment[],
roles: [ roles: [
...getPredefinedRoles({ projectId: "project-template" }).map(({ name, slug, permissions }) => ({ ...getPredefinedRoles({ projectId: "project-template", projectType: rest.type as ProjectType }).map(
name, ({ name, slug, permissions }) => ({
slug, name,
permissions: permissions as TUnpackedPermission[] slug,
})), permissions: permissions as TUnpackedPermission[]
})
),
...(roles as TProjectTemplateRole[]).map((role) => ({ ...(roles as TProjectTemplateRole[]).map((role) => ({
...role, ...role,
permissions: unpackPermissions(role.permissions) permissions: unpackPermissions(role.permissions)
@@ -46,7 +48,10 @@ export const projectTemplateServiceFactory = ({
permissionService, permissionService,
projectTemplateDAL projectTemplateDAL
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => { }: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (actor) => { const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (
actor,
type
) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
if (!plan.projectTemplates) if (!plan.projectTemplates)
@@ -65,11 +70,14 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.ProjectTemplates);
const projectTemplates = await projectTemplateDAL.find({ const projectTemplates = await projectTemplateDAL.find({
orgId: actor.orgId orgId: actor.orgId,
...(type ? { type } : {})
}); });
return [ return [
getDefaultProjectTemplate(actor.orgId), ...(type
? [getDefaultProjectTemplate(actor.orgId, type)]
: Object.values(ProjectType).map((projectType) => getDefaultProjectTemplate(actor.orgId, projectType))),
...projectTemplates.map((template) => $unpackProjectTemplate(template)) ...projectTemplates.map((template) => $unpackProjectTemplate(template))
]; ];
}; };
@@ -134,7 +142,7 @@ export const projectTemplateServiceFactory = ({
}; };
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async ( const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
{ roles, environments, ...params }, { roles, environments, type, ...params },
actor actor
) => { ) => {
const plan = await licenseService.getPlan(actor.orgId); const plan = await licenseService.getPlan(actor.orgId);
@@ -154,6 +162,10 @@ export const projectTemplateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.ProjectTemplates);
if (environments && type !== ProjectType.SecretManager) {
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
}
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) { if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({ throw new BadRequestError({
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -176,8 +188,10 @@ export const projectTemplateServiceFactory = ({
const projectTemplate = await projectTemplateDAL.create({ const projectTemplate = await projectTemplateDAL.create({
...params, ...params,
roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))), roles: JSON.stringify(roles.map((role) => ({ ...role, permissions: packRules(role.permissions) }))),
environments: environments ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null, environments:
orgId: actor.orgId type === ProjectType.SecretManager ? JSON.stringify(environments ?? ProjectTemplateDefaultEnvironments) : null,
orgId: actor.orgId,
type
}); });
return $unpackProjectTemplate(projectTemplate); return $unpackProjectTemplate(projectTemplate);
@@ -208,6 +222,11 @@ export const projectTemplateServiceFactory = ({
); );
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
if (projectTemplate.type !== ProjectType.SecretManager && environments)
throw new BadRequestError({ message: "Cannot configure environments for non-SecretManager project templates" });
if (projectTemplate.type === ProjectType.SecretManager && environments === null)
throw new BadRequestError({ message: "Environments cannot be removed for SecretManager project templates" });
if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) { if (environments && plan.environmentLimit !== null && environments.length > plan.environmentLimit) {
throw new BadRequestError({ throw new BadRequestError({

View File

@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { ProjectMembershipRole, TProjectEnvironments } from "@app/db/schemas"; import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission"; import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
import { OrgServiceActor } from "@app/lib/types"; import { OrgServiceActor } from "@app/lib/types";
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission"; import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
@@ -15,6 +15,7 @@ export type TProjectTemplateRole = {
export type TCreateProjectTemplateDTO = { export type TCreateProjectTemplateDTO = {
name: string; name: string;
type: ProjectType;
description?: string; description?: string;
roles: TProjectTemplateRole[]; roles: TProjectTemplateRole[];
environments?: TProjectTemplateEnvironment[] | null; environments?: TProjectTemplateEnvironment[] | null;
@@ -29,11 +30,15 @@ export enum InfisicalProjectTemplate {
} }
export type TProjectTemplateServiceFactory = { export type TProjectTemplateServiceFactory = {
listProjectTemplatesByOrg: (actor: OrgServiceActor) => Promise< listProjectTemplatesByOrg: (
actor: OrgServiceActor,
type?: ProjectType
) => Promise<
( (
| { | {
id: string; id: string;
name: InfisicalProjectTemplate; name: InfisicalProjectTemplate;
type: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
description: string; description: string;
@@ -58,6 +63,7 @@ export type TProjectTemplateServiceFactory = {
} }
| { | {
environments: TProjectTemplateEnvironment[]; environments: TProjectTemplateEnvironment[];
type: string;
roles: { roles: {
permissions: { permissions: {
action: string[]; action: string[];
@@ -94,6 +100,7 @@ export type TProjectTemplateServiceFactory = {
}[]; }[];
name: string; name: string;
orgId: string; orgId: string;
type: string;
id: string; id: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
@@ -118,6 +125,7 @@ export type TProjectTemplateServiceFactory = {
name: string; name: string;
orgId: string; orgId: string;
id: string; id: string;
type: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
description?: string | null | undefined; description?: string | null | undefined;
@@ -140,6 +148,7 @@ export type TProjectTemplateServiceFactory = {
name: string; name: string;
orgId: string; orgId: string;
id: string; id: string;
type: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
description?: string | null | undefined; description?: string | null | undefined;
@@ -162,6 +171,7 @@ export type TProjectTemplateServiceFactory = {
}[]; }[];
name: string; name: string;
orgId: string; orgId: string;
type: string;
id: string; id: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
@@ -184,6 +194,7 @@ export type TProjectTemplateServiceFactory = {
name: string; name: string;
}[]; }[];
name: string; name: string;
type: string;
orgId: string; orgId: string;
id: string; id: string;
createdAt: Date; createdAt: Date;

View File

@@ -1,7 +1,7 @@
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability"; import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, packRules, unpackRules } from "@casl/ability/extra"; import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
import { TableName } from "@app/db/schemas"; import { ActionProjectType, TableName } from "@app/db/schemas";
import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors"; import { BadRequestError, NotFoundError, PermissionBoundaryError } from "@app/lib/errors";
import { ms } from "@app/lib/ms"; import { ms } from "@app/lib/ms";
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
@@ -61,7 +61,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({ const { permission: targetUserPermission, membership } = await permissionService.getProjectPermission({
@@ -69,7 +70,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId: projectMembership.userId, actorId: projectMembership.userId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -164,7 +166,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
const { permission: targetUserPermission } = await permissionService.getProjectPermission({ const { permission: targetUserPermission } = await permissionService.getProjectPermission({
@@ -172,7 +175,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId: projectMembership.userId, actorId: projectMembership.userId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
// we need to validate that the privilege given is not higher than the assigning users permission // we need to validate that the privilege given is not higher than the assigning users permission
@@ -272,7 +276,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Edit, ProjectPermissionSub.Member);
@@ -317,7 +322,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);
@@ -343,7 +349,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorId, actorId,
projectId: projectMembership.projectId, projectId: projectMembership.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member);

View File

@@ -23,6 +23,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
customFilter?: { customFilter?: {
sapId?: string; sapId?: string;
envId?: string;
} }
) => ) =>
tx(TableName.SecretApprovalPolicy) tx(TableName.SecretApprovalPolicy)
@@ -33,7 +34,17 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
void qb.where(`${TableName.SecretApprovalPolicy}.id`, "=", customFilter.sapId); void qb.where(`${TableName.SecretApprovalPolicy}.id`, "=", customFilter.sapId);
} }
}) })
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`) .join(
TableName.SecretApprovalPolicyEnvironment,
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
`${TableName.SecretApprovalPolicy}.id`
)
.join(TableName.Environment, `${TableName.SecretApprovalPolicyEnvironment}.envId`, `${TableName.Environment}.id`)
.where((qb) => {
if (customFilter?.envId) {
void qb.where(`${TableName.SecretApprovalPolicyEnvironment}.envId`, "=", customFilter.envId);
}
})
.leftJoin( .leftJoin(
TableName.SecretApprovalPolicyApprover, TableName.SecretApprovalPolicyApprover,
`${TableName.SecretApprovalPolicy}.id`, `${TableName.SecretApprovalPolicy}.id`,
@@ -97,7 +108,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
.select( .select(
tx.ref("name").withSchema(TableName.Environment).as("envName"), tx.ref("name").withSchema(TableName.Environment).as("envName"),
tx.ref("slug").withSchema(TableName.Environment).as("envSlug"), tx.ref("slug").withSchema(TableName.Environment).as("envSlug"),
tx.ref("id").withSchema(TableName.Environment).as("envId"), tx.ref("id").withSchema(TableName.Environment).as("environmentId"),
tx.ref("projectId").withSchema(TableName.Environment) tx.ref("projectId").withSchema(TableName.Environment)
) )
.select(selectAllTableCols(TableName.SecretApprovalPolicy)) .select(selectAllTableCols(TableName.SecretApprovalPolicy))
@@ -146,6 +157,15 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
firstName, firstName,
lastName lastName
}) })
},
{
key: "environmentId",
label: "environments" as const,
mapper: ({ environmentId, envName, envSlug }) => ({
id: environmentId,
name: envName,
slug: envSlug
})
} }
] ]
}); });
@@ -160,6 +180,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
customFilter?: { customFilter?: {
sapId?: string; sapId?: string;
envId?: string;
}, },
tx?: Knex tx?: Knex
) => { ) => {
@@ -221,6 +242,15 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
mapper: ({ approverGroupUserId: userId }) => ({ mapper: ({ approverGroupUserId: userId }) => ({
userId userId
}) })
},
{
key: "environmentId",
label: "environments" as const,
mapper: ({ environmentId, envName, envSlug }) => ({
id: environmentId,
name: envName,
slug: envSlug
})
} }
] ]
}); });
@@ -235,5 +265,74 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
return softDeletedPolicy; return softDeletedPolicy;
}; };
return { ...secretApprovalPolicyOrm, findById, find, softDeleteById }; const findPolicyByEnvIdAndSecretPath = async (
{ envIds, secretPath }: { envIds: string[]; secretPath: string },
tx?: Knex
) => {
try {
const docs = await (tx || db.replicaNode())(TableName.SecretApprovalPolicy)
.join(
TableName.SecretApprovalPolicyEnvironment,
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
`${TableName.SecretApprovalPolicy}.id`
)
.join(
TableName.Environment,
`${TableName.SecretApprovalPolicyEnvironment}.envId`,
`${TableName.Environment}.id`
)
.where(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
buildFindFilter(
{
$in: {
envId: envIds
}
},
TableName.SecretApprovalPolicyEnvironment
)
)
.where(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
buildFindFilter(
{
secretPath
},
TableName.SecretApprovalPolicy
)
)
.whereNull(`${TableName.SecretApprovalPolicy}.deletedAt`)
.orderBy("deletedAt", "desc")
.orderByRaw(`"deletedAt" IS NULL`)
.select(selectAllTableCols(TableName.SecretApprovalPolicy))
.select(db.ref("name").withSchema(TableName.Environment).as("envName"))
.select(db.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(db.ref("id").withSchema(TableName.Environment).as("environmentId"))
.select(db.ref("projectId").withSchema(TableName.Environment));
const formattedDocs = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (data) => ({
projectId: data.projectId,
...SecretApprovalPoliciesSchema.parse(data)
}),
childrenMapper: [
{
key: "environmentId",
label: "environments" as const,
mapper: ({ environmentId: id, envName, envSlug }) => ({
id,
name: envName,
slug: envSlug
})
}
]
});
return formattedDocs?.[0];
} catch (error) {
throw new DatabaseError({ error, name: "findPolicyByEnvIdAndSecretPath" });
}
};
return { ...secretApprovalPolicyOrm, findById, find, softDeleteById, findPolicyByEnvIdAndSecretPath };
}; };

View File

@@ -0,0 +1,32 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols } from "@app/lib/knex";
export type TSecretApprovalPolicyEnvironmentDALFactory = ReturnType<typeof secretApprovalPolicyEnvironmentDALFactory>;
export const secretApprovalPolicyEnvironmentDALFactory = (db: TDbClient) => {
const secretApprovalPolicyEnvironmentOrm = ormify(db, TableName.SecretApprovalPolicyEnvironment);
const findAvailablePoliciesByEnvId = async (envId: string, tx?: Knex) => {
try {
const docs = await (tx || db.replicaNode())(TableName.SecretApprovalPolicyEnvironment)
.join(
TableName.SecretApprovalPolicy,
`${TableName.SecretApprovalPolicyEnvironment}.policyId`,
`${TableName.SecretApprovalPolicy}.id`
)
// eslint-disable-next-line @typescript-eslint/no-misused-promises
.where(buildFindFilter({ envId }, TableName.SecretApprovalPolicyEnvironment))
.whereNull(`${TableName.SecretApprovalPolicy}.deletedAt`)
.select(selectAllTableCols(TableName.SecretApprovalPolicyEnvironment));
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "findAvailablePoliciesByEnvId" });
}
};
return { ...secretApprovalPolicyEnvironmentOrm, findAvailablePoliciesByEnvId };
};

View File

@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import picomatch from "picomatch"; import picomatch from "picomatch";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -18,6 +19,7 @@ import {
TSecretApprovalPolicyBypasserDALFactory TSecretApprovalPolicyBypasserDALFactory
} from "./secret-approval-policy-approver-dal"; } from "./secret-approval-policy-approver-dal";
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal"; import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
import { TSecretApprovalPolicyEnvironmentDALFactory } from "./secret-approval-policy-environment-dal";
import { import {
TCreateSapDTO, TCreateSapDTO,
TDeleteSapDTO, TDeleteSapDTO,
@@ -35,12 +37,13 @@ const getPolicyScore = (policy: { secretPath?: string | null }) =>
type TSecretApprovalPolicyServiceFactoryDep = { type TSecretApprovalPolicyServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory; secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">; projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne" | "find">;
userDAL: Pick<TUserDALFactory, "find">; userDAL: Pick<TUserDALFactory, "find">;
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory; secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory; secretApprovalPolicyBypasserDAL: TSecretApprovalPolicyBypasserDALFactory;
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">; secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "update">;
secretApprovalPolicyEnvironmentDAL: TSecretApprovalPolicyEnvironmentDALFactory;
}; };
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>; export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
@@ -50,27 +53,30 @@ export const secretApprovalPolicyServiceFactory = ({
permissionService, permissionService,
secretApprovalPolicyApproverDAL, secretApprovalPolicyApproverDAL,
secretApprovalPolicyBypasserDAL, secretApprovalPolicyBypasserDAL,
secretApprovalPolicyEnvironmentDAL,
projectEnvDAL, projectEnvDAL,
userDAL, userDAL,
licenseService, licenseService,
secretApprovalRequestDAL secretApprovalRequestDAL
}: TSecretApprovalPolicyServiceFactoryDep) => { }: TSecretApprovalPolicyServiceFactoryDep) => {
const $policyExists = async ({ const $policyExists = async ({
envIds,
envId, envId,
secretPath, secretPath,
policyId policyId
}: { }: {
envId: string; envIds?: string[];
envId?: string;
secretPath: string; secretPath: string;
policyId?: string; policyId?: string;
}) => { }) => {
const policy = await secretApprovalPolicyDAL if (!envIds && !envId) {
.findOne({ throw new BadRequestError({ message: "At least one environment should be provided" });
envId, }
secretPath, const policy = await secretApprovalPolicyDAL.findPolicyByEnvIdAndSecretPath({
deletedAt: null envIds: envId ? [envId] : envIds || [],
}) secretPath
.catch(() => null); });
return policyId ? policy && policy.id !== policyId : Boolean(policy); return policyId ? policy && policy.id !== policyId : Boolean(policy);
}; };
@@ -87,6 +93,7 @@ export const secretApprovalPolicyServiceFactory = ({
projectId, projectId,
secretPath, secretPath,
environment, environment,
environments,
enforcementLevel, enforcementLevel,
allowedSelfApprovals allowedSelfApprovals
}: TCreateSapDTO) => { }: TCreateSapDTO) => {
@@ -110,7 +117,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,
@@ -125,17 +133,23 @@ export const secretApprovalPolicyServiceFactory = ({
}); });
} }
const env = await projectEnvDAL.findOne({ slug: environment, projectId }); const mergedEnvs = (environment ? [environment] : environments) || [];
if (!env) { if (mergedEnvs.length === 0) {
throw new NotFoundError({ throw new BadRequestError({ message: "Must provide either environment or environments" });
message: `Environment with slug '${environment}' not found in project with ID ${projectId}` }
}); const envs = await projectEnvDAL.find({ $in: { slug: mergedEnvs }, projectId });
if (!envs.length || envs.length !== mergedEnvs.length) {
const notFoundEnvs = mergedEnvs.filter((env) => !envs.find((el) => el.slug === env));
throw new NotFoundError({ message: `One or more environments not found: ${notFoundEnvs.join(", ")}` });
} }
if (await $policyExists({ envId: env.id, secretPath })) { for (const env of envs) {
throw new BadRequestError({ // eslint-disable-next-line no-await-in-loop
message: `A policy for secret path '${secretPath}' already exists in environment '${environment}'` if (await $policyExists({ envId: env.id, secretPath })) {
}); throw new BadRequestError({
message: `A policy for secret path '${secretPath}' already exists in environment '${env.slug}'`
});
}
} }
let groupBypassers: string[] = []; let groupBypassers: string[] = [];
@@ -179,7 +193,7 @@ export const secretApprovalPolicyServiceFactory = ({
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => { const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
const doc = await secretApprovalPolicyDAL.create( const doc = await secretApprovalPolicyDAL.create(
{ {
envId: env.id, envId: envs[0].id,
approvals, approvals,
secretPath, secretPath,
name, name,
@@ -188,6 +202,13 @@ export const secretApprovalPolicyServiceFactory = ({
}, },
tx tx
); );
await secretApprovalPolicyEnvironmentDAL.insertMany(
envs.map((env) => ({
envId: env.id,
policyId: doc.id
})),
tx
);
let userApproverIds = userApprovers; let userApproverIds = userApprovers;
if (userApproverNames.length) { if (userApproverNames.length) {
@@ -251,12 +272,13 @@ export const secretApprovalPolicyServiceFactory = ({
return doc; return doc;
}); });
return { ...secretApproval, environment: env, projectId }; return { ...secretApproval, environments: envs, projectId, environment: envs[0] };
}; };
const updateSecretApprovalPolicy = async ({ const updateSecretApprovalPolicy = async ({
approvers, approvers,
bypassers, bypassers,
environments,
secretPath, secretPath,
name, name,
actorId, actorId,
@@ -286,17 +308,26 @@ export const secretApprovalPolicyServiceFactory = ({
message: `Secret approval policy with ID '${secretPolicyId}' not found` message: `Secret approval policy with ID '${secretPolicyId}' not found`
}); });
} }
let envs = secretApprovalPolicy.environments;
if ( if (
await $policyExists({ environments &&
envId: secretApprovalPolicy.envId, (environments.length !== envs.length || environments.some((env) => !envs.find((el) => el.slug === env)))
secretPath: secretPath || secretApprovalPolicy.secretPath,
policyId: secretApprovalPolicy.id
})
) { ) {
throw new BadRequestError({ envs = await projectEnvDAL.find({ $in: { slug: environments }, projectId: secretApprovalPolicy.projectId });
message: `A policy for secret path '${secretPath}' already exists in environment '${secretApprovalPolicy.environment.slug}'` }
}); for (const env of envs) {
if (
// eslint-disable-next-line no-await-in-loop
await $policyExists({
envId: env.id,
secretPath: secretPath || secretApprovalPolicy.secretPath,
policyId: secretApprovalPolicy.id
})
) {
throw new BadRequestError({
message: `A policy for secret path '${secretPath || secretApprovalPolicy.secretPath}' already exists in environment '${env.slug}'`
});
}
} }
const { permission } = await permissionService.getProjectPermission({ const { permission } = await permissionService.getProjectPermission({
@@ -304,7 +335,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: secretApprovalPolicy.projectId, projectId: secretApprovalPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
@@ -412,6 +444,17 @@ export const secretApprovalPolicyServiceFactory = ({
); );
} }
if (environments) {
await secretApprovalPolicyEnvironmentDAL.delete({ policyId: doc.id }, tx);
await secretApprovalPolicyEnvironmentDAL.insertMany(
envs.map((env) => ({
envId: env.id,
policyId: doc.id
})),
tx
);
}
await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx); await secretApprovalPolicyBypasserDAL.delete({ policyId: doc.id }, tx);
if (bypasserUserIds.length) { if (bypasserUserIds.length) {
@@ -438,7 +481,8 @@ export const secretApprovalPolicyServiceFactory = ({
}); });
return { return {
...updatedSap, ...updatedSap,
environment: secretApprovalPolicy.environment, environments: secretApprovalPolicy.environments,
environment: secretApprovalPolicy.environments[0],
projectId: secretApprovalPolicy.projectId projectId: secretApprovalPolicy.projectId
}; };
}; };
@@ -459,7 +503,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: sapPolicy.projectId, projectId: sapPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete, ProjectPermissionActions.Delete,
@@ -483,7 +528,12 @@ export const secretApprovalPolicyServiceFactory = ({
const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx); const updatedPolicy = await secretApprovalPolicyDAL.softDeleteById(secretPolicyId, tx);
return updatedPolicy; return updatedPolicy;
}); });
return { ...deletedPolicy, projectId: sapPolicy.projectId, environment: sapPolicy.environment }; return {
...deletedPolicy,
projectId: sapPolicy.projectId,
environments: sapPolicy.environments,
environment: sapPolicy.environments[0]
};
}; };
const getSecretApprovalPolicyByProjectId = async ({ const getSecretApprovalPolicyByProjectId = async ({
@@ -498,7 +548,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
@@ -515,7 +566,7 @@ export const secretApprovalPolicyServiceFactory = ({
}); });
} }
const policies = await secretApprovalPolicyDAL.find({ envId: env.id, deletedAt: null }); const policies = await secretApprovalPolicyDAL.find({ deletedAt: null }, { envId: env.id });
if (!policies.length) return; if (!policies.length) return;
// this will filter policies either without scoped to secret path or the one that matches with secret path // this will filter policies either without scoped to secret path or the one that matches with secret path
const policiesFilteredByPath = policies.filter( const policiesFilteredByPath = policies.filter(
@@ -542,7 +593,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
return getSecretApprovalPolicy(projectId, environment, secretPath); return getSecretApprovalPolicy(projectId, environment, secretPath);
@@ -568,7 +620,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorId, actorId,
projectId: sapPolicy.projectId, projectId: sapPolicy.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);

View File

@@ -5,7 +5,8 @@ import { ApproverType, BypasserType } from "../access-approval-policy/access-app
export type TCreateSapDTO = { export type TCreateSapDTO = {
approvals: number; approvals: number;
secretPath: string; secretPath: string;
environment: string; environment?: string;
environments?: string[];
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[]; approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
bypassers?: ( bypassers?: (
| { type: BypasserType.Group; id: string } | { type: BypasserType.Group; id: string }
@@ -29,6 +30,7 @@ export type TUpdateSapDTO = {
name?: string; name?: string;
enforcementLevel?: EnforcementLevel; enforcementLevel?: EnforcementLevel;
allowedSelfApprovals?: boolean; allowedSelfApprovals?: boolean;
environments?: string[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TDeleteSapDTO = { export type TDeleteSapDTO = {

View File

@@ -40,6 +40,13 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalRequest}.policyId`, `${TableName.SecretApprovalRequest}.policyId`,
`${TableName.SecretApprovalPolicy}.id` `${TableName.SecretApprovalPolicy}.id`
) )
.leftJoin(TableName.SecretApprovalPolicyEnvironment, (bd) => {
bd.on(
`${TableName.SecretApprovalPolicy}.id`,
"=",
`${TableName.SecretApprovalPolicyEnvironment}.policyId`
).andOn(`${TableName.SecretApprovalPolicyEnvironment}.envId`, "=", `${TableName.SecretFolder}.envId`);
})
.leftJoin<TUsers>( .leftJoin<TUsers>(
db(TableName.Users).as("statusChangedByUser"), db(TableName.Users).as("statusChangedByUser"),
`${TableName.SecretApprovalRequest}.statusChangedByUserId`, `${TableName.SecretApprovalRequest}.statusChangedByUserId`,
@@ -146,7 +153,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("projectId").withSchema(TableName.Environment), tx.ref("projectId").withSchema(TableName.Environment),
tx.ref("slug").withSchema(TableName.Environment).as("environment"), tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"), tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"), tx.ref("envId").withSchema(TableName.SecretApprovalPolicyEnvironment).as("policyEnvId"),
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"), tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
tx.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"), tx.ref("allowedSelfApprovals").withSchema(TableName.SecretApprovalPolicy).as("policyAllowedSelfApprovals"),
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"), tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),

View File

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

View File

@@ -3,6 +3,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import { Knex } from "knex"; import { Knex } from "knex";
import { import {
ActionProjectType,
ProjectMembershipRole, ProjectMembershipRole,
SecretEncryptionAlgo, SecretEncryptionAlgo,
SecretKeyEncoding, SecretKeyEncoding,
@@ -184,7 +185,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId); const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId, policyId);
@@ -211,7 +213,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId); const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
@@ -263,7 +266,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
@@ -412,7 +416,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: secretApprovalRequest.projectId, projectId: secretApprovalRequest.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
@@ -481,7 +486,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId: secretApprovalRequest.projectId, projectId: secretApprovalRequest.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
@@ -531,13 +537,19 @@ export const secretApprovalRequestServiceFactory = ({
message: "The policy associated with this secret approval request has been deleted." message: "The policy associated with this secret approval request has been deleted."
}); });
} }
if (!policy.envId) {
throw new BadRequestError({
message: "The policy associated with this secret approval request is not linked to the environment."
});
}
const { hasRole } = await permissionService.getProjectPermission({ const { hasRole } = await permissionService.getProjectPermission({
actor: ActorType.USER, actor: ActorType.USER,
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
if ( if (
@@ -955,7 +967,7 @@ export const secretApprovalRequestServiceFactory = ({
bypassReason, bypassReason,
secretPath: policy.secretPath, secretPath: policy.secretPath,
environment: env.name, environment: env.name,
approvalUrl: `${cfg.SITE_URL}/projects/${project.id}/secret-manager/approval` approvalUrl: `${cfg.SITE_URL}/projects/secret-management/${project.id}/approval`
}, },
template: SmtpTemplates.AccessSecretRequestBypassed template: SmtpTemplates.AccessSecretRequestBypassed
}); });
@@ -1089,7 +1101,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, { throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
@@ -1380,7 +1393,8 @@ export const secretApprovalRequestServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) if (!folder)

View File

@@ -167,7 +167,7 @@ export const secretRotationV2QueueServiceFactory = async ({
environment: environment.name, environment: environment.name,
projectName: project.name, projectName: project.name,
rotationUrl: encodeURI( rotationUrl: encodeURI(
`${appCfg.SITE_URL}/projects/${projectId}/secret-manager/secrets/${environment.slug}` `${appCfg.SITE_URL}/projects/secret-management/${projectId}/secrets/${environment.slug}`
) )
} }
}); });

View File

@@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import { Knex } from "knex"; import { Knex } from "knex";
import isEqual from "lodash.isequal"; import isEqual from "lodash.isequal";
import { SecretType, TableName } from "@app/db/schemas"; import { ActionProjectType, SecretType, TableName } from "@app/db/schemas";
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types"; import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service"; import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@@ -223,7 +223,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -274,7 +274,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -320,7 +320,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -385,7 +385,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -429,7 +429,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -631,7 +631,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -781,7 +781,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1113,7 +1113,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1160,7 +1160,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1212,7 +1212,7 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager,
projectId projectId
}); });
@@ -1328,7 +1328,8 @@ export const secretRotationV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
projectId, projectId,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretManager
}); });
const permissiveFolderMappings = folderMappings.filter(({ path, environment }) => const permissiveFolderMappings = folderMappings.filter(({ path, environment }) =>

View File

@@ -7,12 +7,13 @@ import {
TRotationFactoryRevokeCredentials, TRotationFactoryRevokeCredentials,
TRotationFactoryRotateCredentials TRotationFactoryRotateCredentials
} from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types"; } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-types";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { import {
executeWithPotentialGateway, executeWithPotentialGateway,
SQL_CONNECTION_ALTER_LOGIN_STATEMENT SQL_CONNECTION_ALTER_LOGIN_STATEMENT
} from "@app/services/app-connection/shared/sql"; } from "@app/services/app-connection/shared/sql";
import { generatePassword } from "../utils"; import { DEFAULT_PASSWORD_REQUIREMENTS, generatePassword } from "../utils";
import { import {
TSqlCredentialsRotationGeneratedCredentials, TSqlCredentialsRotationGeneratedCredentials,
TSqlCredentialsRotationWithConnection TSqlCredentialsRotationWithConnection
@@ -32,6 +33,11 @@ const redactPasswords = (e: unknown, credentials: TSqlCredentialsRotationGenerat
return redactedMessage; return redactedMessage;
}; };
const ORACLE_PASSWORD_REQUIREMENTS = {
...DEFAULT_PASSWORD_REQUIREMENTS,
length: 30
};
export const sqlCredentialsRotationFactory: TRotationFactory< export const sqlCredentialsRotationFactory: TRotationFactory<
TSqlCredentialsRotationWithConnection, TSqlCredentialsRotationWithConnection,
TSqlCredentialsRotationGeneratedCredentials TSqlCredentialsRotationGeneratedCredentials
@@ -43,6 +49,9 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
secretsMapping secretsMapping
} = secretRotation; } = secretRotation;
const passwordRequirement =
connection.app === AppConnection.OracleDB ? ORACLE_PASSWORD_REQUIREMENTS : DEFAULT_PASSWORD_REQUIREMENTS;
const executeOperation = <T>( const executeOperation = <T>(
operation: (client: Knex) => Promise<T>, operation: (client: Knex) => Promise<T>,
credentialsOverride?: TSqlCredentialsRotationGeneratedCredentials[number] credentialsOverride?: TSqlCredentialsRotationGeneratedCredentials[number]
@@ -65,7 +74,7 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => { const $validateCredentials = async (credentials: TSqlCredentialsRotationGeneratedCredentials[number]) => {
try { try {
await executeOperation(async (client) => { await executeOperation(async (client) => {
await client.raw("SELECT 1"); await client.raw(connection.app === AppConnection.OracleDB ? `SELECT 1 FROM DUAL` : `Select 1`);
}, credentials); }, credentials);
} catch (error) { } catch (error) {
throw new Error(redactPasswords(error, [credentials])); throw new Error(redactPasswords(error, [credentials]));
@@ -75,11 +84,13 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
const issueCredentials: TRotationFactoryIssueCredentials<TSqlCredentialsRotationGeneratedCredentials> = async ( const issueCredentials: TRotationFactoryIssueCredentials<TSqlCredentialsRotationGeneratedCredentials> = async (
callback callback
) => { ) => {
// For SQL, since we get existing users, we change both their passwords
// on issue to invalidate their existing passwords
// For SQL, since we get existing users, we change both their passwords // For SQL, since we get existing users, we change both their passwords
// on issue to invalidate their existing passwords // on issue to invalidate their existing passwords
const credentialsSet = [ const credentialsSet = [
{ username: username1, password: generatePassword() }, { username: username1, password: generatePassword(passwordRequirement) },
{ username: username2, password: generatePassword() } { username: username2, password: generatePassword(passwordRequirement) }
]; ];
try { try {
@@ -105,7 +116,10 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
credentialsToRevoke, credentialsToRevoke,
callback callback
) => { ) => {
const revokedCredentials = credentialsToRevoke.map(({ username }) => ({ username, password: generatePassword() })); const revokedCredentials = credentialsToRevoke.map(({ username }) => ({
username,
password: generatePassword(passwordRequirement)
}));
try { try {
await executeOperation(async (client) => { await executeOperation(async (client) => {
@@ -128,7 +142,10 @@ export const sqlCredentialsRotationFactory: TRotationFactory<
callback callback
) => { ) => {
// generate new password for the next active user // generate new password for the next active user
const credentials = { username: activeIndex === 0 ? username2 : username1, password: generatePassword() }; const credentials = {
username: activeIndex === 0 ? username2 : username1,
password: generatePassword(passwordRequirement)
};
try { try {
await executeOperation(async (client) => { await executeOperation(async (client) => {

View File

@@ -11,7 +11,7 @@ type TPasswordRequirements = {
allowedSymbols?: string; allowedSymbols?: string;
}; };
const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = { export const DEFAULT_PASSWORD_REQUIREMENTS: TPasswordRequirements = {
length: 48, length: 48,
required: { required: {
lowercase: 1, lowercase: 1,

View File

@@ -1,7 +1,7 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import Ajv from "ajv"; import Ajv from "ajv";
import { ProjectVersion, TableName } from "@app/db/schemas"; import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
@@ -66,7 +66,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Read, ProjectPermissionSecretRotationActions.Read,
@@ -97,7 +98,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Read, ProjectPermissionSecretRotationActions.Read,
@@ -213,7 +215,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Read, ProjectPermissionSecretRotationActions.Read,
@@ -263,7 +266,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Edit, ProjectPermissionSecretRotationActions.Edit,
@@ -283,7 +287,8 @@ export const secretRotationServiceFactory = ({
actorId, actorId,
projectId: doc.projectId, projectId: doc.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionSecretRotationActions.Delete, ProjectPermissionSecretRotationActions.Delete,

View File

@@ -596,7 +596,7 @@ export const secretScanningV2QueueServiceFactory = async ({
numberOfSecrets: payload.numberOfSecrets, numberOfSecrets: payload.numberOfSecrets,
isDiffScan: payload.isDiffScan, isDiffScan: payload.isDiffScan,
url: encodeURI( url: encodeURI(
`${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/findings?search=scanId:${payload.scanId}` `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/findings?search=scanId:${payload.scanId}`
), ),
timestamp timestamp
} }
@@ -607,7 +607,7 @@ export const secretScanningV2QueueServiceFactory = async ({
timestamp, timestamp,
errorMessage: payload.errorMessage, errorMessage: payload.errorMessage,
url: encodeURI( url: encodeURI(
`${appCfg.SITE_URL}/projects/${projectId}/secret-scanning/data-sources/${dataSource.type}/${dataSource.id}` `${appCfg.SITE_URL}/projects/secret-scanning/${projectId}/data-sources/${dataSource.type}/${dataSource.id}`
) )
} }
}); });

View File

@@ -1,6 +1,7 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { join } from "path"; import { join } from "path";
import { ActionProjectType } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { import {
@@ -94,7 +95,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -156,7 +157,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -201,7 +202,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -235,7 +236,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: payload.projectId projectId: payload.projectId
}); });
@@ -348,7 +349,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -401,6 +402,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -474,7 +476,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -538,7 +540,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -583,7 +585,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -626,7 +628,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -669,7 +671,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: dataSource.projectId projectId: dataSource.projectId
}); });
@@ -702,7 +704,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -736,7 +738,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -776,7 +778,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId: finding.projectId projectId: finding.projectId
}); });
@@ -807,7 +809,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });
@@ -842,7 +844,7 @@ export const secretScanningV2ServiceFactory = ({
actorId: actor.id, actorId: actor.id,
actorAuthMethod: actor.authMethod, actorAuthMethod: actor.authMethod,
actorOrgId: actor.orgId, actorOrgId: actor.orgId,
actionProjectType: ActionProjectType.SecretScanning,
projectId projectId
}); });

View File

@@ -2,7 +2,7 @@
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error // akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas"; import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography"; import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
import { InternalServerError, NotFoundError } from "@app/lib/errors"; import { InternalServerError, NotFoundError } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
@@ -103,7 +103,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
@@ -139,7 +140,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
@@ -167,7 +169,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId: snapshot.projectId, projectId: snapshot.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
@@ -391,7 +394,8 @@ export const secretSnapshotServiceFactory = ({
actorId, actorId,
projectId: snapshot.projectId, projectId: snapshot.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SecretManager
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Create, ProjectPermissionActions.Create,

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { BadRequestError, NotFoundError } from "@app/lib/errors";
@@ -58,7 +59,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -130,7 +132,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: certTemplate.projectId, projectId: certTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -198,7 +201,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: certificateTemplate.projectId, projectId: certificateTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -224,7 +228,8 @@ export const sshCertificateTemplateServiceFactory = ({
actorId, actorId,
projectId: certTemplate.projectId, projectId: certTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal"; import { TSshHostDALFactory } from "@app/ee/services/ssh-host/ssh-host-dal";
@@ -79,7 +80,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.SshHostGroups);
@@ -171,7 +173,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
@@ -267,7 +270,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
@@ -290,7 +294,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.SshHostGroups);
@@ -316,7 +321,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SshHostGroups);
@@ -354,7 +360,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);
@@ -393,7 +400,8 @@ export const sshHostGroupServiceFactory = ({
actorId, actorId,
projectId: sshHostGroup.projectId, projectId: sshHostGroup.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SshHostGroups);

View File

@@ -1,5 +1,6 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission"; import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "../permission/project-permission";
@@ -62,7 +63,8 @@ export const createSshLoginMappings = async ({
userId: user.id, userId: user.id,
projectId, projectId,
authMethod: actorAuthMethod, authMethod: actorAuthMethod,
userOrgId: actorOrgId userOrgId: actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
} }

View File

@@ -1,5 +1,6 @@
import { ForbiddenError, subject } from "@casl/ability"; import { ForbiddenError, subject } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionSshHostActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@@ -111,7 +112,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: project.id, projectId: project.id,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
const projectHosts = await sshHostDAL.findUserAccessibleSshHosts([project.id], actorId); const projectHosts = await sshHostDAL.findUserAccessibleSshHosts([project.id], actorId);
@@ -144,7 +146,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -273,7 +276,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -334,7 +338,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -362,7 +367,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -401,7 +407,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
const internalPrincipals = await convertActorToPrincipals({ const internalPrincipals = await convertActorToPrincipals({
@@ -520,7 +527,8 @@ export const sshHostServiceFactory = ({
actorId, actorId,
projectId: host.projectId, projectId: host.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal"; import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
@@ -72,7 +73,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -107,7 +109,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -175,7 +178,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -213,7 +217,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -254,7 +259,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: sshCertificateTemplate.projectId, projectId: sshCertificateTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -375,7 +381,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: sshCertificateTemplate.projectId, projectId: sshCertificateTemplate.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(
@@ -472,7 +479,8 @@ export const sshCertificateAuthorityServiceFactory = ({
actorId, actorId,
projectId: ca.projectId, projectId: ca.projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.SSH
}); });
ForbiddenError.from(permission).throwUnlessCan( ForbiddenError.from(permission).throwUnlessCan(

View File

@@ -1,5 +1,6 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { ActionProjectType } from "@app/db/schemas";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip"; import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -35,7 +36,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
const trustedIps = await trustedIpDAL.find({ const trustedIps = await trustedIpDAL.find({
@@ -59,7 +61,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
@@ -104,7 +107,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
@@ -149,7 +153,8 @@ export const trustedIpServiceFactory = ({
actorId, actorId,
projectId, projectId,
actorAuthMethod, actorAuthMethod,
actorOrgId actorOrgId,
actionProjectType: ActionProjectType.Any
}); });
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);

View File

@@ -2245,7 +2245,9 @@ export const AppConnections = {
}, },
AZURE_CLIENT_SECRETS: { AZURE_CLIENT_SECRETS: {
code: "The OAuth code to use to connect with Azure Client Secrets.", code: "The OAuth code to use to connect with Azure Client Secrets.",
tenantId: "The Tenant ID to use to connect with Azure Client Secrets." tenantId: "The Tenant ID to use to connect with Azure Client Secrets.",
clientId: "The Client ID to use to connect with Azure Client Secrets.",
clientSecret: "The Client Secret to use to connect with Azure Client Secrets."
}, },
AZURE_DEVOPS: { AZURE_DEVOPS: {
code: "The OAuth code to use to connect with Azure DevOps.", code: "The OAuth code to use to connect with Azure DevOps.",
@@ -2290,6 +2292,9 @@ export const AppConnections = {
accessKey: "The Key used to access Supabase.", accessKey: "The Key used to access Supabase.",
instanceUrl: "The URL used to access Supabase." instanceUrl: "The URL used to access Supabase."
}, },
DIGITAL_OCEAN_APP_PLATFORM: {
apiToken: "The API token used to authenticate with Digital Ocean App Platform."
},
OKTA: { OKTA: {
instanceUrl: "The URL used to access your Okta organization.", instanceUrl: "The URL used to access your Okta organization.",
apiToken: "The API token used to authenticate with Okta." apiToken: "The API token used to authenticate with Okta."
@@ -2370,6 +2375,10 @@ export const SecretSyncs = {
keyId: "The AWS KMS key ID or alias to use when encrypting parameters synced by Infisical.", keyId: "The AWS KMS key ID or alias to use when encrypting parameters synced by Infisical.",
tags: "Optional tags to add to secrets synced by Infisical.", tags: "Optional tags to add to secrets synced by Infisical.",
syncSecretMetadataAsTags: `Whether Infisical secret metadata should be added as tags to secrets synced by Infisical.` syncSecretMetadataAsTags: `Whether Infisical secret metadata should be added as tags to secrets synced by Infisical.`
},
RENDER: {
autoRedeployServices:
"Whether Infisical should automatically redeploy the configured Render service upon secret changes."
} }
}, },
DESTINATION_CONFIG: { DESTINATION_CONFIG: {
@@ -2506,6 +2515,11 @@ export const SecretSyncs = {
SUPABASE: { SUPABASE: {
projectId: "The ID of the Supabase project to sync secrets to.", projectId: "The ID of the Supabase project to sync secrets to.",
projectName: "The name of the Supabase project to sync secrets to." projectName: "The name of the Supabase project to sync secrets to."
},
BITBUCKET: {
workspaceSlug: "The Bitbucket Workspace slug to sync secrets to.",
repositorySlug: "The Bitbucket Repository slug to sync secrets to.",
environmentId: "The Bitbucket Deployment Environment uuid to sync secrets to."
} }
} }
}; };

View File

@@ -261,10 +261,26 @@ const envSchema = z
// gcp app // gcp app
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()), INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),
// azure app // Legacy Single Multi Purpose Azure App Connection
INF_APP_CONNECTION_AZURE_CLIENT_ID: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_CLIENT_ID: zpStr(z.string().optional()),
INF_APP_CONNECTION_AZURE_CLIENT_SECRET: zpStr(z.string().optional()), INF_APP_CONNECTION_AZURE_CLIENT_SECRET: zpStr(z.string().optional()),
// Azure App Configuration App Connection
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID: zpStr(z.string().optional()),
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET: zpStr(z.string().optional()),
// Azure Key Vault App Connection
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID: zpStr(z.string().optional()),
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET: zpStr(z.string().optional()),
// Azure Client Secrets App Connection
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID: zpStr(z.string().optional()),
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET: zpStr(z.string().optional()),
// Azure DevOps App Connection
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID: zpStr(z.string().optional()),
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET: zpStr(z.string().optional()),
// datadog // datadog
SHOULD_USE_DATADOG_TRACER: zodStrBool.default("false"), SHOULD_USE_DATADOG_TRACER: zodStrBool.default("false"),
DATADOG_PROFILING_ENABLED: zodStrBool.default("false"), DATADOG_PROFILING_ENABLED: zodStrBool.default("false"),
@@ -341,7 +357,23 @@ const envSchema = z
isHsmConfigured: isHsmConfigured:
Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined, Boolean(data.HSM_LIB_PATH) && Boolean(data.HSM_PIN) && Boolean(data.HSM_KEY_LABEL) && data.HSM_SLOT !== undefined,
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG, samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG,
SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",") SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(","),
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID:
data.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID,
INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET:
data.INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID:
data.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID,
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET:
data.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID:
data.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID,
INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET:
data.INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID:
data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID || data.INF_APP_CONNECTION_AZURE_CLIENT_ID,
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET:
data.INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET || data.INF_APP_CONNECTION_AZURE_CLIENT_SECRET
})); }));
export type TEnvConfig = Readonly<z.infer<typeof envSchema>>; export type TEnvConfig = Readonly<z.infer<typeof envSchema>>;
@@ -451,15 +483,54 @@ export const overwriteSchema: {
} }
] ]
}, },
azure: { azureAppConfiguration: {
name: "Azure", name: "Azure App Configuration",
fields: [ fields: [
{ {
key: "INF_APP_CONNECTION_AZURE_CLIENT_ID", key: "INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID",
description: "The Application (Client) ID of your Azure application." description: "The Application (Client) ID of your Azure application."
}, },
{ {
key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRET", key: "INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET",
description: "The Client Secret of your Azure application."
}
]
},
azureKeyVault: {
name: "Azure Key Vault",
fields: [
{
key: "INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_ID",
description: "The Application (Client) ID of your Azure application."
},
{
key: "INF_APP_CONNECTION_AZURE_KEY_VAULT_CLIENT_SECRET",
description: "The Client Secret of your Azure application."
}
]
},
azureClientSecrets: {
name: "Azure Client Secrets",
fields: [
{
key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID",
description: "The Application (Client) ID of your Azure application."
},
{
key: "INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET",
description: "The Client Secret of your Azure application."
}
]
},
azureDevOps: {
name: "Azure DevOps",
fields: [
{
key: "INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_ID",
description: "The Application (Client) ID of your Azure application."
},
{
key: "INF_APP_CONNECTION_AZURE_DEVOPS_CLIENT_SECRET",
description: "The Client Secret of your Azure application." description: "The Client Secret of your Azure application."
} }
] ]

View File

@@ -14,7 +14,7 @@ import { TSuperAdminDALFactory } from "@app/services/super-admin/super-admin-dal
import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service"; import { ADMIN_CONFIG_DB_UUID } from "@app/services/super-admin/super-admin-service";
import { isBase64 } from "../../base64"; import { isBase64 } from "../../base64";
import { getConfig } from "../../config/env"; import { getConfig, TEnvConfig } from "../../config/env";
import { CryptographyError } from "../../errors"; import { CryptographyError } from "../../errors";
import { logger } from "../../logger"; import { logger } from "../../logger";
import { asymmetricFipsValidated } from "./asymmetric-fips"; import { asymmetricFipsValidated } from "./asymmetric-fips";
@@ -106,12 +106,12 @@ const cryptographyFactory = () => {
} }
}; };
const $setFipsModeEnabled = (enabled: boolean) => { const $setFipsModeEnabled = (enabled: boolean, envCfg?: Pick<TEnvConfig, "ENCRYPTION_KEY">) => {
// If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key. // If FIPS is enabled, we need to validate that the ENCRYPTION_KEY is in a base64 format, and is a 256-bit key.
if (enabled) { if (enabled) {
crypto.setFips(true); crypto.setFips(true);
const appCfg = getConfig(); const appCfg = envCfg || getConfig();
if (appCfg.ENCRYPTION_KEY) { if (appCfg.ENCRYPTION_KEY) {
// we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key // we need to validate that the ENCRYPTION_KEY is a base64 encoded 256-bit key
@@ -141,14 +141,14 @@ const cryptographyFactory = () => {
$isInitialized = true; $isInitialized = true;
}; };
const initialize = async (superAdminDAL: TSuperAdminDALFactory) => { const initialize = async (superAdminDAL: TSuperAdminDALFactory, envCfg?: Pick<TEnvConfig, "ENCRYPTION_KEY">) => {
if ($isInitialized) { if ($isInitialized) {
return isFipsModeEnabled(); return isFipsModeEnabled();
} }
if (process.env.FIPS_ENABLED !== "true") { if (process.env.FIPS_ENABLED !== "true") {
logger.info("Cryptography module initialized in normal operation mode."); logger.info("Cryptography module initialized in normal operation mode.");
$setFipsModeEnabled(false); $setFipsModeEnabled(false, envCfg);
return false; return false;
} }
@@ -158,11 +158,11 @@ const cryptographyFactory = () => {
if (serverCfg) { if (serverCfg) {
if (serverCfg.fipsEnabled) { if (serverCfg.fipsEnabled) {
logger.info("[FIPS]: Instance is configured for FIPS mode of operation. Continuing startup with FIPS enabled."); logger.info("[FIPS]: Instance is configured for FIPS mode of operation. Continuing startup with FIPS enabled.");
$setFipsModeEnabled(true); $setFipsModeEnabled(true, envCfg);
return true; return true;
} }
logger.info("[FIPS]: Instance age predates FIPS mode inception date. Continuing without FIPS."); logger.info("[FIPS]: Instance age predates FIPS mode inception date. Continuing without FIPS.");
$setFipsModeEnabled(false); $setFipsModeEnabled(false, envCfg);
return false; return false;
} }
@@ -171,7 +171,7 @@ const cryptographyFactory = () => {
// TODO(daniel): check if it's an enterprise deployment // TODO(daniel): check if it's an enterprise deployment
// if there is no server cfg, and FIPS_MODE is `true`, its a fresh FIPS deployment. We need to set the fipsEnabled to true. // if there is no server cfg, and FIPS_MODE is `true`, its a fresh FIPS deployment. We need to set the fipsEnabled to true.
$setFipsModeEnabled(true); $setFipsModeEnabled(true, envCfg);
return true; return true;
}; };

View File

@@ -64,7 +64,9 @@ export enum QueueName {
FolderTreeCheckpoint = "folder-tree-checkpoint", FolderTreeCheckpoint = "folder-tree-checkpoint",
InvalidateCache = "invalidate-cache", InvalidateCache = "invalidate-cache",
SecretScanningV2 = "secret-scanning-v2", SecretScanningV2 = "secret-scanning-v2",
TelemetryAggregatedEvents = "telemetry-aggregated-events" TelemetryAggregatedEvents = "telemetry-aggregated-events",
DailyReminders = "daily-reminders",
SecretReminderMigration = "secret-reminder-migration"
} }
export enum QueueJobs { export enum QueueJobs {
@@ -104,7 +106,9 @@ export enum QueueJobs {
SecretScanningV2SendNotification = "secret-scanning-v2-notification", SecretScanningV2SendNotification = "secret-scanning-v2-notification",
CaOrderCertificateForSubscriber = "ca-order-certificate-for-subscriber", CaOrderCertificateForSubscriber = "ca-order-certificate-for-subscriber",
PkiSubscriberDailyAutoRenewal = "pki-subscriber-daily-auto-renewal", PkiSubscriberDailyAutoRenewal = "pki-subscriber-daily-auto-renewal",
TelemetryAggregatedEvents = "telemetry-aggregated-events" TelemetryAggregatedEvents = "telemetry-aggregated-events",
DailyReminders = "daily-reminders",
SecretReminderMigration = "secret-reminder-migration"
} }
export type TQueueJobTypes = { export type TQueueJobTypes = {
@@ -291,6 +295,14 @@ export type TQueueJobTypes = {
caType: CaType; caType: CaType;
}; };
}; };
[QueueName.DailyReminders]: {
name: QueueJobs.DailyReminders;
payload: undefined;
};
[QueueName.SecretReminderMigration]: {
name: QueueJobs.SecretReminderMigration;
payload: undefined;
};
[QueueName.PkiSubscriber]: { [QueueName.PkiSubscriber]: {
name: QueueJobs.PkiSubscriberDailyAutoRenewal; name: QueueJobs.PkiSubscriberDailyAutoRenewal;
payload: undefined; payload: undefined;
@@ -390,6 +402,11 @@ export type TQueueServiceFactory = {
startOffset?: number, startOffset?: number,
endOffset?: number endOffset?: number
) => Promise<{ key: string; name: string; id: string | null }[]>; ) => Promise<{ key: string; name: string; id: string | null }[]>;
getDelayedJobs: (
name: QueueName,
startOffset?: number,
endOffset?: number
) => Promise<{ delay: number; timestamp: number; repeatJobKey?: string; data?: unknown }[]>;
}; };
export const queueServiceFactory = ( export const queueServiceFactory = (
@@ -552,6 +569,13 @@ export const queueServiceFactory = (
return q.getRepeatableJobs(startOffset, endOffset); return q.getRepeatableJobs(startOffset, endOffset);
}; };
const getDelayedJobs: TQueueServiceFactory["getDelayedJobs"] = (name, startOffset, endOffset) => {
const q = queueContainer[name];
if (!q) throw new Error(`Queue '${name}' not initialized`);
return q.getDelayed(startOffset, endOffset);
};
const stopRepeatableJobByJobId: TQueueServiceFactory["stopRepeatableJobByJobId"] = async (name, jobId) => { const stopRepeatableJobByJobId: TQueueServiceFactory["stopRepeatableJobByJobId"] = async (name, jobId) => {
const q = queueContainer[name]; const q = queueContainer[name];
const job = await q.getJob(jobId); const job = await q.getJob(jobId);
@@ -598,6 +622,7 @@ export const queueServiceFactory = (
stopJobById, stopJobById,
stopJobByIdPg, stopJobByIdPg,
getRepeatableJobs, getRepeatableJobs,
getDelayedJobs,
startPg, startPg,
queuePg, queuePg,
schedulePg schedulePg

View File

@@ -162,6 +162,12 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
kubernetes: token?.identityAuth?.kubernetes kubernetes: token?.identityAuth?.kubernetes
}); });
} }
if (token?.identityAuth?.aws) {
requestContext.set("identityAuthInfo", {
identityId: identity.identityId,
aws: token?.identityAuth?.aws
});
}
break; break;
} }
case AuthMode.SERVICE_TOKEN: { case AuthMode.SERVICE_TOKEN: {

View File

@@ -11,6 +11,7 @@ import {
accessApprovalPolicyBypasserDALFactory accessApprovalPolicyBypasserDALFactory
} from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal"; } 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 { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
import { accessApprovalPolicyEnvironmentDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-environment-dal";
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service"; 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 { 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 { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
@@ -76,6 +77,7 @@ import {
secretApprovalPolicyBypasserDALFactory secretApprovalPolicyBypasserDALFactory
} from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal"; } from "@app/ee/services/secret-approval-policy/secret-approval-policy-approver-dal";
import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal"; import { secretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
import { secretApprovalPolicyEnvironmentDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-environment-dal";
import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { secretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal"; import { secretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-reviewer-dal"; import { secretApprovalRequestReviewerDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-reviewer-dal";
@@ -246,6 +248,10 @@ 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 { reminderDALFactory } from "@app/services/reminder/reminder-dal";
import { dailyReminderQueueServiceFactory } from "@app/services/reminder/reminder-queue";
import { reminderServiceFactory } from "@app/services/reminder/reminder-service";
import { reminderRecipientDALFactory } from "@app/services/reminder-recipients/reminder-recipient-dal";
import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue"; import { dailyResourceCleanUpQueueServiceFactory } from "@app/services/resource-cleanup/resource-cleanup-queue";
import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal"; import { resourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal";
@@ -371,6 +377,9 @@ export const registerRoutes = async (
const secretVersionV2BridgeDAL = secretVersionV2BridgeDALFactory(db); const secretVersionV2BridgeDAL = secretVersionV2BridgeDALFactory(db);
const secretVersionTagV2BridgeDAL = secretVersionV2TagBridgeDALFactory(db); const secretVersionTagV2BridgeDAL = secretVersionV2TagBridgeDALFactory(db);
const reminderDAL = reminderDALFactory(db);
const reminderRecipientDAL = reminderRecipientDALFactory(db);
const integrationDAL = integrationDALFactory(db); const integrationDAL = integrationDALFactory(db);
const integrationAuthDAL = integrationAuthDALFactory(db); const integrationAuthDAL = integrationAuthDALFactory(db);
const webhookDAL = webhookDALFactory(db); const webhookDAL = webhookDALFactory(db);
@@ -418,9 +427,11 @@ export const registerRoutes = async (
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db); const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db); const accessApprovalPolicyBypasserDAL = accessApprovalPolicyBypasserDALFactory(db);
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db); const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
const accessApprovalPolicyEnvironmentDAL = accessApprovalPolicyEnvironmentDALFactory(db);
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db); const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db); const sapBypasserDAL = secretApprovalPolicyBypasserDALFactory(db);
const sapEnvironmentDAL = secretApprovalPolicyEnvironmentDALFactory(db);
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db); const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db); const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db); const secretApprovalRequestReviewerDAL = secretApprovalRequestReviewerDALFactory(db);
@@ -554,6 +565,7 @@ export const registerRoutes = async (
projectEnvDAL, projectEnvDAL,
secretApprovalPolicyApproverDAL: sapApproverDAL, secretApprovalPolicyApproverDAL: sapApproverDAL,
secretApprovalPolicyBypasserDAL: sapBypasserDAL, secretApprovalPolicyBypasserDAL: sapBypasserDAL,
secretApprovalPolicyEnvironmentDAL: sapEnvironmentDAL,
permissionService, permissionService,
secretApprovalPolicyDAL, secretApprovalPolicyDAL,
licenseService, licenseService,
@@ -734,9 +746,17 @@ export const registerRoutes = async (
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL }); const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const reminderService = reminderServiceFactory({
reminderDAL,
reminderRecipientDAL,
smtpService,
projectMembershipDAL,
permissionService,
secretV2BridgeDAL
});
const orgService = orgServiceFactory({ const orgService = orgServiceFactory({
userAliasDAL, userAliasDAL,
queueService,
identityMetadataDAL, identityMetadataDAL,
secretDAL, secretDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
@@ -762,7 +782,8 @@ export const registerRoutes = async (
orgBotDAL, orgBotDAL,
oidcConfigDAL, oidcConfigDAL,
loginService, loginService,
projectBotService projectBotService,
reminderService
}); });
const signupService = authSignupServiceFactory({ const signupService = authSignupServiceFactory({
tokenService, tokenService,
@@ -1060,7 +1081,6 @@ export const registerRoutes = async (
secretImportDAL, secretImportDAL,
projectEnvDAL, projectEnvDAL,
webhookDAL, webhookDAL,
orgDAL,
auditLogService, auditLogService,
userDAL, userDAL,
projectMembershipDAL, projectMembershipDAL,
@@ -1082,11 +1102,11 @@ export const registerRoutes = async (
secretApprovalRequestDAL, secretApprovalRequestDAL,
projectKeyDAL, projectKeyDAL,
projectUserMembershipRoleDAL, projectUserMembershipRoleDAL,
secretReminderRecipientsDAL,
orgService, orgService,
resourceMetadataDAL, resourceMetadataDAL,
folderCommitService, folderCommitService,
secretSyncQueue secretSyncQueue,
reminderService
}); });
const projectService = projectServiceFactory({ const projectService = projectServiceFactory({
@@ -1095,7 +1115,6 @@ export const registerRoutes = async (
projectSshConfigDAL, projectSshConfigDAL,
secretDAL, secretDAL,
secretV2BridgeDAL, secretV2BridgeDAL,
queueService,
projectQueue: projectQueueService, projectQueue: projectQueueService,
projectBotService, projectBotService,
identityProjectDAL, identityProjectDAL,
@@ -1132,7 +1151,8 @@ export const registerRoutes = async (
microsoftTeamsIntegrationDAL, microsoftTeamsIntegrationDAL,
projectTemplateService, projectTemplateService,
groupProjectDAL, groupProjectDAL,
smtpService smtpService,
reminderService
}); });
const projectEnvService = projectEnvServiceFactory({ const projectEnvService = projectEnvServiceFactory({
@@ -1141,7 +1161,9 @@ export const registerRoutes = async (
keyStore, keyStore,
licenseService, licenseService,
projectDAL, projectDAL,
folderDAL folderDAL,
accessApprovalPolicyEnvironmentDAL,
secretApprovalPolicyEnvironmentDAL: sapEnvironmentDAL
}); });
const projectRoleService = projectRoleServiceFactory({ const projectRoleService = projectRoleServiceFactory({
@@ -1231,6 +1253,7 @@ export const registerRoutes = async (
kmsService, kmsService,
snapshotService, snapshotService,
resourceMetadataDAL, resourceMetadataDAL,
reminderService,
keyStore keyStore
}); });
@@ -1284,7 +1307,8 @@ export const registerRoutes = async (
secretApprovalRequestSecretDAL, secretApprovalRequestSecretDAL,
secretV2BridgeService, secretV2BridgeService,
secretApprovalRequestService, secretApprovalRequestService,
licenseService licenseService,
reminderService
}); });
const secretSharingService = secretSharingServiceFactory({ const secretSharingService = secretSharingServiceFactory({
@@ -1300,6 +1324,7 @@ export const registerRoutes = async (
accessApprovalPolicyDAL, accessApprovalPolicyDAL,
accessApprovalPolicyApproverDAL, accessApprovalPolicyApproverDAL,
accessApprovalPolicyBypasserDAL, accessApprovalPolicyBypasserDAL,
accessApprovalPolicyEnvironmentDAL,
groupDAL, groupDAL,
permissionService, permissionService,
projectEnvDAL, projectEnvDAL,
@@ -1616,7 +1641,6 @@ export const registerRoutes = async (
auditLogDAL, auditLogDAL,
queueService, queueService,
secretVersionDAL, secretVersionDAL,
secretDAL,
secretFolderVersionDAL: folderVersionDAL, secretFolderVersionDAL: folderVersionDAL,
snapshotDAL, snapshotDAL,
identityAccessTokenDAL, identityAccessTokenDAL,
@@ -1627,6 +1651,13 @@ export const registerRoutes = async (
orgService orgService
}); });
const dailyReminderQueueService = dailyReminderQueueServiceFactory({
reminderService,
queueService,
secretDAL: secretV2BridgeDAL,
secretReminderRecipientsDAL
});
const dailyExpiringPkiItemAlert = dailyExpiringPkiItemAlertQueueServiceFactory({ const dailyExpiringPkiItemAlert = dailyExpiringPkiItemAlertQueueServiceFactory({
queueService, queueService,
pkiAlertService pkiAlertService
@@ -1926,6 +1957,8 @@ export const registerRoutes = async (
await telemetryQueue.startTelemetryCheck(); await telemetryQueue.startTelemetryCheck();
await telemetryQueue.startAggregatedEventsJob(); await telemetryQueue.startAggregatedEventsJob();
await dailyResourceCleanUp.startCleanUp(); await dailyResourceCleanUp.startCleanUp();
await dailyReminderQueueService.startDailyRemindersJob();
await dailyReminderQueueService.startSecretReminderMigrationJob();
await dailyExpiringPkiItemAlert.startSendingAlerts(); await dailyExpiringPkiItemAlert.startSendingAlerts();
await pkiSubscriberQueue.startDailyAutoRenewalJob(); await pkiSubscriberQueue.startDailyAutoRenewalJob();
await kmsService.startService(); await kmsService.startService();
@@ -2036,7 +2069,8 @@ export const registerRoutes = async (
assumePrivileges: assumePrivilegeService, assumePrivileges: assumePrivilegeService,
githubOrgSync: githubOrgSyncConfigService, githubOrgSync: githubOrgSyncConfigService,
folderCommit: folderCommitService, folderCommit: folderCommitService,
secretScanningV2: secretScanningV2Service secretScanningV2: secretScanningV2Service,
reminder: reminderService
}); });
const cronJobs: CronJob[] = []; const cronJobs: CronJob[] = [];

View File

@@ -93,6 +93,13 @@ export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
name: z.string(), name: z.string(),
slug: z.string() slug: z.string()
}), }),
environments: z.array(
z.object({
id: z.string(),
name: z.string(),
slug: z.string()
})
),
projectId: z.string() projectId: z.string()
}) })
); );

View File

@@ -51,6 +51,10 @@ import {
DatabricksConnectionListItemSchema, DatabricksConnectionListItemSchema,
SanitizedDatabricksConnectionSchema SanitizedDatabricksConnectionSchema
} from "@app/services/app-connection/databricks"; } from "@app/services/app-connection/databricks";
import {
DigitalOceanConnectionListItemSchema,
SanitizedDigitalOceanConnectionSchema
} from "@app/services/app-connection/digital-ocean";
import { FlyioConnectionListItemSchema, SanitizedFlyioConnectionSchema } from "@app/services/app-connection/flyio"; import { FlyioConnectionListItemSchema, SanitizedFlyioConnectionSchema } from "@app/services/app-connection/flyio";
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp"; import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github"; import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
@@ -140,6 +144,7 @@ const SanitizedAppConnectionSchema = z.union([
...SanitizedRailwayConnectionSchema.options, ...SanitizedRailwayConnectionSchema.options,
...SanitizedChecklyConnectionSchema.options, ...SanitizedChecklyConnectionSchema.options,
...SanitizedSupabaseConnectionSchema.options, ...SanitizedSupabaseConnectionSchema.options,
...SanitizedDigitalOceanConnectionSchema.options,
...SanitizedOktaConnectionSchema.options ...SanitizedOktaConnectionSchema.options
]); ]);
@@ -178,6 +183,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
RailwayConnectionListItemSchema, RailwayConnectionListItemSchema,
ChecklyConnectionListItemSchema, ChecklyConnectionListItemSchema,
SupabaseConnectionListItemSchema, SupabaseConnectionListItemSchema,
DigitalOceanConnectionListItemSchema,
OktaConnectionListItemSchema OktaConnectionListItemSchema
]); ]);

View File

@@ -85,4 +85,40 @@ export const registerBitbucketConnectionRouter = async (server: FastifyZodProvid
return { repositories }; return { repositories };
} }
}); });
server.route({
method: "GET",
url: `/:connectionId/environments`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
querystring: z.object({
workspaceSlug: z.string().min(1).max(255),
repositorySlug: z.string().min(1).max(255)
}),
response: {
200: z.object({
environments: z.object({ slug: z.string(), name: z.string(), uuid: z.string() }).array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const {
params: { connectionId },
query: { workspaceSlug, repositorySlug }
} = req;
const environments = await server.services.appConnection.bitbucket.listEnvironments(
{ connectionId, workspaceSlug, repositorySlug },
req.permission
);
return { environments };
}
});
}; };

View File

@@ -0,0 +1,57 @@
import { z } from "zod";
import { readLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import {
CreateDigitalOceanConnectionSchema,
SanitizedDigitalOceanConnectionSchema,
UpdateDigitalOceanConnectionSchema
} from "@app/services/app-connection/digital-ocean";
import { AuthMode } from "@app/services/auth/auth-type";
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
export const registerDigitalOceanConnectionRouter = async (server: FastifyZodProvider) => {
registerAppConnectionEndpoints({
app: AppConnection.DigitalOcean,
server,
createSchema: CreateDigitalOceanConnectionSchema,
updateSchema: UpdateDigitalOceanConnectionSchema,
sanitizedResponseSchema: SanitizedDigitalOceanConnectionSchema
});
// The below endpoints are not exposed and for Infisical App use
server.route({
method: "GET",
url: `/:connectionId/apps`,
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
connectionId: z.string().uuid()
}),
response: {
200: z.object({
apps: z
.object({
id: z.string(),
spec: z.object({
name: z.string()
})
})
.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { connectionId } = req.params;
const apps = await server.services.appConnection.digitalOcean.listApps(connectionId, req.permission);
return { apps };
}
});
};

View File

@@ -14,6 +14,7 @@ import { registerCamundaConnectionRouter } from "./camunda-connection-router";
import { registerChecklyConnectionRouter } from "./checkly-connection-router"; import { registerChecklyConnectionRouter } from "./checkly-connection-router";
import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router"; import { registerCloudflareConnectionRouter } from "./cloudflare-connection-router";
import { registerDatabricksConnectionRouter } from "./databricks-connection-router"; import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
import { registerDigitalOceanConnectionRouter } from "./digital-ocean-connection-router";
import { registerFlyioConnectionRouter } from "./flyio-connection-router"; import { registerFlyioConnectionRouter } from "./flyio-connection-router";
import { registerGcpConnectionRouter } from "./gcp-connection-router"; import { registerGcpConnectionRouter } from "./gcp-connection-router";
import { registerGitHubConnectionRouter } from "./github-connection-router"; import { registerGitHubConnectionRouter } from "./github-connection-router";
@@ -74,5 +75,6 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
[AppConnection.Railway]: registerRailwayConnectionRouter, [AppConnection.Railway]: registerRailwayConnectionRouter,
[AppConnection.Checkly]: registerChecklyConnectionRouter, [AppConnection.Checkly]: registerChecklyConnectionRouter,
[AppConnection.Supabase]: registerSupabaseConnectionRouter, [AppConnection.Supabase]: registerSupabaseConnectionRouter,
[AppConnection.DigitalOcean]: registerDigitalOceanConnectionRouter,
[AppConnection.Okta]: registerOktaConnectionRouter [AppConnection.Okta]: registerOktaConnectionRouter
}; };

View File

@@ -270,11 +270,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
} }
} }
}); });
remainingLimit -= imports.length;
adjustedOffset = 0;
} else {
adjustedOffset = Math.max(0, adjustedOffset - totalImportCount);
} }
} }
@@ -317,7 +312,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
} }
} }
if (!includeDynamicSecrets && !includeSecrets) if (!includeDynamicSecrets && !includeSecrets && !includeSecretRotations)
return { return {
folders, folders,
totalFolderCount, totalFolderCount,
@@ -547,7 +542,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
(totalFolderCount ?? 0) + (totalFolderCount ?? 0) +
(totalDynamicSecretCount ?? 0) + (totalDynamicSecretCount ?? 0) +
(totalSecretCount ?? 0) + (totalSecretCount ?? 0) +
(totalImportCount ?? 0) +
(totalSecretRotationCount ?? 0) (totalSecretRotationCount ?? 0)
}; };
} }
@@ -904,7 +898,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
projectId, projectId,
path: secretPath, path: secretPath,
search, search,
tagSlugs: tags tagSlugs: tags,
includeTagsInSearch: true,
includeMetadataInSearch: true
}); });
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) { if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
@@ -924,7 +920,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
search, search,
limit: remainingLimit, limit: remainingLimit,
offset: adjustedOffset, offset: adjustedOffset,
tagSlugs: tags tagSlugs: tags,
includeTagsInSearch: true,
includeMetadataInSearch: true
}) })
).secrets; ).secrets;
} }
@@ -1097,7 +1095,8 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
filters: { filters: {
...sharedFilters, ...sharedFilters,
tagSlugs: tags, tagSlugs: tags,
includeTagsInSearch: true includeTagsInSearch: true,
includeMetadataInSearch: true
} }
}, },
req.permission req.permission

View File

@@ -42,6 +42,7 @@ import { registerProjectEnvRouter } from "./project-env-router";
import { registerProjectKeyRouter } from "./project-key-router"; import { registerProjectKeyRouter } from "./project-key-router";
import { registerProjectMembershipRouter } from "./project-membership-router"; import { registerProjectMembershipRouter } from "./project-membership-router";
import { registerProjectRouter } from "./project-router"; import { registerProjectRouter } from "./project-router";
import { SECRET_REMINDER_REGISTER_ROUTER_MAP } from "./reminder-routers";
import { registerSecretFolderRouter } from "./secret-folder-router"; import { registerSecretFolderRouter } from "./secret-folder-router";
import { registerSecretImportRouter } from "./secret-import-router"; import { registerSecretImportRouter } from "./secret-import-router";
import { registerSecretRequestsRouter } from "./secret-requests-router"; import { registerSecretRequestsRouter } from "./secret-requests-router";
@@ -172,4 +173,14 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
}, },
{ prefix: "/secret-syncs" } { prefix: "/secret-syncs" }
); );
await server.register(
async (reminderRouter) => {
// register service specific reminder endpoints (reminders/secret)
for await (const [reminderType, router] of Object.entries(SECRET_REMINDER_REGISTER_ROUTER_MAP)) {
await reminderRouter.register(router, { prefix: `/${reminderType}` });
}
},
{ prefix: "/reminders" }
);
}; };

View File

@@ -158,7 +158,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
includeRoles: z includeRoles: z
.enum(["true", "false"]) .enum(["true", "false"])
.default("false") .default("false")
.transform((value) => value === "true") .transform((value) => value === "true"),
type: z.nativeEnum(ProjectType).optional()
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -177,7 +178,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id, actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod, actorAuthMethod: req.permission.authMethod,
actor: req.permission.type, actor: req.permission.type,
actorOrgId: req.permission.orgId actorOrgId: req.permission.orgId,
type: req.query.type
}); });
return { workspaces }; return { workspaces };
} }
@@ -1050,6 +1052,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
body: z.object({ body: z.object({
limit: z.number().default(100), limit: z.number().default(100),
offset: z.number().default(0), offset: z.number().default(0),
type: z.nativeEnum(ProjectType).optional(),
orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME), orderBy: z.nativeEnum(SearchProjectSortBy).optional().default(SearchProjectSortBy.NAME),
orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC), orderDirection: z.nativeEnum(SortDirection).optional().default(SortDirection.ASC),
name: z name: z

View File

@@ -0,0 +1,8 @@
import { ReminderType } from "@app/services/reminder/reminder-enums";
import { registerSecretReminderRouter } from "./secret-reminder-router";
export const SECRET_REMINDER_REGISTER_ROUTER_MAP: Record<ReminderType, (server: FastifyZodProvider) => Promise<void>> =
{
[ReminderType.SECRETS]: registerSecretReminderRouter
};

View File

@@ -0,0 +1,154 @@
import { z } from "zod";
import { RemindersSchema } from "@app/db/schemas/reminders";
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";
export const registerSecretReminderRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/:secretId",
method: "POST",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
secretId: z.string().uuid()
}),
body: z
.object({
message: z.string().trim().max(1024).optional(),
repeatDays: z.number().min(1).nullable().optional(),
nextReminderDate: z.string().datetime().nullable().optional(),
recipients: z.string().array().optional()
})
.refine((data) => {
return data.repeatDays || data.nextReminderDate;
}, "At least one of repeatDays or nextReminderDate is required"),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.reminder.createReminder({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
reminder: {
secretId: req.params.secretId,
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
recipients: req.body.recipients
}
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.CREATE_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId,
message: req.body.message,
repeatDays: req.body.repeatDays,
nextReminderDate: req.body.nextReminderDate,
recipients: req.body.recipients
}
}
});
return { message: "Successfully created reminder" };
}
});
server.route({
url: "/:secretId",
method: "GET",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
secretId: z.string().uuid()
}),
response: {
200: z.object({
reminder: RemindersSchema.extend({
recipients: z.string().array().optional()
})
.optional()
.nullable()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const reminder = await server.services.reminder.getReminder({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
secretId: req.params.secretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.GET_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId
}
}
});
return { reminder };
}
});
server.route({
url: "/:secretId",
method: "DELETE",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
secretId: z.string().uuid()
}),
response: {
200: z.object({
message: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
await server.services.reminder.deleteReminder({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
secretId: req.params.secretId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: req.permission.orgId,
event: {
type: EventType.DELETE_SECRET_REMINDER,
metadata: {
secretId: req.params.secretId
}
}
});
return { message: "Successfully deleted reminder" };
}
});
};

View File

@@ -0,0 +1,17 @@
import {
BitbucketSyncSchema,
CreateBitbucketSyncSchema,
UpdateBitbucketSyncSchema
} from "@app/services/secret-sync/bitbucket";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerBitbucketSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.Bitbucket,
server,
responseSchema: BitbucketSyncSchema,
createSchema: CreateBitbucketSyncSchema,
updateSchema: UpdateBitbucketSyncSchema
});

View File

@@ -0,0 +1,17 @@
import {
CreateDigitalOceanAppPlatformSyncSchema,
DigitalOceanAppPlatformSyncSchema,
UpdateDigitalOceanAppPlatformSyncSchema
} from "@app/services/secret-sync/digital-ocean-app-platform";
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
export const registerDigitalOceanAppPlatformSyncRouter = async (server: FastifyZodProvider) =>
registerSyncSecretsEndpoints({
destination: SecretSync.DigitalOceanAppPlatform,
server,
responseSchema: DigitalOceanAppPlatformSyncSchema,
createSchema: CreateDigitalOceanAppPlatformSyncSchema,
updateSchema: UpdateDigitalOceanAppPlatformSyncSchema
});

View File

@@ -7,11 +7,13 @@ import { registerAwsSecretsManagerSyncRouter } from "./aws-secrets-manager-sync-
import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router"; import { registerAzureAppConfigurationSyncRouter } from "./azure-app-configuration-sync-router";
import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router"; import { registerAzureDevOpsSyncRouter } from "./azure-devops-sync-router";
import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router"; import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
import { registerBitbucketSyncRouter } from "./bitbucket-sync-router";
import { registerCamundaSyncRouter } from "./camunda-sync-router"; import { registerCamundaSyncRouter } from "./camunda-sync-router";
import { registerChecklySyncRouter } from "./checkly-sync-router"; import { registerChecklySyncRouter } from "./checkly-sync-router";
import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router"; import { registerCloudflarePagesSyncRouter } from "./cloudflare-pages-sync-router";
import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router"; import { registerCloudflareWorkersSyncRouter } from "./cloudflare-workers-sync-router";
import { registerDatabricksSyncRouter } from "./databricks-sync-router"; import { registerDatabricksSyncRouter } from "./databricks-sync-router";
import { registerDigitalOceanAppPlatformSyncRouter } from "./digital-ocean-app-platform-sync-router";
import { registerFlyioSyncRouter } from "./flyio-sync-router"; import { registerFlyioSyncRouter } from "./flyio-sync-router";
import { registerGcpSyncRouter } from "./gcp-sync-router"; import { registerGcpSyncRouter } from "./gcp-sync-router";
import { registerGitHubSyncRouter } from "./github-sync-router"; import { registerGitHubSyncRouter } from "./github-sync-router";
@@ -57,5 +59,7 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
[SecretSync.Supabase]: registerSupabaseSyncRouter, [SecretSync.Supabase]: registerSupabaseSyncRouter,
[SecretSync.Zabbix]: registerZabbixSyncRouter, [SecretSync.Zabbix]: registerZabbixSyncRouter,
[SecretSync.Railway]: registerRailwaySyncRouter, [SecretSync.Railway]: registerRailwaySyncRouter,
[SecretSync.Checkly]: registerChecklySyncRouter [SecretSync.Checkly]: registerChecklySyncRouter,
[SecretSync.DigitalOceanAppPlatform]: registerDigitalOceanAppPlatformSyncRouter,
[SecretSync.Bitbucket]: registerBitbucketSyncRouter
}; };

View File

@@ -21,6 +21,7 @@ import {
} from "@app/services/secret-sync/azure-app-configuration"; } from "@app/services/secret-sync/azure-app-configuration";
import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops"; import { AzureDevOpsSyncListItemSchema, AzureDevOpsSyncSchema } from "@app/services/secret-sync/azure-devops";
import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault"; import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/services/secret-sync/azure-key-vault";
import { BitbucketSyncListItemSchema, BitbucketSyncSchema } from "@app/services/secret-sync/bitbucket";
import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda"; import { CamundaSyncListItemSchema, CamundaSyncSchema } from "@app/services/secret-sync/camunda";
import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas"; import { ChecklySyncListItemSchema, ChecklySyncSchema } from "@app/services/secret-sync/checkly/checkly-sync-schemas";
import { import {
@@ -32,6 +33,10 @@ import {
CloudflareWorkersSyncSchema CloudflareWorkersSyncSchema
} from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas"; } from "@app/services/secret-sync/cloudflare-workers/cloudflare-workers-schemas";
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks"; import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
import {
DigitalOceanAppPlatformSyncListItemSchema,
DigitalOceanAppPlatformSyncSchema
} from "@app/services/secret-sync/digital-ocean-app-platform";
import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio"; import { FlyioSyncListItemSchema, FlyioSyncSchema } from "@app/services/secret-sync/flyio";
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp"; import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github"; import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
@@ -75,7 +80,9 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
SupabaseSyncSchema, SupabaseSyncSchema,
ZabbixSyncSchema, ZabbixSyncSchema,
RailwaySyncSchema, RailwaySyncSchema,
ChecklySyncSchema ChecklySyncSchema,
DigitalOceanAppPlatformSyncSchema,
BitbucketSyncSchema
]); ]);
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
@@ -102,11 +109,12 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
GitLabSyncListItemSchema, GitLabSyncListItemSchema,
CloudflarePagesSyncListItemSchema, CloudflarePagesSyncListItemSchema,
CloudflareWorkersSyncListItemSchema, CloudflareWorkersSyncListItemSchema,
DigitalOceanAppPlatformSyncListItemSchema,
ZabbixSyncListItemSchema, ZabbixSyncListItemSchema,
RailwaySyncListItemSchema, RailwaySyncListItemSchema,
ChecklySyncListItemSchema, ChecklySyncListItemSchema,
SupabaseSyncListItemSchema SupabaseSyncListItemSchema,
BitbucketSyncListItemSchema
]); ]);
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => { export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {

View File

@@ -1,9 +1,11 @@
import fastifyMultipart from "@fastify/multipart"; import fastifyMultipart from "@fastify/multipart";
import { z } from "zod";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { readLimit } from "@app/server/config/rateLimiter"; import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { VaultMappingType } from "@app/services/external-migration/external-migration-types";
const MB25_IN_BYTES = 26214400; const MB25_IN_BYTES = 26214400;
@@ -15,7 +17,7 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
bodyLimit: MB25_IN_BYTES, bodyLimit: MB25_IN_BYTES,
url: "/env-key", url: "/env-key",
config: { config: {
rateLimit: readLimit rateLimit: writeLimit
}, },
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => { handler: async (req) => {
@@ -52,4 +54,30 @@ export const registerExternalMigrationRouter = async (server: FastifyZodProvider
}); });
} }
}); });
server.route({
method: "POST",
url: "/vault",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
vaultAccessToken: z.string(),
vaultNamespace: z.string().trim().optional(),
vaultUrl: z.string(),
mappingType: z.nativeEnum(VaultMappingType)
})
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
await server.services.migration.importVaultData({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
...req.body
});
}
});
}; };

View File

@@ -11,5 +11,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => {
await server.register(registerUserRouter, { prefix: "/users" }); await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerSecretRouter, { prefix: "/secrets" }); await server.register(registerSecretRouter, { prefix: "/secrets" });
await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" }); await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" });
await server.register(registerExternalMigrationRouter, { prefix: "/migrate" }); await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" });
}; };

View File

@@ -33,6 +33,7 @@ export enum AppConnection {
Bitbucket = "bitbucket", Bitbucket = "bitbucket",
Checkly = "checkly", Checkly = "checkly",
Supabase = "supabase", Supabase = "supabase",
DigitalOcean = "digital-ocean",
Okta = "okta" Okta = "okta"
} }

View File

@@ -68,6 +68,11 @@ import {
getDatabricksConnectionListItem, getDatabricksConnectionListItem,
validateDatabricksConnectionCredentials validateDatabricksConnectionCredentials
} from "./databricks"; } from "./databricks";
import {
DigitalOceanConnectionMethod,
getDigitalOceanConnectionListItem,
validateDigitalOceanConnectionCredentials
} from "./digital-ocean";
import { FlyioConnectionMethod, getFlyioConnectionListItem, validateFlyioConnectionCredentials } from "./flyio"; import { FlyioConnectionMethod, getFlyioConnectionListItem, validateFlyioConnectionCredentials } from "./flyio";
import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp"; import { GcpConnectionMethod, getGcpConnectionListItem, validateGcpConnectionCredentials } from "./gcp";
import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github"; import { getGitHubConnectionListItem, GitHubConnectionMethod, validateGitHubConnectionCredentials } from "./github";
@@ -157,6 +162,7 @@ export const listAppConnectionOptions = () => {
getBitbucketConnectionListItem(), getBitbucketConnectionListItem(),
getChecklyConnectionListItem(), getChecklyConnectionListItem(),
getSupabaseConnectionListItem(), getSupabaseConnectionListItem(),
getDigitalOceanConnectionListItem(),
getOktaConnectionListItem() getOktaConnectionListItem()
].sort((a, b) => a.name.localeCompare(b.name)); ].sort((a, b) => a.name.localeCompare(b.name));
}; };
@@ -244,6 +250,7 @@ export const validateAppConnectionCredentials = async (
[AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Bitbucket]: validateBitbucketConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Checkly]: validateChecklyConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Supabase]: validateSupabaseConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.DigitalOcean]: validateDigitalOceanConnectionCredentials as TAppConnectionCredentialsValidator,
[AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator [AppConnection.Okta]: validateOktaConnectionCredentials as TAppConnectionCredentialsValidator
}; };
@@ -283,6 +290,7 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
case CloudflareConnectionMethod.APIToken: case CloudflareConnectionMethod.APIToken:
case BitbucketConnectionMethod.ApiToken: case BitbucketConnectionMethod.ApiToken:
case ZabbixConnectionMethod.ApiToken: case ZabbixConnectionMethod.ApiToken:
case DigitalOceanConnectionMethod.ApiToken:
case OktaConnectionMethod.ApiToken: case OktaConnectionMethod.ApiToken:
return "API Token"; return "API Token";
case PostgresConnectionMethod.UsernameAndPassword: case PostgresConnectionMethod.UsernameAndPassword:
@@ -372,6 +380,7 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record<
[AppConnection.Bitbucket]: platformManagedCredentialsNotSupported, [AppConnection.Bitbucket]: platformManagedCredentialsNotSupported,
[AppConnection.Checkly]: platformManagedCredentialsNotSupported, [AppConnection.Checkly]: platformManagedCredentialsNotSupported,
[AppConnection.Supabase]: platformManagedCredentialsNotSupported, [AppConnection.Supabase]: platformManagedCredentialsNotSupported,
[AppConnection.DigitalOcean]: platformManagedCredentialsNotSupported,
[AppConnection.Okta]: platformManagedCredentialsNotSupported [AppConnection.Okta]: platformManagedCredentialsNotSupported
}; };

View File

@@ -35,6 +35,7 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
[AppConnection.Bitbucket]: "Bitbucket", [AppConnection.Bitbucket]: "Bitbucket",
[AppConnection.Checkly]: "Checkly", [AppConnection.Checkly]: "Checkly",
[AppConnection.Supabase]: "Supabase", [AppConnection.Supabase]: "Supabase",
[AppConnection.DigitalOcean]: "DigitalOcean App Platform",
[AppConnection.Okta]: "Okta" [AppConnection.Okta]: "Okta"
}; };
@@ -73,5 +74,6 @@ export const APP_CONNECTION_PLAN_MAP: Record<AppConnection, AppConnectionPlanTyp
[AppConnection.Bitbucket]: AppConnectionPlanType.Regular, [AppConnection.Bitbucket]: AppConnectionPlanType.Regular,
[AppConnection.Checkly]: AppConnectionPlanType.Regular, [AppConnection.Checkly]: AppConnectionPlanType.Regular,
[AppConnection.Supabase]: AppConnectionPlanType.Regular, [AppConnection.Supabase]: AppConnectionPlanType.Regular,
[AppConnection.DigitalOcean]: AppConnectionPlanType.Regular,
[AppConnection.Okta]: AppConnectionPlanType.Regular [AppConnection.Okta]: AppConnectionPlanType.Regular
}; };

View File

@@ -61,6 +61,8 @@ import { ValidateCloudflareConnectionCredentialsSchema } from "./cloudflare/clou
import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service"; import { cloudflareConnectionService } from "./cloudflare/cloudflare-connection-service";
import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks"; import { ValidateDatabricksConnectionCredentialsSchema } from "./databricks";
import { databricksConnectionService } from "./databricks/databricks-connection-service"; import { databricksConnectionService } from "./databricks/databricks-connection-service";
import { ValidateDigitalOceanConnectionCredentialsSchema } from "./digital-ocean";
import { digitalOceanAppPlatformConnectionService } from "./digital-ocean/digital-ocean-connection-service";
import { ValidateFlyioConnectionCredentialsSchema } from "./flyio"; import { ValidateFlyioConnectionCredentialsSchema } from "./flyio";
import { flyioConnectionService } from "./flyio/flyio-connection-service"; import { flyioConnectionService } from "./flyio/flyio-connection-service";
import { ValidateGcpConnectionCredentialsSchema } from "./gcp"; import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
@@ -145,6 +147,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
[AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema, [AppConnection.Bitbucket]: ValidateBitbucketConnectionCredentialsSchema,
[AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema, [AppConnection.Checkly]: ValidateChecklyConnectionCredentialsSchema,
[AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema, [AppConnection.Supabase]: ValidateSupabaseConnectionCredentialsSchema,
[AppConnection.DigitalOcean]: ValidateDigitalOceanConnectionCredentialsSchema,
[AppConnection.Okta]: ValidateOktaConnectionCredentialsSchema [AppConnection.Okta]: ValidateOktaConnectionCredentialsSchema
}; };
@@ -607,6 +610,7 @@ export const appConnectionServiceFactory = ({
bitbucket: bitbucketConnectionService(connectAppConnectionById), bitbucket: bitbucketConnectionService(connectAppConnectionById),
checkly: checklyConnectionService(connectAppConnectionById), checkly: checklyConnectionService(connectAppConnectionById),
supabase: supabaseConnectionService(connectAppConnectionById), supabase: supabaseConnectionService(connectAppConnectionById),
digitalOcean: digitalOceanAppPlatformConnectionService(connectAppConnectionById),
okta: oktaConnectionService(connectAppConnectionById) okta: oktaConnectionService(connectAppConnectionById)
}; };
}; };

View File

@@ -87,6 +87,12 @@ import {
TDatabricksConnectionInput, TDatabricksConnectionInput,
TValidateDatabricksConnectionCredentialsSchema TValidateDatabricksConnectionCredentialsSchema
} from "./databricks"; } from "./databricks";
import {
TDigitalOceanConnection,
TDigitalOceanConnectionConfig,
TDigitalOceanConnectionInput,
TValidateDigitalOceanCredentialsSchema
} from "./digital-ocean";
import { import {
TFlyioConnection, TFlyioConnection,
TFlyioConnectionConfig, TFlyioConnectionConfig,
@@ -238,6 +244,7 @@ export type TAppConnection = { id: string } & (
| TRailwayConnection | TRailwayConnection
| TChecklyConnection | TChecklyConnection
| TSupabaseConnection | TSupabaseConnection
| TDigitalOceanConnection
| TOktaConnection | TOktaConnection
); );
@@ -280,6 +287,7 @@ export type TAppConnectionInput = { id: string } & (
| TRailwayConnectionInput | TRailwayConnectionInput
| TChecklyConnectionInput | TChecklyConnectionInput
| TSupabaseConnectionInput | TSupabaseConnectionInput
| TDigitalOceanConnectionInput
| TOktaConnectionInput | TOktaConnectionInput
); );
@@ -330,6 +338,7 @@ export type TAppConnectionConfig =
| TRailwayConnectionConfig | TRailwayConnectionConfig
| TChecklyConnectionConfig | TChecklyConnectionConfig
| TSupabaseConnectionConfig | TSupabaseConnectionConfig
| TDigitalOceanConnectionConfig
| TOktaConnectionConfig; | TOktaConnectionConfig;
export type TValidateAppConnectionCredentialsSchema = export type TValidateAppConnectionCredentialsSchema =
@@ -367,6 +376,7 @@ export type TValidateAppConnectionCredentialsSchema =
| TValidateRailwayConnectionCredentialsSchema | TValidateRailwayConnectionCredentialsSchema
| TValidateChecklyConnectionCredentialsSchema | TValidateChecklyConnectionCredentialsSchema
| TValidateSupabaseConnectionCredentialsSchema | TValidateSupabaseConnectionCredentialsSchema
| TValidateDigitalOceanCredentialsSchema
| TValidateOktaConnectionCredentialsSchema; | TValidateOktaConnectionCredentialsSchema;
export type TListAwsConnectionKmsKeys = { export type TListAwsConnectionKmsKeys = {

View File

@@ -14,13 +14,13 @@ import {
} from "./azure-app-configuration-connection-types"; } from "./azure-app-configuration-connection-types";
export const getAzureAppConfigurationConnectionListItem = () => { export const getAzureAppConfigurationConnectionListItem = () => {
const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); const { INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID } = getConfig();
return { return {
name: "Azure App Configuration" as const, name: "Azure App Configuration" as const,
app: AppConnection.AzureAppConfiguration as const, app: AppConnection.AzureAppConfiguration as const,
methods: Object.values(AzureAppConfigurationConnectionMethod) as [AzureAppConfigurationConnectionMethod.OAuth], methods: Object.values(AzureAppConfigurationConnectionMethod) as [AzureAppConfigurationConnectionMethod.OAuth],
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID oauthClientId: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID
}; };
}; };
@@ -29,9 +29,16 @@ export const validateAzureAppConfigurationConnectionCredentials = async (
) => { ) => {
const { credentials: inputCredentials, method } = config; const { credentials: inputCredentials, method } = config;
const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig(); const {
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID,
INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET,
SITE_URL
} = getConfig();
if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) { if (
!INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID ||
!INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET
) {
throw new InternalServerError({ throw new InternalServerError({
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured` message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
}); });
@@ -47,8 +54,8 @@ export const validateAzureAppConfigurationConnectionCredentials = async (
grant_type: "authorization_code", grant_type: "authorization_code",
code: inputCredentials.code, code: inputCredentials.code,
scope: `openid offline_access https://azconfig.io/.default`, scope: `openid offline_access https://azconfig.io/.default`,
client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID, client_id: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_ID,
client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET, client_secret: INF_APP_CONNECTION_AZURE_APP_CONFIGURATION_CLIENT_SECRET,
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback` redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
}) })
); );

View File

@@ -1,3 +1,4 @@
export enum AzureClientSecretsConnectionMethod { export enum AzureClientSecretsConnectionMethod {
OAuth = "oauth" OAuth = "oauth",
ClientSecret = "client-secret"
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable no-case-declarations */
import { AxiosError, AxiosResponse } from "axios"; import { AxiosError, AxiosResponse } from "axios";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
@@ -16,18 +17,22 @@ import { AppConnection } from "../app-connection-enums";
import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums"; import { AzureClientSecretsConnectionMethod } from "./azure-client-secrets-connection-enums";
import { import {
ExchangeCodeAzureResponse, ExchangeCodeAzureResponse,
TAzureClientSecretsConnectionClientSecretCredentials,
TAzureClientSecretsConnectionConfig, TAzureClientSecretsConnectionConfig,
TAzureClientSecretsConnectionCredentials TAzureClientSecretsConnectionCredentials
} from "./azure-client-secrets-connection-types"; } from "./azure-client-secrets-connection-types";
export const getAzureClientSecretsConnectionListItem = () => { export const getAzureClientSecretsConnectionListItem = () => {
const { INF_APP_CONNECTION_AZURE_CLIENT_ID } = getConfig(); const { INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID } = getConfig();
return { return {
name: "Azure Client Secrets" as const, name: "Azure Client Secrets" as const,
app: AppConnection.AzureClientSecrets as const, app: AppConnection.AzureClientSecrets as const,
methods: Object.values(AzureClientSecretsConnectionMethod) as [AzureClientSecretsConnectionMethod.OAuth], methods: Object.values(AzureClientSecretsConnectionMethod) as [
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_ID AzureClientSecretsConnectionMethod.OAuth,
AzureClientSecretsConnectionMethod.ClientSecret
],
oauthClientId: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID
}; };
}; };
@@ -37,12 +42,6 @@ export const getAzureConnectionAccessToken = async (
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey"> kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">
) => { ) => {
const appCfg = getConfig(); const appCfg = getConfig();
if (!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID || !appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
throw new BadRequestError({
message: `Azure environment variables have not been configured`
});
}
const appConnection = await appConnectionDAL.findById(connectionId); const appConnection = await appConnectionDAL.findById(connectionId);
if (!appConnection) { if (!appConnection) {
@@ -63,104 +62,195 @@ export const getAzureConnectionAccessToken = async (
const { refreshToken } = credentials; const { refreshToken } = credentials;
const currentTime = Date.now(); const currentTime = Date.now();
switch (appConnection.method) {
case AzureClientSecretsConnectionMethod.OAuth:
if (
!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID ||
!appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET
) {
throw new BadRequestError({
message: `Azure OAuth environment variables have not been configured`
});
}
const { data } = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", credentials.tenantId || "common"),
new URLSearchParams({
grant_type: "refresh_token",
scope: `openid offline_access https://graph.microsoft.com/.default`,
client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID,
client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET,
refresh_token: refreshToken
})
);
const { data } = await request.post<ExchangeCodeAzureResponse>( const updatedCredentials = {
IntegrationUrls.AZURE_TOKEN_URL.replace("common", credentials.tenantId || "common"), ...credentials,
new URLSearchParams({ accessToken: data.access_token,
grant_type: "refresh_token", expiresAt: currentTime + data.expires_in * 1000,
scope: `openid offline_access https://graph.microsoft.com/.default`, refreshToken: data.refresh_token
client_id: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_ID, };
client_secret: appCfg.INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
refresh_token: refreshToken
})
);
const updatedCredentials = { const encryptedCredentials = await encryptAppConnectionCredentials({
...credentials, credentials: updatedCredentials,
accessToken: data.access_token, orgId: appConnection.orgId,
expiresAt: currentTime + data.expires_in * 1000, kmsService
refreshToken: data.refresh_token });
};
const encryptedCredentials = await encryptAppConnectionCredentials({ await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials });
credentials: updatedCredentials,
orgId: appConnection.orgId,
kmsService
});
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials }); return data.access_token;
case AzureClientSecretsConnectionMethod.ClientSecret:
const accessTokenCredentials = (await decryptAppConnectionCredentials({
orgId: appConnection.orgId,
kmsService,
encryptedCredentials: appConnection.encryptedCredentials
})) as TAzureClientSecretsConnectionClientSecretCredentials;
const { accessToken, expiresAt, clientId, clientSecret, tenantId } = accessTokenCredentials;
if (accessToken && expiresAt && expiresAt > currentTime + 300000) {
return accessToken;
}
return data.access_token; const { data: clientData } = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", tenantId || "common"),
new URLSearchParams({
grant_type: "client_credentials",
scope: `https://graph.microsoft.com/.default`,
client_id: clientId,
client_secret: clientSecret
})
);
const updatedClientCredentials = {
...accessTokenCredentials,
accessToken: clientData.access_token,
expiresAt: currentTime + clientData.expires_in * 1000
};
const encryptedClientCredentials = await encryptAppConnectionCredentials({
credentials: updatedClientCredentials,
orgId: appConnection.orgId,
kmsService
});
await appConnectionDAL.updateById(appConnection.id, { encryptedCredentials: encryptedClientCredentials });
return clientData.access_token;
default:
throw new InternalServerError({
message: `Unhandled Azure connection method: ${appConnection.method as AzureClientSecretsConnectionMethod}`
});
}
}; };
export const validateAzureClientSecretsConnectionCredentials = async (config: TAzureClientSecretsConnectionConfig) => { export const validateAzureClientSecretsConnectionCredentials = async (config: TAzureClientSecretsConnectionConfig) => {
const { credentials: inputCredentials, method } = config; const { credentials: inputCredentials, method } = config;
const { INF_APP_CONNECTION_AZURE_CLIENT_ID, INF_APP_CONNECTION_AZURE_CLIENT_SECRET, SITE_URL } = getConfig(); const {
INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID,
if (!SITE_URL) { INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET,
throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" }); SITE_URL
} } = getConfig();
if (!INF_APP_CONNECTION_AZURE_CLIENT_ID || !INF_APP_CONNECTION_AZURE_CLIENT_SECRET) {
throw new InternalServerError({
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
});
}
let tokenResp: AxiosResponse<ExchangeCodeAzureResponse> | null = null;
let tokenError: AxiosError | null = null;
try {
tokenResp = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", inputCredentials.tenantId || "common"),
new URLSearchParams({
grant_type: "authorization_code",
code: inputCredentials.code,
scope: `openid offline_access https://graph.microsoft.com/.default`,
client_id: INF_APP_CONNECTION_AZURE_CLIENT_ID,
client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRET,
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
})
);
} catch (e: unknown) {
if (e instanceof AxiosError) {
tokenError = e;
} else {
throw new BadRequestError({
message: `Unable to validate connection: verify credentials`
});
}
}
if (tokenError) {
if (tokenError instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to get access token: ${
(tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error"
}`
});
} else {
throw new InternalServerError({
message: "Failed to get access token"
});
}
}
if (!tokenResp) {
throw new InternalServerError({
message: `Failed to get access token: Token was empty with no error`
});
}
switch (method) { switch (method) {
case AzureClientSecretsConnectionMethod.OAuth: case AzureClientSecretsConnectionMethod.OAuth:
if (!SITE_URL) {
throw new InternalServerError({ message: "SITE_URL env var is required to complete Azure OAuth flow" });
}
if (
!INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID ||
!INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET
) {
throw new InternalServerError({
message: `Azure ${getAppConnectionMethodName(method)} environment variables have not been configured`
});
}
let tokenResp: AxiosResponse<ExchangeCodeAzureResponse> | null = null;
let tokenError: AxiosError | null = null;
try {
tokenResp = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", inputCredentials.tenantId || "common"),
new URLSearchParams({
grant_type: "authorization_code",
code: inputCredentials.code,
scope: `openid offline_access https://graph.microsoft.com/.default`,
client_id: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_ID,
client_secret: INF_APP_CONNECTION_AZURE_CLIENT_SECRETS_CLIENT_SECRET,
redirect_uri: `${SITE_URL}/organization/app-connections/azure/oauth/callback`
})
);
} catch (e: unknown) {
if (e instanceof AxiosError) {
tokenError = e;
} else {
throw new BadRequestError({
message: `Unable to validate connection: verify credentials`
});
}
}
if (tokenError) {
if (tokenError instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to get access token: ${
(tokenError?.response?.data as { error_description?: string })?.error_description || "Unknown error"
}`
});
} else {
throw new InternalServerError({
message: "Failed to get access token"
});
}
}
if (!tokenResp) {
throw new InternalServerError({
message: `Failed to get access token: Token was empty with no error`
});
}
return { return {
tenantId: inputCredentials.tenantId, tenantId: inputCredentials.tenantId,
accessToken: tokenResp.data.access_token, accessToken: tokenResp.data.access_token,
refreshToken: tokenResp.data.refresh_token, refreshToken: tokenResp.data.refresh_token,
expiresAt: Date.now() + tokenResp.data.expires_in * 1000 expiresAt: Date.now() + tokenResp.data.expires_in * 1000
}; };
case AzureClientSecretsConnectionMethod.ClientSecret:
const { tenantId, clientId, clientSecret } = inputCredentials;
try {
const { data: clientData } = await request.post<ExchangeCodeAzureResponse>(
IntegrationUrls.AZURE_TOKEN_URL.replace("common", tenantId || "common"),
new URLSearchParams({
grant_type: "client_credentials",
scope: `https://graph.microsoft.com/.default`,
client_id: clientId,
client_secret: clientSecret
})
);
return {
tenantId,
accessToken: clientData.access_token,
expiresAt: Date.now() + clientData.expires_in * 1000,
clientId,
clientSecret
};
} catch (e: unknown) {
if (e instanceof AxiosError) {
throw new BadRequestError({
message: `Failed to get access token: ${
(e?.response?.data as { error_description?: string })?.error_description || "Unknown error"
}`
});
} else {
throw new InternalServerError({
message: "Failed to get access token"
});
}
}
default: default:
throw new InternalServerError({ throw new InternalServerError({
message: `Unhandled Azure connection method: ${method as AzureClientSecretsConnectionMethod}` message: `Unhandled Azure connection method: ${method as AzureClientSecretsConnectionMethod}`

View File

@@ -26,6 +26,36 @@ export const AzureClientSecretsConnectionOAuthOutputCredentialsSchema = z.object
expiresAt: z.number() expiresAt: z.number()
}); });
export const AzureClientSecretsConnectionClientSecretInputCredentialsSchema = z.object({
clientId: z
.string()
.uuid()
.trim()
.min(1, "Client ID required")
.max(50, "Client ID must be at most 50 characters long")
.describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientId),
clientSecret: z
.string()
.trim()
.min(1, "Client Secret required")
.max(50, "Client Secret must be at most 50 characters long")
.describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.clientSecret),
tenantId: z
.string()
.uuid()
.trim()
.min(1, "Tenant ID required")
.describe(AppConnections.CREDENTIALS.AZURE_CLIENT_SECRETS.tenantId)
});
export const AzureClientSecretsConnectionClientSecretOutputCredentialsSchema = z.object({
clientId: z.string(),
clientSecret: z.string(),
tenantId: z.string(),
accessToken: z.string(),
expiresAt: z.number()
});
export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discriminatedUnion("method", [ export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discriminatedUnion("method", [
z.object({ z.object({
method: z method: z
@@ -34,6 +64,14 @@ export const ValidateAzureClientSecretsConnectionCredentialsSchema = z.discrimin
credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.describe( credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials
) )
}),
z.object({
method: z
.literal(AzureClientSecretsConnectionMethod.ClientSecret)
.describe(AppConnections.CREATE(AppConnection.AzureClientSecrets).method),
credentials: AzureClientSecretsConnectionClientSecretInputCredentialsSchema.describe(
AppConnections.CREATE(AppConnection.AzureClientSecrets).credentials
)
}) })
]); ]);
@@ -43,9 +81,13 @@ export const CreateAzureClientSecretsConnectionSchema = ValidateAzureClientSecre
export const UpdateAzureClientSecretsConnectionSchema = z export const UpdateAzureClientSecretsConnectionSchema = z
.object({ .object({
credentials: AzureClientSecretsConnectionOAuthInputCredentialsSchema.optional().describe( credentials: z
AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials .union([
) AzureClientSecretsConnectionOAuthInputCredentialsSchema,
AzureClientSecretsConnectionClientSecretInputCredentialsSchema
])
.optional()
.describe(AppConnections.UPDATE(AppConnection.AzureClientSecrets).credentials)
}) })
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureClientSecrets)); .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.AzureClientSecrets));
@@ -59,6 +101,10 @@ export const AzureClientSecretsConnectionSchema = z.intersection(
z.object({ z.object({
method: z.literal(AzureClientSecretsConnectionMethod.OAuth), method: z.literal(AzureClientSecretsConnectionMethod.OAuth),
credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema
}),
z.object({
method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret),
credentials: AzureClientSecretsConnectionClientSecretOutputCredentialsSchema
}) })
]) ])
); );
@@ -69,6 +115,13 @@ export const SanitizedAzureClientSecretsConnectionSchema = z.discriminatedUnion(
credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema.pick({ credentials: AzureClientSecretsConnectionOAuthOutputCredentialsSchema.pick({
tenantId: true tenantId: true
}) })
}),
BaseAzureClientSecretsConnectionSchema.extend({
method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret),
credentials: AzureClientSecretsConnectionClientSecretOutputCredentialsSchema.pick({
clientId: true,
tenantId: true
})
}) })
]); ]);

View File

@@ -4,6 +4,7 @@ import { DiscriminativePick } from "@app/lib/types";
import { AppConnection } from "../app-connection-enums"; import { AppConnection } from "../app-connection-enums";
import { import {
AzureClientSecretsConnectionClientSecretOutputCredentialsSchema,
AzureClientSecretsConnectionOAuthOutputCredentialsSchema, AzureClientSecretsConnectionOAuthOutputCredentialsSchema,
AzureClientSecretsConnectionSchema, AzureClientSecretsConnectionSchema,
CreateAzureClientSecretsConnectionSchema, CreateAzureClientSecretsConnectionSchema,
@@ -30,6 +31,10 @@ export type TAzureClientSecretsConnectionCredentials = z.infer<
typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema typeof AzureClientSecretsConnectionOAuthOutputCredentialsSchema
>; >;
export type TAzureClientSecretsConnectionClientSecretCredentials = z.infer<
typeof AzureClientSecretsConnectionClientSecretOutputCredentialsSchema
>;
export interface ExchangeCodeAzureResponse { export interface ExchangeCodeAzureResponse {
token_type: string; token_type: string;
scope: string; scope: string;

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