1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-04-02 14:38:48 +00:00

Compare commits

...

314 Commits

Author SHA1 Message Date
45b9de63f0 Merge pull request from Infisical/daniel/fix-cleanup-job-fail
fix: `timestamp out of range` error during daily cleanup
2024-10-24 19:52:12 +04:00
114966ded4 fix: LEAST() to fix TTL's with high values causing timestamp out of range 2024-10-24 19:40:20 +04:00
71081d8e9a Merge pull request from Infisical/daniel/operator-auth-refresh
feat: automatic k8 operator token refreshing
2024-10-24 10:42:46 -04:00
dad3d50f3e Merge pull request from Infisical/daniel/fix-aws-local-login
fix(cli): aws-iam local authentication
2024-10-24 17:48:42 +04:00
e5ca5d3da2 Update test-TestUniversalAuth_SecretsGetWrongEnvironment 2024-10-24 17:45:00 +04:00
301cd54dc3 chore: bumped go sdk version 2024-10-24 17:33:51 +04:00
593bda8bc6 Merge pull request from Infisical/feat/azure-app-configuration
feat: azure app configuration integration
2024-10-24 19:37:38 +08:00
4db79edf19 misc: addressed review comments 2024-10-24 19:20:21 +08:00
e3a356cda9 updated go sdk 2024-10-24 14:47:00 +04:00
521b24debf Merge pull request from Infisical/feat/add-assume-role-support-for-aws-parameter-store
feat: add assume role support for aws parameter store
2024-10-24 02:48:29 +08:00
ca3b64bf6c Merge pull request from mtariqsajid/patch-1
jenkins integration is now available
2024-10-23 08:32:37 -04:00
b7e48fd556 feat: add assume role support for aws parameter store 2024-10-23 19:56:47 +08:00
c01ea048ce Merge pull request from scott-ray-wilson/fix-region-display
Fix: refine check of when to display region select
2024-10-22 23:58:15 -04:00
7e7d9a2bd5 fix: refine check of when to display region select 2024-10-22 20:56:10 -07:00
782e3a8985 jenkins integration is now available 2024-10-23 01:25:13 +05:00
8497ac831f Merge pull request from Infisical/feat/allow-approvers-to-bypass-secret-change-requests
feat: allow approvers to bypass secret change requests
2024-10-22 22:28:52 +04:00
e5821122d5 Merge pull request from Infisical/feat/moved-mfa-to-org-level
feat: moved mfa to org level
2024-10-22 14:14:48 -04:00
340693cfcd feat: allow approvers to bypass secret change requests 2024-10-23 01:54:40 +08:00
014b9585e0 Merge pull request from Infisical/azure-permission-docs
Add permission note for Azure Key Vault (KV) integration documentation
2024-10-22 09:48:25 -07:00
67373b0883 Add permission note for azure kv integration 2024-10-22 09:43:36 -07:00
2101040a77 misc: updated e2e 2024-10-23 00:13:10 +08:00
2e2fea304b Merge remote-tracking branch 'origin/main' into feat/moved-mfa-to-org-level 2024-10-23 00:04:09 +08:00
571709370d misc: addressed ux issues 2024-10-23 00:00:15 +08:00
e1dbe769a8 doc: added documentation for azure app configuration 2024-10-22 21:41:44 +08:00
e7e0d84c8e feat: azure app configuration integration 2024-10-22 19:41:10 +08:00
4c2ed1cc8b Merge pull request from scott-ray-wilson/fix-uw-secret-overview-overflow
Fix: cap width for tw container size on uw monitors
2024-10-22 00:48:48 -04:00
067b0f4232 fix: cap width for tw container size on uw monitors 2024-10-21 20:58:28 -07:00
6ed786e6d0 Merge pull request from Infisical/daniel/go-sdk-docs-update
docs: go SDK refreshing docs
2024-10-21 19:23:00 -04:00
d187cc3d4d Merge pull request from Infisical/daniel/error-context
feat: more contextual not found errors
2024-10-21 19:09:12 -04:00
764446a2d9 update small missing ' 2024-10-21 19:06:02 -04:00
614e4934a2 Merge pull request from scott-ray-wilson/incorrect-import-value-display-fix
Fix: Correct Secret Value Override Display on Overview Table
2024-10-21 14:18:43 -04:00
14e92f895a fix: only override secret value/id by key if same environment 2024-10-21 10:55:39 -07:00
0a38374a73 Merge pull request from Infisical/daniel/cli-snapshot-update
fix: cli snapshot test error message change
2024-10-21 21:29:34 +04:00
ec3b94a335 fix: snapshot error message change 2024-10-21 21:27:16 +04:00
ca0241bb51 improvement: improve empty table labeling for org group/users 2024-10-21 10:20:43 -07:00
7403385e7c fix: fix select all rows checkbox being checked if no folders/secrets 2024-10-21 09:45:56 -07:00
2cd1141a65 Merge pull request from scott-ray-wilson/group-tables-fixes/improvements
Fix: Group Tables/Pagination Fixes and Improvements
2024-10-21 09:39:00 -07:00
256627b2cc Update go.mdx 2024-10-21 20:38:32 +04:00
fd7e196f8b Merge pull request from Infisical/misc/export-org-data-feature
feat: add migration script to migrate org
2024-10-21 10:16:41 -04:00
212748f140 misc: added cleanup of global/instance-level resources 2024-10-21 21:19:55 +08:00
b61582a60e Merge remote-tracking branch 'origin/main' into misc/export-org-data-feature 2024-10-21 19:04:02 +08:00
9ca8da152b Update go.mdx 2024-10-21 12:08:33 +04:00
c5aa1b8664 Merge pull request from Infisical/vmatsiiako-patch-docsimage-1
Update group-mappings.mdx
2024-10-20 21:18:48 -04:00
90dbb417ac Update group-mappings.mdx 2024-10-20 18:17:20 -07:00
7fb3076238 fix: added sdk context support 2024-10-19 08:40:02 +04:00
946651496f Merge pull request from Infisical/daniel/rate-limit-error
fix: better rate limit errors
2024-10-19 07:25:46 +04:00
5a8ac850b5 fix: variable naming 2024-10-19 06:41:29 +04:00
77a88f1575 feat: better rate limit errors 2024-10-19 06:35:49 +04:00
c6f66226c8 feat: more contextual not found errors 2024-10-19 05:00:14 +04:00
be00d13a46 Merge pull request from scott-ray-wilson/improve-overview-table-overflow
Improvement: Cap Expanded Secret View Width when Overview Table Overflows
2024-10-18 19:14:42 -04:00
84814a0012 improvement: improve handling of expanded secret when table overflows 2024-10-18 16:06:25 -07:00
a0865cda2e fix: enable sdk silent mode 2024-10-19 02:59:29 +04:00
de03692469 Merge pull request from scott-ray-wilson/select-all-secrets-page
Feat: Select All Rows for Secrets Tables
2024-10-18 18:30:35 -04:00
fb2d3e4eb7 Merge pull request from scott-ray-wilson/scim-group-mapping-docs
Docs: SCIM Group Mapping and SCIM/Organization Doc Improvements
2024-10-18 18:06:22 -04:00
29150e809d Merge pull request from Infisical/misc/allow-secret-scanning-whitelist
misc: added secret scanning whitelist configuration
2024-10-18 18:03:54 -04:00
e18a606b23 improvements: adjust UI for alignment and remove checkbox separator 2024-10-18 14:47:31 -07:00
67708411cd update tooltip for k8 2024-10-18 17:43:37 -04:00
1e7b1ccf22 feat: automatic token refreshing 2024-10-19 01:38:11 +04:00
3e4bd28916 Merge pull request from scott-ray-wilson/fix-default-tag-color
Fix: Set Default Value for Color in Tags Modal
2024-10-18 14:12:34 -07:00
a2e16370fa fix: set default value for color in tags modal 2024-10-18 14:06:38 -07:00
d677654311 improvement: org user groups tables search fixed and col sort added and group add users pagination fixed and search improved to include first and last name 2024-10-18 13:20:17 -07:00
903fac1005 misc: added infisical cli to docker and fixed redirect 2024-10-19 03:18:13 +08:00
ff045214d6 improve readability 2024-10-18 11:59:23 -07:00
57dcf5ab28 docs: scim group mapping and scim/org improvements 2024-10-18 11:57:36 -07:00
959a5ec55b misc: added secret scanning whitelist conig 2024-10-19 01:59:45 +08:00
b22a93a175 Merge pull request from akhilmhdh/feat/org-kms-ui
feat: added organization kms in org role permission section
2024-10-18 21:59:56 +05:30
5debeb421d Merge remote-tracking branch 'origin/main' into feat/moved-mfa-to-org-level 2024-10-18 20:07:59 +08:00
25b30e441a misc: added missing enforcement checks 2024-10-18 19:51:31 +08:00
d7d88f3356 Merge pull request from Infisical/vmatsiiako-patch-scim-docs
Update azure.mdx
2024-10-17 21:50:00 -07:00
dbaef9d227 Update azure.mdx 2024-10-17 21:42:45 -07:00
38d8b14b03 Merge pull request from Infisical/revert-2557-feat/permission-phase-2
Revert "Permission phase 2"
2024-10-17 17:38:08 -04:00
8b9244b079 Revert "Permission phase 2" 2024-10-17 17:37:41 -04:00
3d938ea62f Merge pull request from Infisical/revert-2605-feat/permission-phase-2
Revert "feat: added filter folder to remove read only in migration"
2024-10-17 17:36:38 -04:00
78f668bd7f Revert "feat: added filter folder to remove read only in migration" 2024-10-17 17:36:25 -04:00
13c0b315a4 Merge pull request from akhilmhdh/feat/permission-phase-2
feat: added filter folder to remove read only in migration
2024-10-17 16:07:14 -04:00
=
99e65f7b59 feat: added filter folder to remove read only in migration 2024-10-18 01:35:15 +05:30
96bad7bf90 Merge pull request from akhilmhdh/feat/permission-phase-2
Permission phase 2
2024-10-17 15:47:04 -04:00
=
5e5f20cab2 feat: small fix in ui for delete root cred 2024-10-18 01:01:31 +05:30
8eb668cd72 misc: removed remaining mfa handling 2024-10-18 03:26:48 +08:00
=
2383c93139 feat: changed dynamic secret mapping to new one, made optional secretname and tag in permission 2024-10-18 00:33:38 +05:30
154ea9e55d fix: correct delete secret UI permission check with path included 2024-10-18 00:33:38 +05:30
d36a9e2000 fix: correct dummy row display count 2024-10-18 00:33:38 +05:30
=
6f334e4cab fix: resolved rebase and missing import module 2024-10-18 00:33:37 +05:30
=
700c5409bf feat: resolved additional privilege not taking priority and dummy column miscalculation 2024-10-18 00:33:37 +05:30
=
6158b8a91d feat: corrected dummy column in overview and main page 2024-10-18 00:33:37 +05:30
=
0c3024819c feat: review comments over dynamic-secrets, folder read, neq removed in backend, contain in tag 2024-10-18 00:33:37 +05:30
c8410ac6f3 fix: keep main page filters enabled by default for UI and only disable query via permissions 2024-10-18 00:33:37 +05:30
41e4af4e65 improvement: adjust policy UI for flow/clarity 2024-10-18 00:33:37 +05:30
=
bac9936c2a fix: added back missing permission 2024-10-18 00:33:37 +05:30
=
936a48f458 feat: addressed backend review changes needed by scott 2024-10-18 00:33:36 +05:30
=
43cfd63660 fix: resolved failing test 2024-10-18 00:33:36 +05:30
=
0f10874f80 feat: added no secret access views 2024-10-18 00:33:36 +05:30
=
a9e6c229d0 feat: completed migration of permission v1 to v2. Pending intense testing 2024-10-18 00:33:36 +05:30
=
7cd83ad945 feat: added lease permission for dynamic secret 2024-10-18 00:33:36 +05:30
=
2f691db0a2 feat: added discarding the wildcard check in frontend for negated rules 2024-10-18 00:33:36 +05:30
=
eb6d5d2fb9 feat: added inverted to project permission 2024-10-18 00:33:36 +05:30
=
fc5487396b feat: added helper text for operators and improved rendering of selective operators 2024-10-18 00:33:35 +05:30
=
6db8c100ba fix: resolved fixes for permission changes 2024-10-18 00:33:35 +05:30
=
acfb4693ee feat: backend fixed bug in permission change 2024-10-18 00:33:35 +05:30
=
aeaabe2c27 feat: rebased and added back missing idempotence in some migration files 2024-10-18 00:33:35 +05:30
=
c60d957269 fix: resolved overlap routes in v2 e2ee 2024-10-18 00:33:35 +05:30
=
b6dc6ffc01 feat: updated frontend project permission logic 2024-10-18 00:33:35 +05:30
=
181821f8f5 feat: removed unused casl mapper 2024-10-18 00:33:35 +05:30
=
6ac44a79b2 feat: added new project role route v2 and new conditions 2024-10-18 00:33:34 +05:30
=
77740d2c86 feat: updated all services with permission changes 2024-10-18 00:33:34 +05:30
=
17567ebd0f feat: completed easier changes on other files where permission is needed 2024-10-18 00:33:34 +05:30
=
7ed0818279 feat: updated folder, secret import partially and dynamic secret service 2024-10-18 00:33:34 +05:30
bb079b3e46 misc: updated cli interactive to support mfa in org select 2024-10-18 01:59:24 +08:00
d94b4b2a3c feat: select all on page for secrets tables and fix multipage select behavior for actions 2024-10-17 10:17:23 -07:00
=
9d90c35629 feat: added organization kms in org role permission section 2024-10-17 20:26:22 +05:30
7a77dc7343 feat: added mfa popup for all select org 2024-10-17 22:29:38 +08:00
2cff772caa Merge pull request from scott-ray-wilson/entra-group-role-mapping
Feature: SCIM Group to Organization Role Mapping
2024-10-16 20:31:26 -04:00
849cad054e Merge pull request from scott-ray-wilson/admin-doc-revisions
Improvements: Revise Admin Console Docs and Server Admin Badge
2024-10-16 17:49:56 -04:00
518ca5fe58 Fix grammar 2024-10-16 17:44:27 -04:00
65e42f980c improvements: revise admin console docs and display server admin badge on users tables 2024-10-16 14:20:40 -07:00
f95957d534 Merge pull request from Infisical/daniel/cli-eu-region
feat: cloud EU region support
2024-10-17 00:11:00 +04:00
bd1ed2614e feat: added enforceMfa toggle for orgs 2024-10-17 03:02:26 +08:00
01920d7a50 fix: proper errors on failed to find env 2024-10-16 22:38:36 +04:00
83ac8abf81 Update init.go 2024-10-16 22:27:11 +04:00
44544e0491 fix: use put instead of post and improve var naming 2024-10-16 11:05:53 -07:00
c47e0d661b Merge pull request from Infisical/feat/github-integration-app-auth
feat: github integration with Github app auth
2024-10-17 02:02:08 +08:00
9192c5caa2 feat: created reusable mfa flow 2024-10-17 02:01:22 +08:00
b0fc5c7e27 fix: correct boolean check for orgId error and improve visual separation of github connections 2024-10-16 10:42:22 -07:00
bf5d7b2ba1 Merge pull request from akhilmhdh/fix/scim-type-removed
feat: field type is not even used in schema so removed as some providers don't provide it
2024-10-16 20:24:11 +05:30
8da2213bf1 misc: removed mfa from existing login 2024-10-16 22:44:04 +08:00
=
5b4c4f4543 feat: field type is not even used in schema so removed as some providers don't provide it 2024-10-16 19:51:20 +05:30
080cf67b8c misc: addressed review comments 2024-10-16 19:54:35 +08:00
36bb954373 Merge pull request from AdityaGoyal1999/docs-fix
Updated docs to use docker compose instead of docker-compose
2024-10-16 13:09:08 +05:30
93afa91239 Merge pull request from akhilmhdh/doc/docker-integration
chore: updated documentation for docker compose and docker for machine identity
2024-10-16 13:06:21 +05:30
73fbf66d4c Merge pull request from Infisical/maidul-uhdgwqudy
prevent sync of empty secret in ssm
2024-10-16 00:27:10 -04:00
8ae0d97973 prevent sync of empty secret in ssm 2024-10-15 18:36:06 -04:00
ca5ec94082 Merge pull request from Infisical/daniel/fix-envkey-missing-project
fix: envkey project imports
2024-10-15 18:05:59 -04:00
5d5da97b45 Update external-migration-fns.ts 2024-10-16 01:58:06 +04:00
d61f36bca8 requested changes 2024-10-16 01:33:57 +04:00
96f5dc7300 Update external-migration-fns.ts 2024-10-16 01:05:45 +04:00
8e5debca90 update password reset 2024-10-15 14:11:28 -04:00
08ed544e52 misc: added missing section regarding enabling of user auth 2024-10-16 01:38:13 +08:00
8c4a26b0e2 feature: scim group org role mapping 2024-10-15 07:57:26 -07:00
bda0681dee Merge pull request from Infisical/misc/increase-identity-metadata-col-length
misc: increase identity metadata col length
2024-10-15 21:06:01 +08:00
cf092d8b4f doc: updated github action docs 2024-10-15 21:01:37 +08:00
a11bcab0db Merge pull request from akhilmhdh/feat/sync-on-shared-sec
feat: only do sync secret and snapshot if its shared secret change
2024-10-15 18:25:20 +05:30
986bcaf0df feat: cloud EU region support 2024-10-15 16:20:48 +04:00
192d1b0be3 misc: finalized ui design 2024-10-15 19:07:39 +08:00
82c8ca9c3d misc: added auto redirect to new connection flow 2024-10-15 19:04:40 +08:00
4a1adb76ab misc: finalized auth method selection ui/ux 2024-10-15 18:21:02 +08:00
94b799e80b misc: finalized variable names 2024-10-15 18:17:57 +08:00
bdae136bed misc: added proper selection of existing github oauth 2024-10-15 17:20:23 +08:00
73e73c5489 misc: increase identity metadata col length 2024-10-15 16:59:13 +08:00
f3bcdf74df Merge pull request from Infisical/daniel/envkey-fix
fix: envkey migration failing due to not using batches
2024-10-14 22:29:54 -07:00
87cd3ea727 fix: envkey migration failing due to not using batches 2024-10-15 09:26:05 +04:00
114f42fc14 Merge pull request from akhilmhdh/feat/secret-path-cli-template
feat: added secret path to template and optional more arguments as js…
2024-10-14 17:19:45 -07:00
6daa1aa221 add example with path 2024-10-14 20:16:39 -04:00
52f85753c5 Merge pull request from dks333/patch-1
Add footer to docs
2024-10-14 14:31:29 -07:00
0a5634aa05 Update mint.json for advanced footer 2024-10-14 14:22:40 -07:00
3e8b9aa296 Merge pull request from akhilmhdh/fix/upgrade-v1-to-v2
feat: added auto ghost user creation and fixed ghost user creation in v1
2024-10-14 13:55:31 -07:00
=
67058d8b55 feat: updated cli docs 2024-10-15 01:49:38 +05:30
=
d112ec2f0a feat: switched expandSecretReferences to server based one and added same support in template too 2024-10-15 01:49:27 +05:30
73382c5363 feat: added handling of using same connection with different projects 2024-10-15 03:37:11 +08:00
=
96c0e718d0 feat: added auto ghost user creation and fixed ghost user creation in v1 2024-10-14 17:37:51 +05:30
522e1dfd0e Merge pull request from Infisical/misc/made-audit-log-endpoint-accessible-by-mi
misc: made audit log endpoint mi accessible
2024-10-14 17:14:43 +08:00
08145f9b96 misc: made audit log endpoint mi accessible 2024-10-14 17:09:49 +08:00
faf2c6df90 misc: moved metadata parsing into github scope 2024-10-14 17:06:28 +08:00
b8f3814df0 feat: added support for app octokit 2024-10-14 16:17:39 +08:00
1f4db2bd80 Merge pull request from Infisical/daniel/stream-upload
fix: env-key large file uploads
2024-10-14 12:11:17 +04:00
d8d784a0bc Update external-migration-router.ts 2024-10-14 12:04:41 +04:00
2dc1416f30 fix: envkey upload timeout 2024-10-14 11:49:26 +04:00
7fdcb29bab Merge pull request from Infisical/daniel/envkey-import-bug
feat: Process Envkey import in queue
2024-10-13 22:48:59 -07:00
6a89e3527c Merge pull request from Infisical/vmatsiiako-changelog-patch-1-1
Update overview.mdx
2024-10-13 14:34:37 -07:00
d1d0667cd5 Update overview.mdx 2024-10-12 22:03:08 -07:00
c176a20010 Updated docs to use docker compose instead of docker-compose 2024-10-12 15:31:41 -04:00
865db5a9b3 removed redundancies 2024-10-12 07:54:21 +04:00
ad2f19658b requested changes 2024-10-12 07:40:14 +04:00
=
bed8efb24c chore: added comment explaning why ...string 2024-10-12 00:41:27 +05:30
=
aa9af7b41c feat: added secret path to template and optional more arguments as json get secrets 2024-10-12 00:39:51 +05:30
=
02fd484632 feat: updated v1 engine sync to be on shared secret mutation 2024-10-11 16:37:08 +05:30
=
96eab464c7 feat: only do sync secret and snapshot if its shared secret change 2024-10-11 16:31:51 +05:30
162005d72f feat: redis-based external imports 2024-10-11 11:15:56 +04:00
09d28156f8 Merge pull request from Infisical/vmatsiiako-readme-patch-1
Update README.md
2024-10-10 19:40:45 -07:00
fc67c496c5 Update README.md 2024-10-10 19:39:51 -07:00
540a1a29b1 Merge pull request from akhilmhdh/fix/scim-error-response
Resolved response schema mismatch for scim
2024-10-10 13:53:33 -07:00
3163adf486 increase depth count 2024-10-10 13:50:03 -07:00
=
e042f9b5e2 feat: made missing errors as internal server error and added depth in scim knex 2024-10-11 01:42:38 +05:30
05a1b5397b Merge pull request from Infisical/daniel/envkey-import-bug
fix: handle undefined variable values
2024-10-10 21:23:08 +04:00
19776df46c fix: handle undefined variable values 2024-10-10 21:13:17 +04:00
64fd65aa52 Update requirements.mdx 2024-10-10 08:58:35 -07:00
=
3d58eba78c fix: resolved response schema mismatch for scim 2024-10-10 18:38:29 +05:30
565884d089 Merge pull request from Infisical/maidul-helm-static-dynamic
Make helm chart more dynamic
2024-10-10 00:05:04 -07:00
2a83da1cb6 update helm chart version 2024-10-10 00:00:56 -07:00
f186ce9649 Add support for existing pg secret 2024-10-09 23:43:37 -07:00
6ecfee5faf Merge pull request from Infisical/daniel/envvar-fix
fix: allow 25MB uploads for migrations
2024-10-09 17:21:09 -07:00
662f1a31f6 fix: allow 25MB uploads for migrations 2024-10-10 03:37:08 +04:00
06f9a1484b Merge pull request from scott-ray-wilson/fix-unintentional-project-creation
Fix: Prevent Example Project Creation on SSO Signup When Joining Org
2024-10-09 15:01:44 -07:00
c90e8ca715 chore: revert prem features 2024-10-09 14:01:16 -07:00
6ddc4ce4b1 fix: prevent example project from being created when joining existing org SSO 2024-10-09 13:58:22 -07:00
4fffac07fd Merge pull request from akhilmhdh/fix/ssm-integration-1-1
fix: resolved ssm failing for empty secret in 1-1 mapping
2024-10-09 13:19:22 -07:00
059c552307 misc: initial setup for github integration with Github app auth 2024-10-10 03:22:25 +08:00
75d71d4208 Merge pull request from scott-ray-wilson/org-default-role
Feat: Default Org Membership Role
2024-10-09 11:55:47 -07:00
e38628509d improvement: address more feedback 2024-10-09 11:52:02 -07:00
0b247176bb improvements: address feedback 2024-10-09 11:52:02 -07:00
faad09961d Update OrgRoleTable.tsx 2024-10-09 22:47:14 +04:00
98d4f808e5 improvement: set intial org role value in dropdown on add user to default org membership value 2024-10-09 11:04:47 -07:00
2ae91db65d Merge pull request from scott-ray-wilson/add-project-users-multi-select
Feature: Multi-Select Component and Improve Adding Users to Project
2024-10-09 10:45:59 -07:00
529328f0ae chore: revert package-lock name 2024-10-09 10:02:42 -07:00
e59d9ff3c6 chore: revert prem features 2024-10-09 10:00:38 -07:00
4aad36601c feature: add multiselect component and improve adding users to project 2024-10-09 09:58:00 -07:00
=
4aaba3ef9f fix: resolved ssm failing for empty secret in 1-1 mapping 2024-10-09 16:06:48 +05:30
b482a9cda7 Add audit log env to prod stage 2024-10-08 20:52:27 -07:00
595eb739af Merge pull request from Infisical/daniel/rpm-binary
feat: rpm binary
2024-10-08 16:08:10 -07:00
b46bbea0c5 fix: removed debug data & re-add compression 2024-10-09 01:48:23 +04:00
6dad24ffde Update build-binaries.yml 2024-10-09 01:39:53 +04:00
f8759b9801 Update build-binaries.yml 2024-10-09 01:14:24 +04:00
049c77c902 Update build-binaries.yml 2024-10-09 00:50:32 +04:00
1478833c9c Merge pull request from scott-ray-wilson/fix-secret-overview-overflow
Improvement: Secret Overview Table Scroll
2024-10-08 13:24:05 -07:00
c8d40c6905 fix for corrupt data 2024-10-09 00:17:48 +04:00
ff815b5f42 Update build-binaries.yml 2024-10-08 23:38:20 +04:00
e5138d0e99 Merge pull request from akhilmhdh/docs/admin-panel
docs: added docs for infisical admin panels
2024-10-08 12:03:00 -07:00
f43725a16e fix: move pagination beneath table container to make overflow-scroll more intuitive 2024-10-08 11:57:54 -07:00
f6c65584bf Update build-binaries.yml 2024-10-08 22:40:33 +04:00
246020729e Update build-binaries.yml 2024-10-08 22:18:15 +04:00
63cc4e347d Update build-binaries.yml 2024-10-08 22:17:59 +04:00
ecaca82d9a improvement: minor adjustments 2024-10-08 11:07:05 -07:00
d6ef0d1c83 Merge pull request from Infisical/daniel/include-env-on-interation
fix: include env on integration api
2024-10-08 22:01:20 +04:00
f2a7f164e1 Trigger build 2024-10-08 21:58:49 +04:00
dfbdc46971 fix: rpm binary 2024-10-08 21:56:58 +04:00
3049f9e719 Merge pull request from Infisical/misc/made-partition-operation-separate
misc: made audit log partition opt-in
2024-10-08 09:39:01 -07:00
391c9abbb0 misc: updated error description 2024-10-08 22:49:11 +08:00
e191a72ca0 misc: finalized env name 2024-10-08 21:38:38 +08:00
68c38f228d misc: moved to using env 2024-10-08 21:29:36 +08:00
a823347c99 misc: added proper deletion of indices 2024-10-08 21:21:32 +08:00
22b417b50b misc: made partition opt-in 2024-10-08 17:53:53 +08:00
98ed063ce6 misc: enabled audit log exploration 2024-10-08 12:52:43 +08:00
c0fb493f57 Merge pull request from Infisical/misc/move-audit-logs-to-dedicated
misc: audit log migration + special handing
2024-10-07 16:04:23 -07:00
eae5e57346 feat: default org membership role 2024-10-07 15:02:14 -07:00
f6fcef24c6 misc: added console statement to partition migration 2024-10-08 02:56:10 +08:00
5bf6f69fca misc: moved to partitionauditlogs schema 2024-10-08 02:44:24 +08:00
acf054d992 fix: include env on integration 2024-10-07 22:05:38 +04:00
56798f09bf Merge pull request from Infisical/daniel/project-env-position-fixes
fix: project environment positions
2024-10-07 21:22:38 +04:00
4c1253dc87 Merge pull request from Infisical/doc/oidc-auth-circle-ci
doc: circle ci oidc auth
2024-10-07 23:26:31 +08:00
09793979c7 Merge pull request from Infisical/meet/eng-1577-lots-of-content-header-issues-in-console
fix: add CSP directive to allow posthog
2024-10-07 18:56:12 +05:30
fa360b8208 fix: add CSP directive to allow posthog 2024-10-07 18:28:14 +05:30
9f6d837a9b feat: add migration script to migrate org 2024-10-07 17:28:32 +05:30
f94e100c30 Update project-env.spec.ts 2024-10-07 13:30:32 +04:00
33b54e78f9 fix: project environment positions 2024-10-07 12:52:59 +04:00
98cca7039c misc: addressed comments 2024-10-07 14:00:20 +08:00
f50b0876e4 Merge pull request from Infisical/maidul-sdsafdf
Remove service token notice
2024-10-06 17:43:02 -07:00
c30763c98f Merge pull request from Infisical/databricks-integration
Databricks integration
2024-10-06 17:36:14 -07:00
6fc95c3ff8 Merge pull request from scott-ray-wilson/kms-keys-temp-slug-col
Fix: Mitigate KMS Key Slug to Name Transition Side-Effects
2024-10-06 17:35:48 -07:00
eef1f2b6ef remove trigger functions 2024-10-05 18:05:50 -07:00
128b1cf856 fix: create separate triggers for insert/update 2024-10-05 11:01:30 -07:00
6b9944001e Merge pull request from akhilmhdh/fix/identity-list
feat: corrected identity pagination in org level
2024-10-05 10:54:09 -07:00
1cc22a6195 improvement: minizime kms key slug -> name transition impact 2024-10-05 10:43:57 -07:00
=
af643468fd feat: corrected identity pagination in org level 2024-10-05 10:50:05 +05:30
f8358a0807 Merge pull request from Infisical/maidul-resolve-identity-count
Resolve identity count issue
2024-10-04 19:00:17 -07:00
3eefb98f30 resolve identity count 2024-10-04 18:58:12 -07:00
8f39f953f8 fix PR review comments for databricks integration 2024-10-04 16:04:00 -07:00
5e4af7e568 Merge pull request from Infisical/daniel/terraform-imports-prerequsuite
feat: terraform imports prerequisite / api improvements
2024-10-05 02:18:46 +04:00
24bd13403a Merge pull request from scott-ray-wilson/kms-fix-doc-link
Fix: Correct KMS Doc Link
2024-10-04 13:43:59 -07:00
4149cbdf07 Merge pull request from Infisical/meet/fix-handlebars-import
fix handlebars import
2024-10-04 12:52:27 -07:00
ced3ab97e8 chore: fix handlebars import 2024-10-05 01:18:13 +05:30
3f7f0a7b0a doc: circle ci oidc auth 2024-10-05 01:56:33 +08:00
20bcf8aab8 allow billing page on eu 2024-10-04 07:53:33 -07:00
0814245ce6 cleanup 2024-10-04 18:43:29 +04:00
1687d66a0e misc: ignore partitions in generate schema 2024-10-04 22:37:13 +08:00
cf446a38b3 misc: improved knex import 2024-10-04 22:27:11 +08:00
36ef87909e Merge remote-tracking branch 'origin/main' into misc/move-audit-logs-to-dedicated 2024-10-04 22:16:46 +08:00
6bfeac5e98 misc: addressed import knex issue 2024-10-04 22:15:39 +08:00
d669320385 misc: addressed type issue with knex 2024-10-04 22:06:32 +08:00
8dbdb79833 misc: finalized partition migration script 2024-10-04 21:43:33 +08:00
2d2f27ea46 accounted for not scopes in databricks use case 2024-10-04 00:27:17 -07:00
4aeb2bf65e fix pr review for databricks integration 2024-10-04 00:09:33 -07:00
24da76db19 Merge pull request from Infisical/meet/switch-templating-engine
chore: switch templating engine away from mustache
2024-10-04 09:04:47 +05:30
3c49936eee chore: lint fix 2024-10-04 08:57:55 +05:30
b416e79d63 chore: switch templating engine away from mustache 2024-10-04 08:08:36 +05:30
92c529587b fix: correct doc link 2024-10-03 18:55:58 -07:00
3b74c232dc Update pull_request_template.md 2024-10-04 04:04:00 +04:00
6164dc32d7 chore: api docs 2024-10-04 04:00:43 +04:00
37e7040eea feat: include path and environment on secret folder 2024-10-04 03:59:28 +04:00
a7ebb4b241 feat: get secret import by ID 2024-10-04 03:58:39 +04:00
2fc562ff2d update image for databricks integartion 2024-10-03 16:36:07 -07:00
b5c83fea4d fixed databricks integration docs 2024-10-03 16:28:19 -07:00
b586f98926 fixed databricks integration docs 2024-10-03 16:26:38 -07:00
e6205c086f fix license changes 2024-10-03 16:23:39 -07:00
2ca34099ed added custom instance URLs to databricks 2024-10-03 16:21:47 -07:00
5da6c12941 Merge pull request from scott-ray-wilson/kms-feature
Feature: KMS MVP
2024-10-03 15:15:08 -07:00
e2612b75fc chore: move migration file to latest 2024-10-03 15:04:00 -07:00
ca5edb95f1 fix: revert mint api url 2024-10-03 14:46:06 -07:00
724e2b3692 Update docs for Infisical KMS 2024-10-03 14:29:26 -07:00
2c93561a3b improvement: format docs and change wording 2024-10-03 13:31:53 -07:00
0b24cc8631 fix: address missing slug -> name ref 2024-10-03 13:05:10 -07:00
6c6e932899 Merge pull request from Infisical/daniel/create-multiple-project-envs
fix: allow creation of multiple project envs
2024-10-04 00:04:10 +04:00
c66a711890 improvements: address requested changes 2024-10-03 12:55:53 -07:00
787f8318fe updated locks 2024-10-03 23:50:53 +04:00
9a27873af5 requested changes 2024-10-03 23:50:53 +04:00
0abab57d83 fix: variable naming 2024-10-03 23:50:53 +04:00
d5662dfef4 feat: allow creation of multiple project envs 2024-10-03 23:50:53 +04:00
ee2ee48b47 Merge pull request from Infisical/meet/fix-mustache-import-error
fix: change mustache import
2024-10-03 23:30:18 +04:00
e05f05f9ed misc: added timeout error prompt 2024-10-04 02:41:21 +08:00
81846d9c67 misc: added timeout for db queries 2024-10-04 02:25:02 +08:00
723f0e862d misc: finalized partition script 2024-10-04 01:42:24 +08:00
2d0433b96c misc: initial setup for audit log partition: 2024-10-03 22:47:16 +08:00
25b55087cf added databricks integration 2024-10-02 22:49:02 -07:00
7cd85cf84a fix: correct order of drop sequence 2024-10-02 16:57:24 -07:00
cf5c886b6f chore: revert prem permission 2024-10-02 16:38:02 -07:00
e667c7c988 improvement: finish address changes 2024-10-02 16:35:53 -07:00
9b1615f2fb misc: migrated json filters to new op 2024-10-03 00:31:23 +08:00
dc8c3a30bd misc: added project name to publish log 2024-10-02 22:40:33 +08:00
86cb51364a misc: initial setup for migration of audit logs 2024-10-02 22:30:07 +08:00
=
5856a42807 docs: added docs for infisical admin panels 2024-09-29 20:46:34 +05:30
0df80c5b2d Merge pull request from Infisical/maidul-dhqduqyw
add trip on identityId for identity logins
2024-09-17 12:31:09 -04:00
c577f51c19 add trip on identityId for identity logins 2024-09-17 12:15:34 -04:00
24d121ab59 Remove service token notice 2024-09-16 21:25:53 -04:00
ccbf09398e docs: minor rewriting 2024-09-16 16:56:47 +04:00
afbca118b7 Fixed typo 2024-09-16 16:56:34 +04:00
=
bd29d6feb9 chore: updated documentation for docker compose and docker for machine identity 2024-09-16 17:56:00 +05:30
466 changed files with 12661 additions and 3474 deletions
.env.example.env.migration.example
.github
Dockerfile.standalone-infisicalREADME.md
backend
e2e-test/routes/v1
package-lock.jsonpackage.json
scripts
src
@types
db
ee
keystore
lib
main.ts
queue
server
services
api-key
auth-token
auth
certificate-authority
certificate-template
cmek
external-group-org-role-mapping
external-migration
group-project
identity-access-token
identity-aws-auth
identity-azure-auth
identity-gcp-auth
identity-kubernetes-auth
identity-oidc-auth
identity-project
identity-token-auth
identity-ua
identity
integration-auth
integration
kms
org-admin
org
pki-alert
pki-collection
project-bot
project-env
project-membership
project-role
project
secret-blind-index
secret-folder
secret-import
secret-sharing
secret-tag
secret-v2-bridge
secret
service-token
slack
smtp
super-admin
user
webhook
cli
docs
api-reference/endpoints/kms/keys
changelog
cli/commands
contributing/platform
documentation/platform
images
integrations
platform
integrations
mint.json
sdks/languages
self-hosting/configuration
frontend
next.config.jspackage-lock.jsonpackage.json
public
data
images/integrations
src
components
context
ProjectPermissionContext
index.tsx
helpers
hooks
layouts
AdminLayout
AppLayout
pages
integrations
login
org/[id]
overview
secret-scanning
project/[id]/kms
signupinvite.tsx
views
IntegrationsPage
IntegrationDetailsPage/components
IntegrationPage.utils.tsx
components/IntegrationsSection
Login
Org
AuditLogsPage
MembersPage/components
OrgGroupsTab/components/OrgGroupsSection
OrgIdentityTab/components/IdentitySection
OrgMembersTab/components/OrgMembersSection
OrgRoleTabSection
RolePage/components
Project
CertificatesPage
KmsPage
MembersPage/components
IdentityTab
MembersTab/components
RolePage/components/RolePermissionsSection
SecretApprovalPage/components/SecretApprovalRequest/components
SecretMainPage
SecretOverviewPage
SecretOverviewPage.tsx
components
SecretOverviewTableRow
SelectionPanel
Settings
OrgSettingsPage/components
ProjectSettingsPage/components/EncryptionTab
Signup/components/UserInfoSSOStep
admin/DashboardPage
helm-charts
k8-operator
nginx

@ -36,16 +36,22 @@ CLIENT_ID_HEROKU=
CLIENT_ID_VERCEL=
CLIENT_ID_NETLIFY=
CLIENT_ID_GITHUB=
CLIENT_ID_GITHUB_APP=
CLIENT_SLUG_GITHUB_APP=
CLIENT_ID_GITLAB=
CLIENT_ID_BITBUCKET=
CLIENT_SECRET_HEROKU=
CLIENT_SECRET_VERCEL=
CLIENT_SECRET_NETLIFY=
CLIENT_SECRET_GITHUB=
CLIENT_SECRET_GITHUB_APP=
CLIENT_SECRET_GITLAB=
CLIENT_SECRET_BITBUCKET=
CLIENT_SLUG_VERCEL=
CLIENT_PRIVATE_KEY_GITHUB_APP=
CLIENT_APP_ID_GITHUB_APP=
# Sentry (optional) for monitoring errors
SENTRY_DSN=

@ -1 +1,2 @@
DB_CONNECTION_URI=
AUDIT_LOGS_DB_CONNECTION_URI=

@ -6,6 +6,7 @@
- [ ] Bug fix
- [ ] New feature
- [ ] Improvement
- [ ] Breaking change
- [ ] Documentation

@ -7,7 +7,6 @@ on:
description: "Version number"
required: true
type: string
defaults:
run:
working-directory: ./backend
@ -49,9 +48,9 @@ jobs:
- name: Package into node binary
run: |
if [ "${{ matrix.os }}" != "linux" ]; then
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
pkg --no-bytecode --public-packages "*" --public --compress GZip --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
else
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
pkg --no-bytecode --public-packages "*" --public --compress GZip --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
fi
# Set up .deb package structure (Debian/Ubuntu only)
@ -83,6 +82,86 @@ jobs:
dpkg-deb --build infisical-core
mv infisical-core.deb ./binary/infisical-core-${{matrix.arch}}.deb
### RPM
# Set up .rpm package structure
- name: Set up .rpm package structure
if: matrix.os == 'linux'
run: |
mkdir -p infisical-core-rpm/usr/local/bin
cp ./binary/infisical-core infisical-core-rpm/usr/local/bin/
chmod +x infisical-core-rpm/usr/local/bin/infisical-core
# Install RPM build tools
- name: Install RPM build tools
if: matrix.os == 'linux'
run: sudo apt-get update && sudo apt-get install -y rpm
# Create .spec file for RPM
- name: Create .spec file for RPM
if: matrix.os == 'linux'
run: |
cat <<EOF > infisical-core.spec
%global _enable_debug_package 0
%global debug_package %{nil}
%global __os_install_post /usr/lib/rpm/brp-compress %{nil}
Name: infisical-core
Version: ${{ github.event.inputs.version }}
Release: 1%{?dist}
Summary: Infisical Core standalone executable
License: Proprietary
URL: https://app.infisical.com
%description
Infisical Core standalone executable (app.infisical.com)
%install
mkdir -p %{buildroot}/usr/local/bin
cp %{_sourcedir}/infisical-core %{buildroot}/usr/local/bin/
%files
/usr/local/bin/infisical-core
%pre
%post
%preun
%postun
EOF
# Build .rpm file
- name: Build .rpm package
if: matrix.os == 'linux'
run: |
# Create necessary directories
mkdir -p rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}
# Copy the binary directly to SOURCES
cp ./binary/infisical-core rpmbuild/SOURCES/
# Run rpmbuild with verbose output
rpmbuild -vv -bb \
--define "_topdir $(pwd)/rpmbuild" \
--define "_sourcedir $(pwd)/rpmbuild/SOURCES" \
--define "_rpmdir $(pwd)/rpmbuild/RPMS" \
--target ${{ matrix.arch == 'x64' && 'x86_64' || 'aarch64' }} \
infisical-core.spec
# Try to find the RPM file
find rpmbuild -name "*.rpm"
# Move the RPM file if found
if [ -n "$(find rpmbuild -name '*.rpm')" ]; then
mv $(find rpmbuild -name '*.rpm') ./binary/infisical-core-${{matrix.arch}}.rpm
else
echo "RPM file not found!"
exit 1
fi
- uses: actions/setup-python@v4
with:
python-version: "3.x" # Specify the Python version you need
@ -97,6 +176,12 @@ jobs:
working-directory: ./backend
run: cloudsmith push deb --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.deb
# Publish .rpm file to Cloudsmith (Red Hat-based systems only)
- name: Publish .rpm to Cloudsmith
if: matrix.os == 'linux'
working-directory: ./backend
run: cloudsmith push rpm --republish --no-wait-for-sync --api-key=${{ secrets.CLOUDSMITH_API_KEY }} infisical/infisical-core/any-distro/any-version ./binary/infisical-core-${{ matrix.arch }}.rpm
# Publish .exe file to Cloudsmith (Windows only)
- name: Publish to Cloudsmith (Windows)
if: matrix.os == 'win'

@ -127,6 +127,7 @@ jobs:
- name: Change directory to backend and install dependencies
env:
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
AUDIT_LOGS_DB_CONNECTION_URI: ${{ secrets.AUDIT_LOGS_DB_CONNECTION_URI }}
run: |
cd backend
npm install

@ -95,6 +95,10 @@ RUN mkdir frontend-build
# Production stage
FROM base AS production
RUN apk add --upgrade --no-cache ca-certificates
RUN apk add --no-cache bash curl && curl -1sLf \
'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.alpine.sh' | bash \
&& apk add infisical=0.31.1 && apk add --no-cache git
RUN addgroup --system --gid 1001 nodejs \
&& adduser --system --uid 1001 non-root-user

@ -73,6 +73,11 @@ We're on a mission to make security tooling more accessible to everyone, not jus
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
### Key Management (KMS):
- **[Cryptograhic Keys](https://infisical.com/docs/documentation/platform/kms)**: Centrally manage keys across projects through a user-friendly interface or via the API.
- **[Encrypt and Decrypt Data](https://infisical.com/docs/documentation/platform/kms#guide-to-encrypting-data)**: Use symmetric keys to encrypt and decrypt data.
### General Platform:
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
@ -130,9 +135,7 @@ Lean about Infisical's code scanning feature [here](https://infisical.com/docs/c
This repo available under the [MIT expat license](https://github.com/Infisical/infisical/blob/main/LICENSE), with the exception of the `ee` directory which will contain premium enterprise features requiring a Infisical license.
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://infisical.cal.com/vlad/infisical-demo):
<a href="[https://infisical.cal.com/vlad/infisical-demo](https://infisical.cal.com/vlad/infisical-demo)"><img alt="Schedule a meeting" src="https://cal.com/book-with-cal-dark.svg" /></a>
If you are interested in managed Infisical Cloud of self-hosted Enterprise Offering, take a look at [our website](https://infisical.com/) or [book a meeting with us](https://infisical.cal.com/vlad/infisical-demo).
## Security
@ -158,4 +161,3 @@ Not sure where to get started? You can:
- [Twitter](https://twitter.com/infisical) for fast news
- [YouTube](https://www.youtube.com/@infisical_os) for videos on secret management
- [Blog](https://infisical.com/blog) for secret management insights, articles, tutorials, and updates
- [Roadmap](https://www.notion.so/infisical/be2d2585a6694e40889b03aef96ea36b?v=5b19a8127d1a4060b54769567a8785fa) for planned features

@ -39,8 +39,6 @@ describe("Login V1 Router", async () => {
});
expect(res.statusCode).toBe(200);
const payload = JSON.parse(res.payload);
expect(payload).toHaveProperty("mfaEnabled");
expect(payload).toHaveProperty("token");
expect(payload.mfaEnabled).toBeFalsy();
});
});

@ -123,7 +123,7 @@ describe("Project Environment Router", async () => {
id: deletedProjectEnvironment.id,
name: mockProjectEnv.name,
slug: mockProjectEnv.slug,
position: 4,
position: 5,
createdAt: expect.any(String),
updatedAt: expect.any(String)
})

@ -21,12 +21,14 @@
"@fastify/etag": "^5.1.0",
"@fastify/formbody": "^7.4.0",
"@fastify/helmet": "^11.1.1",
"@fastify/multipart": "8.3.0",
"@fastify/passport": "^2.4.0",
"@fastify/rate-limit": "^9.0.0",
"@fastify/session": "^10.7.0",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^2.1.0",
"@node-saml/passport-saml": "^4.0.4",
"@octokit/auth-app": "^7.1.1",
"@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1",
@ -61,12 +63,11 @@
"jwks-rsa": "^3.1.0",
"knex": "^3.0.1",
"ldapjs": "^3.0.7",
"ldif": "^0.5.1",
"ldif": "0.5.1",
"libsodium-wrappers": "^0.7.13",
"lodash.isequal": "^4.5.0",
"mongodb": "^6.8.1",
"ms": "^2.1.3",
"mustache": "^4.2.0",
"mysql2": "^3.9.8",
"nanoid": "^3.3.4",
"nodemailer": "^6.9.9",
@ -111,7 +112,6 @@
"@types/jsrp": "^0.2.6",
"@types/libsodium-wrappers": "^0.7.13",
"@types/lodash.isequal": "^4.5.8",
"@types/mustache": "^4.2.5",
"@types/node": "^20.9.5",
"@types/nodemailer": "^6.4.14",
"@types/passport-github": "^1.1.12",
@ -4313,6 +4313,15 @@
"fast-uri": "^2.0.0"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz",
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@fastify/cookie": {
"version": "9.3.1",
"resolved": "https://registry.npmjs.org/@fastify/cookie/-/cookie-9.3.1.tgz",
@ -4383,6 +4392,20 @@
"helmet": "^7.0.0"
}
},
"node_modules/@fastify/multipart": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@fastify/multipart/-/multipart-8.3.0.tgz",
"integrity": "sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==",
"license": "MIT",
"dependencies": {
"@fastify/busboy": "^2.1.0",
"@fastify/deepmerge": "^1.0.0",
"@fastify/error": "^3.0.0",
"fastify-plugin": "^4.0.0",
"secure-json-parse": "^2.4.0",
"stream-wormhole": "^1.1.0"
}
},
"node_modules/@fastify/passport": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@fastify/passport/-/passport-2.4.0.tgz",
@ -4978,24 +5001,73 @@
}
},
"node_modules/@octokit/auth-app": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.0.3.tgz",
"integrity": "sha512-9N7IlBAKEJR3tJgPSubCxIDYGXSdc+2xbkjYpk9nCyqREnH8qEMoMhiEB1WgoA9yTFp91El92XNXAi+AjuKnfw==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.1.1.tgz",
"integrity": "sha512-kRAd6yelV9OgvlEJE88H0VLlQdZcag9UlLr7dV0YYP37X8PPDvhgiTy66QVhDXdyoT0AleFN2w/qXkPdrSzINg==",
"dependencies": {
"@octokit/auth-oauth-app": "^7.0.0",
"@octokit/auth-oauth-user": "^4.0.0",
"@octokit/request": "^8.0.2",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^12.0.0",
"deprecation": "^2.3.1",
"@octokit/auth-oauth-app": "^8.1.0",
"@octokit/auth-oauth-user": "^5.1.0",
"@octokit/request": "^9.1.1",
"@octokit/request-error": "^6.1.1",
"@octokit/types": "^13.4.1",
"lru-cache": "^10.0.0",
"universal-github-app-jwt": "^1.1.2",
"universal-user-agent": "^6.0.0"
"universal-github-app-jwt": "^2.2.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/auth-app/node_modules/@octokit/request": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
"dependencies": {
"@octokit/endpoint": "^10.0.0",
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app/node_modules/@octokit/request-error": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"dependencies": {
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-app/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/auth-app/node_modules/lru-cache": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
@ -5004,53 +5076,220 @@
"node": "14 || >=16.14"
}
},
"node_modules/@octokit/auth-app/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-oauth-app": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.0.1.tgz",
"integrity": "sha512-RE0KK0DCjCHXHlQBoubwlLijXEKfhMhKm9gO56xYvFmP1QTMb+vvwRPmQLLx0V+5AvV9N9I3lr1WyTzwL3rMDg==",
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz",
"integrity": "sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==",
"dependencies": {
"@octokit/auth-oauth-device": "^6.0.0",
"@octokit/auth-oauth-user": "^4.0.0",
"@octokit/request": "^8.0.2",
"@octokit/types": "^12.0.0",
"@types/btoa-lite": "^1.0.0",
"btoa-lite": "^1.0.0",
"universal-user-agent": "^6.0.0"
"@octokit/auth-oauth-device": "^7.0.0",
"@octokit/auth-oauth-user": "^5.0.1",
"@octokit/request": "^9.0.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
"dependencies": {
"@octokit/endpoint": "^10.0.0",
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request-error": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"dependencies": {
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/auth-oauth-app/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-oauth-device": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.0.1.tgz",
"integrity": "sha512-yxU0rkL65QkjbqQedgVx3gmW7YM5fF+r5uaSj9tM/cQGVqloXcqP2xK90eTyYvl29arFVCW8Vz4H/t47mL0ELw==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz",
"integrity": "sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==",
"dependencies": {
"@octokit/oauth-methods": "^4.0.0",
"@octokit/request": "^8.0.0",
"@octokit/types": "^12.0.0",
"universal-user-agent": "^6.0.0"
"@octokit/oauth-methods": "^5.0.0",
"@octokit/request": "^9.0.0",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.0.1.tgz",
"integrity": "sha512-N94wWW09d0hleCnrO5wt5MxekatqEJ4zf+1vSe8MKMrhZ7gAXKFOKrDEZW2INltvBWJCyDUELgGRv8gfErH1Iw==",
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/auth-oauth-device": "^6.0.0",
"@octokit/oauth-methods": "^4.0.0",
"@octokit/request": "^8.0.2",
"@octokit/types": "^12.0.0",
"btoa-lite": "^1.0.0",
"universal-user-agent": "^6.0.0"
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
"dependencies": {
"@octokit/endpoint": "^10.0.0",
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request-error": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"dependencies": {
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/auth-oauth-device/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-oauth-user": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz",
"integrity": "sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==",
"dependencies": {
"@octokit/auth-oauth-device": "^7.0.1",
"@octokit/oauth-methods": "^5.0.0",
"@octokit/request": "^9.0.1",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
"dependencies": {
"@octokit/endpoint": "^10.0.0",
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request-error": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"dependencies": {
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/auth-oauth-user/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/auth-token": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
@ -5114,28 +5353,82 @@
}
},
"node_modules/@octokit/oauth-authorization-url": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz",
"integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz",
"integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.0.1.tgz",
"integrity": "sha512-1NdTGCoBHyD6J0n2WGXg9+yDLZrRNZ0moTEex/LSPr49m530WNKcCfXDghofYptr3st3eTii+EHoG5k/o+vbtw==",
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.2.tgz",
"integrity": "sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==",
"dependencies": {
"@octokit/oauth-authorization-url": "^6.0.2",
"@octokit/request": "^8.0.2",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^12.0.0",
"btoa-lite": "^1.0.0"
"@octokit/oauth-authorization-url": "^7.0.0",
"@octokit/request": "^9.1.0",
"@octokit/request-error": "^6.1.0",
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
"dependencies": {
"@octokit/types": "^13.0.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/request": {
"version": "9.1.3",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz",
"integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==",
"dependencies": {
"@octokit/endpoint": "^10.0.0",
"@octokit/request-error": "^6.0.1",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^7.0.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/request-error": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.5.tgz",
"integrity": "sha512-IlBTfGX8Yn/oFPMwSfvugfncK2EwRLjzbrpifNaMY8o/HTEAFqCA1FZxjD9cWvSKBHgrIhc4CSBIzMxiLsbzFQ==",
"dependencies": {
"@octokit/types": "^13.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/@octokit/oauth-methods/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
},
"node_modules/@octokit/openapi-types": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
@ -5250,13 +5543,13 @@
}
},
"node_modules/@octokit/request": {
"version": "8.1.6",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.6.tgz",
"integrity": "sha512-YhPaGml3ncZC1NfXpP3WZ7iliL1ap6tLkAp6MvbK2fTTPytzVUyUesBBogcdMm86uRYO5rHaM1xIWxigWZ17MQ==",
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz",
"integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==",
"dependencies": {
"@octokit/endpoint": "^9.0.0",
"@octokit/request-error": "^5.0.0",
"@octokit/types": "^12.0.0",
"@octokit/endpoint": "^9.0.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.1.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
@ -5264,11 +5557,11 @@
}
},
"node_modules/@octokit/request-error": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz",
"integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz",
"integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==",
"dependencies": {
"@octokit/types": "^12.0.0",
"@octokit/types": "^13.1.0",
"deprecation": "^2.0.0",
"once": "^1.4.0"
},
@ -5276,6 +5569,32 @@
"node": ">= 18"
}
},
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/@octokit/request/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/@octokit/rest": {
"version": "20.0.2",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
@ -7079,13 +7398,6 @@
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
},
"node_modules/@types/mustache": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.5.tgz",
"integrity": "sha512-PLwiVvTBg59tGFL/8VpcGvqOu3L4OuveNvPi0EYbWchRdEVP++yRUXJPFl+CApKEq13017/4Nf7aQ5lTtHUNsA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.9.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.5.tgz",
@ -13729,15 +14041,6 @@
"node": ">= 6"
}
},
"node_modules/mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"license": "MIT",
"bin": {
"mustache": "bin/mustache"
}
},
"node_modules/mylas": {
"version": "2.1.13",
"resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz",
@ -14178,6 +14481,154 @@
"@octokit/core": ">=5"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-app": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.2.tgz",
"integrity": "sha512-fWjIOpxnL8/YFY3kqquciFQ4o99aCqHw5kMFoGPYbz/h5HNZ11dJlV9zag5wS2nt0X1wJ5cs9BUo+CsAPfW4jQ==",
"dependencies": {
"@octokit/auth-oauth-app": "^7.1.0",
"@octokit/auth-oauth-user": "^4.1.0",
"@octokit/request": "^8.3.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.1.0",
"deprecation": "^2.3.1",
"lru-cache": "^10.0.0",
"universal-github-app-jwt": "^1.1.2",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-app/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-app": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz",
"integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==",
"dependencies": {
"@octokit/auth-oauth-device": "^6.1.0",
"@octokit/auth-oauth-user": "^4.1.0",
"@octokit/request": "^8.3.1",
"@octokit/types": "^13.0.0",
"@types/btoa-lite": "^1.0.0",
"btoa-lite": "^1.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-device": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz",
"integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==",
"dependencies": {
"@octokit/oauth-methods": "^4.1.0",
"@octokit/request": "^8.3.1",
"@octokit/types": "^13.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-user": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz",
"integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==",
"dependencies": {
"@octokit/auth-oauth-device": "^6.1.0",
"@octokit/oauth-methods": "^4.1.0",
"@octokit/request": "^8.3.1",
"@octokit/types": "^13.0.0",
"btoa-lite": "^1.0.0",
"universal-user-agent": "^6.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/oauth-authorization-url": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz",
"integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==",
"engines": {
"node": ">= 18"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/oauth-methods": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz",
"integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==",
"dependencies": {
"@octokit/oauth-authorization-url": "^6.0.2",
"@octokit/request": "^8.3.1",
"@octokit/request-error": "^5.1.0",
"@octokit/types": "^13.0.0",
"btoa-lite": "^1.0.0"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/oauth-methods/node_modules/@octokit/types": {
"version": "13.6.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
"dependencies": {
"@octokit/openapi-types": "^22.2.0"
}
},
"node_modules/octokit-auth-probot/node_modules/@octokit/openapi-types": {
"version": "22.2.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
},
"node_modules/octokit-auth-probot/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
},
"node_modules/octokit-auth-probot/node_modules/universal-github-app-jwt": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz",
"integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==",
"dependencies": {
"@types/jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.2"
}
},
"node_modules/oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
@ -16622,6 +17073,15 @@
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ=="
},
"node_modules/stream-wormhole": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stream-wormhole/-/stream-wormhole-1.1.0.tgz",
"integrity": "sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@ -18161,13 +18621,9 @@
}
},
"node_modules/universal-github-app-jwt": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.1.2.tgz",
"integrity": "sha512-t1iB2FmLFE+yyJY9+3wMx0ejB+MQpEVkH0gQv7dR6FZyltyq+ZZO0uDpbopxhrZ3SLEO4dCEkIujOMldEQ2iOA==",
"dependencies": {
"@types/jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.2"
}
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.0.tgz",
"integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ=="
},
"node_modules/universal-user-agent": {
"version": "6.0.1",

@ -45,13 +45,20 @@
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
"generate:component": "tsx ./scripts/create-backend-file.ts",
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
"auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest",
"auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up",
"auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down",
"auditlog-migration:list": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:list",
"auditlog-migration:status": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:status",
"auditlog-migration:rollback": "knex --knexfile ./src/db/auditlog-knexfile.ts migrate:rollback",
"migration:new": "tsx ./scripts/create-migration.ts",
"migration:up": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:up",
"migration:down": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:down",
"migration:list": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:list",
"migration:latest": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
"migration:status": "knex --knexfile ./src/db/knexfile.ts --client pg migrate:status",
"migration:rollback": "knex --knexfile ./src/db/knexfile.ts migrate:rollback",
"migration:up": "npm run auditlog-migration:up && knex --knexfile ./src/db/knexfile.ts --client pg migrate:up",
"migration:down": "npm run auditlog-migration:down && knex --knexfile ./src/db/knexfile.ts --client pg migrate:down",
"migration:list": "npm run auditlog-migration:list && knex --knexfile ./src/db/knexfile.ts --client pg migrate:list",
"migration:latest": "npm run auditlog-migration:latest && knex --knexfile ./src/db/knexfile.ts --client pg migrate:latest",
"migration:status": "npm run auditlog-migration:status && knex --knexfile ./src/db/knexfile.ts --client pg migrate:status",
"migration:rollback": "npm run auditlog-migration:rollback && knex --knexfile ./src/db/knexfile.ts migrate:rollback",
"migrate:org": "tsx ./scripts/migrate-organization.ts",
"seed:new": "tsx ./scripts/create-seed-file.ts",
"seed": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
@ -71,7 +78,6 @@
"@types/jsrp": "^0.2.6",
"@types/libsodium-wrappers": "^0.7.13",
"@types/lodash.isequal": "^4.5.8",
"@types/mustache": "^4.2.5",
"@types/node": "^20.9.5",
"@types/nodemailer": "^6.4.14",
"@types/passport-github": "^1.1.12",
@ -120,12 +126,14 @@
"@fastify/etag": "^5.1.0",
"@fastify/formbody": "^7.4.0",
"@fastify/helmet": "^11.1.1",
"@fastify/multipart": "8.3.0",
"@fastify/passport": "^2.4.0",
"@fastify/rate-limit": "^9.0.0",
"@fastify/session": "^10.7.0",
"@fastify/swagger": "^8.14.0",
"@fastify/swagger-ui": "^2.1.0",
"@node-saml/passport-saml": "^4.0.4",
"@octokit/auth-app": "^7.1.1",
"@octokit/plugin-retry": "^5.0.5",
"@octokit/rest": "^20.0.2",
"@octokit/webhooks-types": "^7.3.1",
@ -160,12 +168,11 @@
"jwks-rsa": "^3.1.0",
"knex": "^3.0.1",
"ldapjs": "^3.0.7",
"ldif": "^0.5.1",
"ldif": "0.5.1",
"libsodium-wrappers": "^0.7.13",
"lodash.isequal": "^4.5.0",
"mongodb": "^6.8.1",
"ms": "^2.1.3",
"mustache": "^4.2.0",
"mysql2": "^3.9.8",
"nanoid": "^3.3.4",
"nodemailer": "^6.9.9",

@ -90,7 +90,12 @@ const main = async () => {
.whereRaw("table_schema = current_schema()")
.select<{ tableName: string }[]>("table_name as tableName")
.orderBy("table_name")
).filter((el) => !el.tableName.includes("_migrations"));
).filter(
(el) =>
!el.tableName.includes("_migrations") &&
!el.tableName.includes("audit_logs_") &&
el.tableName !== "intermediate_audit_logs"
);
for (let i = 0; i < tables.length; i += 1) {
const { tableName } = tables[i];

@ -0,0 +1,84 @@
/* eslint-disable */
import promptSync from "prompt-sync";
import { execSync } from "child_process";
import path from "path";
import { existsSync } from "fs";
const prompt = promptSync({
sigint: true
});
const exportDb = () => {
const exportHost = prompt("Enter your Postgres Host to migrate from: ");
const exportPort = prompt("Enter your Postgres Port to migrate from [Default = 5432]: ") ?? "5432";
const exportUser = prompt("Enter your Postgres User to migrate from: [Default = infisical]: ") ?? "infisical";
const exportPassword = prompt("Enter your Postgres Password to migrate from: ");
const exportDatabase = prompt("Enter your Postgres Database to migrate from [Default = infisical]: ") ?? "infisical";
// we do not include the audit_log and secret_sharing entries
execSync(
`PGDATABASE="${exportDatabase}" PGPASSWORD="${exportPassword}" PGHOST="${exportHost}" PGPORT=${exportPort} PGUSER=${exportUser} pg_dump infisical --exclude-table-data="secret_sharing" --exclude-table-data="audit_log*" > ${path.join(
__dirname,
"../src/db/dump.sql"
)}`,
{ stdio: "inherit" }
);
};
const importDbForOrg = () => {
const importHost = prompt("Enter your Postgres Host to migrate to: ");
const importPort = prompt("Enter your Postgres Port to migrate to [Default = 5432]: ") ?? "5432";
const importUser = prompt("Enter your Postgres User to migrate to: [Default = infisical]: ") ?? "infisical";
const importPassword = prompt("Enter your Postgres Password to migrate to: ");
const importDatabase = prompt("Enter your Postgres Database to migrate to [Default = infisical]: ") ?? "infisical";
const orgId = prompt("Enter the organization ID to migrate: ");
if (!existsSync(path.join(__dirname, "../src/db/dump.sql"))) {
console.log("File not found, please export the database first.");
return;
}
execSync(
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -f ${path.join(
__dirname,
"../src/db/dump.sql"
)}`
);
execSync(
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c "DELETE FROM public.organizations WHERE id != '${orgId}'"`
);
// delete global/instance-level resources not relevant to the organization to migrate
// users
execSync(
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM users WHERE users.id NOT IN (SELECT org_memberships."userId" FROM org_memberships)'`
);
// identities
execSync(
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM identities WHERE id NOT IN (SELECT "identityId" FROM identity_org_memberships)'`
);
// reset slack configuration in superAdmin
execSync(
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'UPDATE super_admin SET "encryptedSlackClientId" = null, "encryptedSlackClientSecret" = null'`
);
console.log("Organization migrated successfully.");
};
const main = () => {
const action = prompt(
"Enter the action to perform\n 1. Export from existing instance.\n 2. Import org to instance.\n \n Action: "
);
if (action === "1") {
exportDb();
} else if (action === "2") {
importDbForOrg();
} else {
console.log("Invalid action");
}
};
main();

@ -38,6 +38,8 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { TCmekServiceFactory } from "@app/services/cmek/cmek-service";
import { TExternalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
import { TExternalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
@ -182,7 +184,9 @@ declare module "fastify" {
orgAdmin: TOrgAdminServiceFactory;
slack: TSlackServiceFactory;
workflowIntegration: TWorkflowIntegrationServiceFactory;
cmek: TCmekServiceFactory;
migration: TExternalMigrationServiceFactory;
externalGroupOrgRoleMapping: TExternalGroupOrgRoleMappingServiceFactory;
};
// this is exclusive use for middlewares in which we need to inject data
// everywhere else access using service layer

@ -336,6 +336,11 @@ import {
TWorkflowIntegrationsInsert,
TWorkflowIntegrationsUpdate
} from "@app/db/schemas";
import {
TExternalGroupOrgRoleMappings,
TExternalGroupOrgRoleMappingsInsert,
TExternalGroupOrgRoleMappingsUpdate
} from "@app/db/schemas/external-group-org-role-mappings";
import {
TSecretV2TagJunction,
TSecretV2TagJunctionInsert,
@ -808,5 +813,10 @@ declare module "knex/types/tables" {
TWorkflowIntegrationsInsert,
TWorkflowIntegrationsUpdate
>;
[TableName.ExternalGroupOrgRoleMapping]: KnexOriginal.CompositeTableType<
TExternalGroupOrgRoleMappings,
TExternalGroupOrgRoleMappingsInsert,
TExternalGroupOrgRoleMappingsUpdate
>;
}
}

@ -0,0 +1,75 @@
// eslint-disable-next-line
import "ts-node/register";
import dotenv from "dotenv";
import type { Knex } from "knex";
import path from "path";
// Update with your config settings. .
dotenv.config({
path: path.join(__dirname, "../../../.env.migration")
});
dotenv.config({
path: path.join(__dirname, "../../../.env")
});
if (!process.env.AUDIT_LOGS_DB_CONNECTION_URI && !process.env.AUDIT_LOGS_DB_HOST) {
console.info("Dedicated audit log database not found. No further migrations necessary");
process.exit(0);
}
console.info("Executing migration on audit log database...");
export default {
development: {
client: "postgres",
connection: {
connectionString: process.env.AUDIT_LOGS_DB_CONNECTION_URI,
host: process.env.AUDIT_LOGS_DB_HOST,
port: process.env.AUDIT_LOGS_DB_PORT,
user: process.env.AUDIT_LOGS_DB_USER,
database: process.env.AUDIT_LOGS_DB_NAME,
password: process.env.AUDIT_LOGS_DB_PASSWORD,
ssl: process.env.AUDIT_LOGS_DB_ROOT_CERT
? {
rejectUnauthorized: true,
ca: Buffer.from(process.env.AUDIT_LOGS_DB_ROOT_CERT, "base64").toString("ascii")
}
: false
},
pool: {
min: 2,
max: 10
},
seeds: {
directory: "./seeds"
},
migrations: {
tableName: "infisical_migrations"
}
},
production: {
client: "postgres",
connection: {
connectionString: process.env.AUDIT_LOGS_DB_CONNECTION_URI,
host: process.env.AUDIT_LOGS_DB_HOST,
port: process.env.AUDIT_LOGS_DB_PORT,
user: process.env.AUDIT_LOGS_DB_USER,
database: process.env.AUDIT_LOGS_DB_NAME,
password: process.env.AUDIT_LOGS_DB_PASSWORD,
ssl: process.env.AUDIT_LOGS_DB_ROOT_CERT
? {
rejectUnauthorized: true,
ca: Buffer.from(process.env.AUDIT_LOGS_DB_ROOT_CERT, "base64").toString("ascii")
}
: false
},
pool: {
min: 2,
max: 10
},
migrations: {
tableName: "infisical_migrations"
}
}
} as Knex.Config;

@ -1,2 +1,2 @@
export type { TDbClient } from "./instance";
export { initDbConnection } from "./instance";
export { initAuditLogDbConnection, initDbConnection } from "./instance";

@ -70,3 +70,45 @@ export const initDbConnection = ({
return db;
};
export const initAuditLogDbConnection = ({
dbConnectionUri,
dbRootCert
}: {
dbConnectionUri: string;
dbRootCert?: string;
}) => {
// akhilmhdh: the default Knex is knex.Knex<any, any[]>. but when assigned with knex({<config>}) the value is knex.Knex<any, unknown[]>
// this was causing issue with files like `snapshot-dal` `findRecursivelySnapshots` this i am explicitly putting the any and unknown[]
// eslint-disable-next-line
const db: Knex<any, unknown[]> = knex({
client: "pg",
connection: {
connectionString: dbConnectionUri,
host: process.env.AUDIT_LOGS_DB_HOST,
// @ts-expect-error I have no clue why only for the port there is a type error
// eslint-disable-next-line
port: process.env.AUDIT_LOGS_DB_PORT,
user: process.env.AUDIT_LOGS_DB_USER,
database: process.env.AUDIT_LOGS_DB_NAME,
password: process.env.AUDIT_LOGS_DB_PASSWORD,
ssl: dbRootCert
? {
rejectUnauthorized: true,
ca: Buffer.from(dbRootCert, "base64").toString("ascii")
}
: false
}
});
// we add these overrides so that auditLogDb and the primary DB are interchangeable
db.primaryNode = () => {
return db;
};
db.replicaNode = () => {
return db;
};
return db;
};

@ -0,0 +1,161 @@
import kx, { Knex } from "knex";
import { TableName } from "../schemas";
const INTERMEDIATE_AUDIT_LOG_TABLE = "intermediate_audit_logs";
const formatPartitionDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const createAuditLogPartition = async (knex: Knex, startDate: Date, endDate: Date) => {
const startDateStr = formatPartitionDate(startDate);
const endDateStr = formatPartitionDate(endDate);
const partitionName = `${TableName.AuditLog}_${startDateStr.replace(/-/g, "")}_${endDateStr.replace(/-/g, "")}`;
await knex.schema.raw(
`CREATE TABLE ${partitionName} PARTITION OF ${TableName.AuditLog} FOR VALUES FROM ('${startDateStr}') TO ('${endDateStr}')`
);
};
const up = async (knex: Knex): Promise<void> => {
console.info("Dropping primary key of audit log table...");
await knex.schema.alterTable(TableName.AuditLog, (t) => {
// remove existing keys
t.dropPrimary();
});
// Get all indices of the audit log table and drop them
const indexNames: { rows: { indexname: string }[] } = await knex.raw(
`
SELECT indexname
FROM pg_indexes
WHERE tablename = '${TableName.AuditLog}'
`
);
console.log(
"Deleting existing audit log indices:",
indexNames.rows.map((e) => e.indexname)
);
for await (const row of indexNames.rows) {
await knex.raw(`DROP INDEX IF EXISTS ${row.indexname}`);
}
// renaming audit log to intermediate table
console.log("Renaming audit log table to the intermediate name");
await knex.schema.renameTable(TableName.AuditLog, INTERMEDIATE_AUDIT_LOG_TABLE);
if (!(await knex.schema.hasTable(TableName.AuditLog))) {
const createTableSql = knex.schema
.createTable(TableName.AuditLog, (t) => {
t.uuid("id").defaultTo(knex.fn.uuid());
t.string("actor").notNullable();
t.jsonb("actorMetadata").notNullable();
t.string("ipAddress");
t.string("eventType").notNullable();
t.jsonb("eventMetadata");
t.string("userAgent");
t.string("userAgentType");
t.datetime("expiresAt");
t.timestamps(true, true, true);
t.uuid("orgId");
t.string("projectId");
t.string("projectName");
t.primary(["id", "createdAt"]);
})
.toString();
console.info("Creating partition table...");
await knex.schema.raw(`
${createTableSql} PARTITION BY RANGE ("createdAt");
`);
console.log("Adding indices...");
await knex.schema.alterTable(TableName.AuditLog, (t) => {
t.index(["projectId", "createdAt"]);
t.index(["orgId", "createdAt"]);
t.index("expiresAt");
t.index("orgId");
t.index("projectId");
});
console.log("Adding GIN indices...");
await knex.raw(
`CREATE INDEX IF NOT EXISTS "audit_logs_actorMetadata_idx" ON ${TableName.AuditLog} USING gin("actorMetadata" jsonb_path_ops)`
);
console.log("GIN index for actorMetadata done");
await knex.raw(
`CREATE INDEX IF NOT EXISTS "audit_logs_eventMetadata_idx" ON ${TableName.AuditLog} USING gin("eventMetadata" jsonb_path_ops)`
);
console.log("GIN index for eventMetadata done");
// create default partition
console.log("Creating default partition...");
await knex.schema.raw(`CREATE TABLE ${TableName.AuditLog}_default PARTITION OF ${TableName.AuditLog} DEFAULT`);
const nextDate = new Date();
nextDate.setDate(nextDate.getDate() + 1);
const nextDateStr = formatPartitionDate(nextDate);
console.log("Attaching existing audit log table as a partition...");
await knex.schema.raw(`
ALTER TABLE ${INTERMEDIATE_AUDIT_LOG_TABLE} ADD CONSTRAINT audit_log_old
CHECK ( "createdAt" < DATE '${nextDateStr}' );
ALTER TABLE ${TableName.AuditLog} ATTACH PARTITION ${INTERMEDIATE_AUDIT_LOG_TABLE}
FOR VALUES FROM (MINVALUE) TO ('${nextDateStr}' );
`);
// create partition from now until end of month
console.log("Creating audit log partitions ahead of time... next date:", nextDateStr);
await createAuditLogPartition(knex, nextDate, new Date(nextDate.getFullYear(), nextDate.getMonth() + 1));
// create partitions 4 years ahead
const partitionMonths = 4 * 12;
const partitionPromises: Promise<void>[] = [];
for (let x = 1; x <= partitionMonths; x += 1) {
partitionPromises.push(
createAuditLogPartition(
knex,
new Date(nextDate.getFullYear(), nextDate.getMonth() + x, 1),
new Date(nextDate.getFullYear(), nextDate.getMonth() + (x + 1), 1)
)
);
}
await Promise.all(partitionPromises);
console.log("Partition migration complete");
}
};
export const executeMigration = async (url: string) => {
console.log("Executing migration...");
const knex = kx({
client: "pg",
connection: url
});
await knex.transaction(async (tx) => {
await up(tx);
});
};
const dbUrl = process.env.AUDIT_LOGS_DB_CONNECTION_URI;
if (!dbUrl) {
console.error("Please provide a DB connection URL to the AUDIT_LOGS_DB_CONNECTION_URI env");
process.exit(1);
}
void executeMigration(dbUrl).then(() => {
console.log("Migration: partition-audit-logs DONE");
process.exit(0);
});

@ -0,0 +1,46 @@
import { Knex } from "knex";
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.KmsKey)) {
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
// drop constraint if exists (won't exist if rolled back, see below)
await dropConstraintIfExists(TableName.KmsKey, "kms_keys_orgid_slug_unique", knex);
// projectId for CMEK functionality
await knex.schema.alterTable(TableName.KmsKey, (table) => {
table.string("projectId").nullable().references("id").inTable(TableName.Project).onDelete("CASCADE");
if (hasOrgId) {
table.unique(["orgId", "projectId", "slug"]);
}
if (hasSlug) {
table.renameColumn("slug", "name");
}
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.KmsKey)) {
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
const hasName = await knex.schema.hasColumn(TableName.KmsKey, "name");
// remove projectId for CMEK functionality
await knex.schema.alterTable(TableName.KmsKey, (table) => {
if (hasName) {
table.renameColumn("name", "slug");
}
if (hasOrgId) {
table.dropUnique(["orgId", "projectId", "slug"]);
}
table.dropColumn("projectId");
});
}
}

@ -0,0 +1,30 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.KmsKey)) {
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
if (!hasSlug) {
// add slug back temporarily and set value equal to name
await knex.schema
.alterTable(TableName.KmsKey, (table) => {
table.string("slug", 32);
})
.then(() => knex(TableName.KmsKey).update("slug", knex.ref("name")));
}
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.KmsKey)) {
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
if (hasSlug) {
await knex.schema.alterTable(TableName.KmsKey, (table) => {
table.dropColumn("slug");
});
}
}
}

@ -0,0 +1,48 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.AuditLog)) {
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
const doesProjectNameExist = await knex.schema.hasColumn(TableName.AuditLog, "projectName");
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesOrgIdExist) {
t.dropForeign("orgId");
}
if (doesProjectIdExist) {
t.dropForeign("projectId");
}
// add normalized field
if (!doesProjectNameExist) {
t.string("projectName");
}
});
}
}
export async function down(knex: Knex): Promise<void> {
const doesProjectIdExist = await knex.schema.hasColumn(TableName.AuditLog, "projectId");
const doesOrgIdExist = await knex.schema.hasColumn(TableName.AuditLog, "orgId");
const doesProjectNameExist = await knex.schema.hasColumn(TableName.AuditLog, "projectName");
if (await knex.schema.hasTable(TableName.AuditLog)) {
await knex.schema.alterTable(TableName.AuditLog, (t) => {
if (doesOrgIdExist) {
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
}
if (doesProjectIdExist) {
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
}
// remove normalized field
if (doesProjectNameExist) {
t.dropColumn("projectName");
}
});
}
}

@ -0,0 +1,29 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export async function up(knex: Knex): Promise<void> {
// org default role
if (await knex.schema.hasTable(TableName.Organization)) {
const hasDefaultRoleCol = await knex.schema.hasColumn(TableName.Organization, "defaultMembershipRole");
if (!hasDefaultRoleCol) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.string("defaultMembershipRole").notNullable().defaultTo("member");
});
}
}
}
export async function down(knex: Knex): Promise<void> {
// org default role
if (await knex.schema.hasTable(TableName.Organization)) {
const hasDefaultRoleCol = await knex.schema.hasColumn(TableName.Organization, "defaultMembershipRole");
if (hasDefaultRoleCol) {
await knex.schema.alterTable(TableName.Organization, (tb) => {
tb.dropColumn("defaultMembershipRole");
});
}
}
}

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

@ -0,0 +1,32 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils";
export async function up(knex: Knex): Promise<void> {
// add external group to org role mapping table
if (!(await knex.schema.hasTable(TableName.ExternalGroupOrgRoleMapping))) {
await knex.schema.createTable(TableName.ExternalGroupOrgRoleMapping, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("groupName").notNullable();
t.index("groupName");
t.string("role").notNullable();
t.uuid("roleId");
t.foreign("roleId").references("id").inTable(TableName.OrgRoles);
t.uuid("orgId").notNullable();
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
t.timestamps(true, true, true);
t.unique(["orgId", "groupName"]);
});
await createOnUpdateTrigger(knex, TableName.ExternalGroupOrgRoleMapping);
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.ExternalGroupOrgRoleMapping)) {
await dropOnUpdateTrigger(knex, TableName.ExternalGroupOrgRoleMapping);
await knex.schema.dropTable(TableName.ExternalGroupOrgRoleMapping);
}
}

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

@ -0,0 +1,21 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.SamlConfig, "orgId")) {
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
t.dropForeign("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.SamlConfig, "orgId")) {
await knex.schema.alterTable(TableName.SamlConfig, (t) => {
t.dropForeign("orgId");
t.foreign("orgId").references("id").inTable(TableName.Organization);
});
}
}

@ -0,0 +1,6 @@
import { Knex } from "knex";
import { TableName } from "@app/db/schemas";
export const dropConstraintIfExists = (tableName: TableName, constraintName: string, knex: Knex) =>
knex.raw(`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${constraintName};`);

@ -54,7 +54,7 @@ export const getSecretManagerDataKey = async (knex: Knex, projectId: string) =>
} else {
const [kmsDoc] = await knex(TableName.KmsKey)
.insert({
slug: slugify(alphaNumericNanoId(8).toLowerCase()),
name: slugify(alphaNumericNanoId(8).toLowerCase()),
orgId: project.orgId,
isReserved: false
})

@ -20,7 +20,8 @@ export const AuditLogsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
orgId: z.string().uuid().nullable().optional(),
projectId: z.string().nullable().optional()
projectId: z.string().nullable().optional(),
projectName: z.string().nullable().optional()
});
export type TAuditLogs = z.infer<typeof AuditLogsSchema>;

@ -0,0 +1,27 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const ExternalGroupOrgRoleMappingsSchema = z.object({
id: z.string().uuid(),
groupName: z.string(),
role: z.string(),
roleId: z.string().uuid().nullable().optional(),
orgId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TExternalGroupOrgRoleMappings = z.infer<typeof ExternalGroupOrgRoleMappingsSchema>;
export type TExternalGroupOrgRoleMappingsInsert = Omit<
z.input<typeof ExternalGroupOrgRoleMappingsSchema>,
TImmutableDBKeys
>;
export type TExternalGroupOrgRoleMappingsUpdate = Partial<
Omit<z.input<typeof ExternalGroupOrgRoleMappingsSchema>, TImmutableDBKeys>
>;

@ -13,9 +13,11 @@ export const KmsKeysSchema = z.object({
isDisabled: z.boolean().default(false).nullable().optional(),
isReserved: z.boolean().default(true).nullable().optional(),
orgId: z.string().uuid(),
slug: z.string(),
name: z.string(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
projectId: z.string().nullable().optional(),
slug: z.string().nullable().optional()
});
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;

@ -17,6 +17,7 @@ export enum TableName {
Groups = "groups",
GroupProjectMembership = "group_project_memberships",
GroupProjectMembershipRole = "group_project_membership_roles",
ExternalGroupOrgRoleMapping = "external_group_org_role_mappings",
UserGroupMembership = "user_group_membership",
UserAliases = "user_aliases",
UserEncryptionKey = "user_encryption_keys",

@ -19,7 +19,9 @@ export const OrganizationsSchema = z.object({
authEnforced: z.boolean().default(false).nullable().optional(),
scimEnabled: z.boolean().default(false).nullable().optional(),
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
kmsEncryptedDataKey: zodBuffer.nullable().optional()
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
defaultMembershipRole: z.string().default("member"),
enforceMfa: z.boolean().default(false)
});
export type TOrganizations = z.infer<typeof OrganizationsSchema>;

@ -26,7 +26,7 @@ const sanitizedExternalSchemaForGetAll = KmsKeysSchema.pick({
isDisabled: true,
createdAt: true,
updatedAt: true,
slug: true
name: true
})
.extend({
externalKms: ExternalKmsSchema.pick({
@ -57,7 +57,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
},
schema: {
body: z.object({
slug: z.string().min(1).trim().toLowerCase(),
name: z.string().min(1).trim().toLowerCase(),
description: z.string().trim().optional(),
provider: ExternalKmsInputSchema
}),
@ -74,7 +74,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
name: req.body.name,
provider: req.body.provider,
description: req.body.description
});
@ -87,7 +87,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
metadata: {
kmsId: externalKms.id,
provider: req.body.provider.type,
slug: req.body.slug,
name: req.body.name,
description: req.body.description
}
}
@ -108,7 +108,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
id: z.string().trim().min(1)
}),
body: z.object({
slug: z.string().min(1).trim().toLowerCase().optional(),
name: z.string().min(1).trim().toLowerCase().optional(),
description: z.string().trim().optional(),
provider: ExternalKmsInputUpdateSchema
}),
@ -125,7 +125,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.body.slug,
name: req.body.name,
provider: req.body.provider,
description: req.body.description,
id: req.params.id
@ -139,7 +139,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
metadata: {
kmsId: externalKms.id,
provider: req.body.provider.type,
slug: req.body.slug,
name: req.body.name,
description: req.body.description
}
}
@ -182,7 +182,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
type: EventType.DELETE_KMS,
metadata: {
kmsId: externalKms.id,
slug: externalKms.slug
name: externalKms.name
}
}
});
@ -224,7 +224,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
type: EventType.GET_KMS,
metadata: {
kmsId: externalKms.id,
slug: externalKms.slug
name: externalKms.name
}
}
});
@ -260,13 +260,13 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
url: "/slug/:slug",
url: "/name/:name",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
slug: z.string().trim().min(1)
name: z.string().trim().min(1)
}),
response: {
200: z.object({
@ -276,12 +276,12 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const externalKms = await server.services.externalKms.findBySlug({
const externalKms = await server.services.externalKms.findByName({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
slug: req.params.slug
name: req.params.name
});
return { externalKms };
}

@ -165,7 +165,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
querystring: z.object({
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
username: z.string().optional().describe(GROUPS.LIST_USERS.username)
username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username),
search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search)
}),
response: {
200: z.object({

@ -203,7 +203,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
200: z.object({
secretManagerKmsKey: z.object({
id: z.string(),
slug: z.string(),
name: z.string(),
isExternal: z.boolean()
})
})
@ -243,7 +243,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
200: z.object({
secretManagerKmsKey: z.object({
id: z.string(),
slug: z.string(),
name: z.string(),
isExternal: z.boolean()
})
})
@ -268,7 +268,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
metadata: {
secretManagerKmsKey: {
id: secretManagerKmsKey.id,
slug: secretManagerKmsKey.slug
name: secretManagerKmsKey.name
}
}
}
@ -336,7 +336,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
200: z.object({
secretManagerKmsKey: z.object({
id: z.string(),
slug: z.string(),
name: z.string(),
isExternal: z.boolean()
})
})

@ -128,7 +128,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
.map((key) => {
// for the ones like in format: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email
const formatedKey = key.startsWith("http") ? key.split("/").at(-1) || "" : key;
return { key: formatedKey, value: String((profile.attributes as Record<string, string>)[key]) };
return {
key: formatedKey,
value: String((profile.attributes as Record<string, string>)[key]).substring(0, 1020)
};
})
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));

@ -20,7 +20,7 @@ const ScimUserSchema = z.object({
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
type: z.string().trim().default("work")
})
)
.optional(),
@ -210,8 +210,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
value: z.string().email()
})
)
.optional(),
@ -281,8 +280,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
.array(
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
value: z.string().email()
})
)
.optional(),
@ -301,7 +299,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
z.object({
primary: z.boolean(),
value: z.string().email(),
type: z.string().trim()
type: z.string().trim().default("work")
})
),
displayName: z.string().trim(),

@ -2,6 +2,8 @@ import { z } from "zod";
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
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";
@ -23,6 +25,13 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const appCfg = getConfig();
if (!appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(req.auth.orgId)) {
throw new BadRequestError({
message: "Secret scanning is temporarily unavailable."
});
}
const session = await server.services.secretScanning.createInstallationSession({
actor: req.permission.type,
actorId: req.permission.id,
@ -30,6 +39,7 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
actorOrgId: req.permission.orgId,
orgId: req.body.organizationId
});
return session;
}
});

@ -58,7 +58,7 @@ export const accessApprovalPolicyServiceFactory = ({
enforcementLevel
}: TCreateAccessApprovalPolicy) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// If there is a group approver people might be added to the group later to meet the approvers quota
const groupApprovers = approvers
@ -89,7 +89,7 @@ export const accessApprovalPolicyServiceFactory = ({
ProjectPermissionSub.SecretApproval
);
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
if (!env) throw new NotFoundError({ message: "Environment not found" });
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
let approverUserIds = userApprovers;
if (userApproverNames.length) {
@ -124,7 +124,9 @@ export const accessApprovalPolicyServiceFactory = ({
const verifyAllApprovers = [...approverUserIds];
for (const groupId of groupApprovers) {
usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }));
usersPromises.push(
groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }).then((group) => group.members)
);
}
const verifyGroupApprovers = (await Promise.all(usersPromises))
.flat()
@ -192,7 +194,7 @@ export const accessApprovalPolicyServiceFactory = ({
projectSlug
}: TListAccessApprovalPoliciesDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone in the project should be able to get the policies.
/* const { permission } = */ await permissionService.getProjectPermission(
@ -243,7 +245,9 @@ export const accessApprovalPolicyServiceFactory = ({
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
}
if (!accessApprovalPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
if (!accessApprovalPolicy) {
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
}
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
@ -327,7 +331,11 @@ export const accessApprovalPolicyServiceFactory = ({
>[] = [];
for (const groupId of groupApprovers) {
usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }));
usersPromises.push(
groupDAL
.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 })
.then((group) => group.members)
);
}
const verifyGroupApprovers = (await Promise.all(usersPromises))
.flat()
@ -376,7 +384,7 @@ export const accessApprovalPolicyServiceFactory = ({
actorOrgId
}: TDeleteAccessApprovalPolicy) => {
const policy = await accessApprovalPolicyDAL.findById(policyId);
if (!policy) throw new NotFoundError({ message: "Secret approval policy not found" });
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@ -404,7 +412,7 @@ export const accessApprovalPolicyServiceFactory = ({
}: TGetAccessPolicyCountByEnvironmentDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
actor,
@ -418,10 +426,10 @@ export const accessApprovalPolicyServiceFactory = ({
}
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
if (!environment) throw new NotFoundError({ message: "Environment not found" });
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
const policies = await accessApprovalPolicyDAL.find({ envId: environment.id, projectId: project.id });
if (!policies) throw new NotFoundError({ message: "No policies found" });
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
return { count: policies.length };
};
@ -437,7 +445,7 @@ export const accessApprovalPolicyServiceFactory = ({
if (!policy) {
throw new NotFoundError({
message: "Cannot find access approval policy"
message: `Cannot find access approval policy with ID ${policyId}`
});
}

@ -99,7 +99,7 @@ export const accessApprovalRequestServiceFactory = ({
}: TCreateAccessApprovalRequestDTO) => {
const cfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
// Anyone can create an access approval request.
const { membership } = await permissionService.getProjectPermission(
@ -121,13 +121,17 @@ export const accessApprovalRequestServiceFactory = ({
const { envSlug, secretPath, accessTypes } = verifyRequestedPermissions({ permissions: requestedPermissions });
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
if (!environment) throw new NotFoundError({ message: "Environment not found" });
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
const policy = await accessApprovalPolicyDAL.findOne({
envId: environment.id,
secretPath
});
if (!policy) throw new NotFoundError({ message: "No policy matching criteria was found." });
if (!policy) {
throw new NotFoundError({
message: `No policy in environment with slug '${environment.slug}' and with secret path '${secretPath}' was found.`
});
}
const approverIds: string[] = [];
const approverGroupIds: string[] = [];
@ -147,10 +151,12 @@ export const accessApprovalRequestServiceFactory = ({
const groupUsers = (
await Promise.all(
approverGroupIds.map((groupApproverId) =>
groupDAL.findAllGroupPossibleMembers({
orgId: actorOrgId,
groupId: groupApproverId
})
groupDAL
.findAllGroupPossibleMembers({
orgId: actorOrgId,
groupId: groupApproverId
})
.then((group) => group.members)
)
)
).flat();
@ -264,7 +270,7 @@ export const accessApprovalRequestServiceFactory = ({
actorAuthMethod
}: TListApprovalRequestsDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
actor,
@ -300,7 +306,9 @@ export const accessApprovalRequestServiceFactory = ({
actorOrgId
}: TReviewAccessRequestDTO) => {
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
if (!accessApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
if (!accessApprovalRequest) {
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
}
const { policy } = accessApprovalRequest;
const { membership, hasRole } = await permissionService.getProjectPermission(
@ -421,7 +429,7 @@ export const accessApprovalRequestServiceFactory = ({
const getCount = async ({ projectSlug, actor, actorAuthMethod, actorId, actorOrgId }: TGetAccessRequestCountDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const { membership } = await permissionService.getProjectPermission(
actor,

@ -130,7 +130,7 @@ export const auditLogStreamServiceFactory = ({
});
const logStream = await auditLogStreamDAL.findById(id);
if (!logStream) throw new NotFoundError({ message: "Audit log stream not found" });
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
const { orgId } = logStream;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
@ -182,7 +182,7 @@ export const auditLogStreamServiceFactory = ({
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
const logStream = await auditLogStreamDAL.findById(id);
if (!logStream) throw new NotFoundError({ message: "Audit log stream not found" });
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
const { orgId } = logStream;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
@ -194,7 +194,7 @@ export const auditLogStreamServiceFactory = ({
const getById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetDetailsAuditLogStreamDTO) => {
const logStream = await auditLogStreamDAL.findById(id);
if (!logStream) throw new NotFoundError({ message: "Audit log stream not found" });
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
const { orgId } = logStream;
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);

@ -1,8 +1,9 @@
import { Knex } from "knex";
// weird commonjs-related error in the CI requires us to do the import like this
import knex from "knex";
import { TDbClient } from "@app/db";
import { AuditLogsSchema, TableName } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { TableName } from "@app/db/schemas";
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
import { ormify, selectAllTableCols } from "@app/lib/knex";
import { logger } from "@app/lib/logger";
import { QueueName } from "@app/queue";
@ -46,7 +47,7 @@ export const auditLogDALFactory = (db: TDbClient) => {
eventType?: EventType[];
eventMetadata?: Record<string, string>;
},
tx?: Knex
tx?: knex.Knex
) => {
if (!orgId && !projectId) {
throw new Error("Either orgId or projectId must be provided");
@ -55,11 +56,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
try {
// Find statements
const sqlQuery = (tx || db.replicaNode())(TableName.AuditLog)
.leftJoin(TableName.Project, `${TableName.AuditLog}.projectId`, `${TableName.Project}.id`)
// eslint-disable-next-line func-names
.where(function () {
if (orgId) {
void this.where(`${TableName.Project}.orgId`, orgId).orWhere(`${TableName.AuditLog}.orgId`, orgId);
void this.where(`${TableName.AuditLog}.orgId`, orgId);
} else if (projectId) {
void this.where(`${TableName.AuditLog}.projectId`, projectId);
}
@ -72,23 +72,19 @@ export const auditLogDALFactory = (db: TDbClient) => {
// Select statements
void sqlQuery
.select(selectAllTableCols(TableName.AuditLog))
.select(
db.ref("name").withSchema(TableName.Project).as("projectName"),
db.ref("slug").withSchema(TableName.Project).as("projectSlug")
)
.limit(limit)
.offset(offset)
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
// Special case: Filter by actor ID
if (actorId) {
void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actorId]);
void sqlQuery.whereRaw(`"actorMetadata" @> jsonb_build_object('userId', ?::text)`, [actorId]);
}
// Special case: Filter by key/value pairs in eventMetadata field
if (eventMetadata && Object.keys(eventMetadata).length) {
Object.entries(eventMetadata).forEach(([key, value]) => {
void sqlQuery.whereRaw(`"eventMetadata"->>'${key}' = ?`, [value]);
void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object(?::text, ?::text)`, [key, value]);
});
}
@ -109,30 +105,25 @@ export const auditLogDALFactory = (db: TDbClient) => {
if (endDate) {
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
}
const docs = await sqlQuery;
return docs.map((doc) => {
// Our type system refuses to acknowledge that the project name and slug are present in the doc, due to the disjointed query structure above.
// This is a quick and dirty way to get around the types.
const projectDoc = doc as unknown as { projectName: string; projectSlug: string };
// we timeout long running queries to prevent DB resource issues (2 minutes)
const docs = await sqlQuery.timeout(1000 * 120);
return {
...AuditLogsSchema.parse(doc),
...(projectDoc?.projectSlug && {
project: {
name: projectDoc.projectName,
slug: projectDoc.projectSlug
}
})
};
});
return docs;
} catch (error) {
if (error instanceof knex.KnexTimeoutError) {
throw new GatewayTimeoutError({
error,
message: "Failed to fetch audit logs due to timeout. Add more search filters."
});
}
throw new DatabaseError({ error });
}
};
// delete all audit log that have expired
const pruneAuditLog = async (tx?: Knex) => {
const pruneAuditLog = async (tx?: knex.Knex) => {
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
@ -148,6 +139,7 @@ export const auditLogDALFactory = (db: TDbClient) => {
.where("expiresAt", "<", today)
.select("id")
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
// eslint-disable-next-line no-await-in-loop
deletedAuditLogIds = await (tx || db)(TableName.AuditLog)
.whereIn("id", findExpiredLogSubQuery)

@ -74,6 +74,7 @@ export const auditLogQueueServiceFactory = ({
actorMetadata: actor.metadata,
userAgent,
projectId,
projectName: project?.name,
ipAddress,
orgId,
eventType: event.type,

@ -1,3 +1,4 @@
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { TProjectPermission } from "@app/lib/types";
import { ActorType } from "@app/services/auth/auth-type";
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
@ -122,6 +123,7 @@ export enum EventType {
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
DELETE_WEBHOOK = "delete-webhook",
GET_SECRET_IMPORTS = "get-secret-imports",
GET_SECRET_IMPORT = "get-secret-import",
CREATE_SECRET_IMPORT = "create-secret-import",
UPDATE_SECRET_IMPORT = "update-secret-import",
DELETE_SECRET_IMPORT = "delete-secret-import",
@ -182,7 +184,15 @@ export enum EventType {
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
INTEGRATION_SYNCED = "integration-synced"
INTEGRATION_SYNCED = "integration-synced",
CREATE_CMEK = "create-cmek",
UPDATE_CMEK = "update-cmek",
DELETE_CMEK = "delete-cmek",
GET_CMEKS = "get-cmeks",
CMEK_ENCRYPT = "cmek-encrypt",
CMEK_DECRYPT = "cmek-decrypt",
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "get-external-group-org-role-mapping"
}
interface UserActorMetadata {
@ -1004,6 +1014,14 @@ interface GetSecretImportsEvent {
};
}
interface GetSecretImportEvent {
type: EventType.GET_SECRET_IMPORT;
metadata: {
secretImportId: string;
folderId: string;
};
}
interface CreateSecretImportEvent {
type: EventType.CREATE_SECRET_IMPORT;
metadata: {
@ -1350,7 +1368,7 @@ interface CreateKmsEvent {
metadata: {
kmsId: string;
provider: string;
slug: string;
name: string;
description?: string;
};
}
@ -1359,7 +1377,7 @@ interface DeleteKmsEvent {
type: EventType.DELETE_KMS;
metadata: {
kmsId: string;
slug: string;
name: string;
};
}
@ -1368,7 +1386,7 @@ interface UpdateKmsEvent {
metadata: {
kmsId: string;
provider: string;
slug?: string;
name?: string;
description?: string;
};
}
@ -1377,7 +1395,7 @@ interface GetKmsEvent {
type: EventType.GET_KMS;
metadata: {
kmsId: string;
slug: string;
name: string;
};
}
@ -1386,7 +1404,7 @@ interface UpdateProjectKmsEvent {
metadata: {
secretManagerKmsKey: {
id: string;
slug: string;
name: string;
};
};
}
@ -1541,6 +1559,65 @@ interface IntegrationSyncedEvent {
};
}
interface CreateCmekEvent {
type: EventType.CREATE_CMEK;
metadata: {
keyId: string;
name: string;
description?: string;
encryptionAlgorithm: SymmetricEncryption;
};
}
interface DeleteCmekEvent {
type: EventType.DELETE_CMEK;
metadata: {
keyId: string;
};
}
interface UpdateCmekEvent {
type: EventType.UPDATE_CMEK;
metadata: {
keyId: string;
name?: string;
description?: string;
};
}
interface GetCmeksEvent {
type: EventType.GET_CMEKS;
metadata: {
keyIds: string[];
};
}
interface CmekEncryptEvent {
type: EventType.CMEK_ENCRYPT;
metadata: {
keyId: string;
};
}
interface CmekDecryptEvent {
type: EventType.CMEK_DECRYPT;
metadata: {
keyId: string;
};
}
interface GetExternalGroupOrgRoleMappingsEvent {
type: EventType.GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS;
metadata?: Record<string, never>; // not needed, based off orgId
}
interface UpdateExternalGroupOrgRoleMappingsEvent {
type: EventType.UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS;
metadata: {
mappings: { groupName: string; roleSlug: string }[];
};
}
export type Event =
| GetSecretsEvent
| GetSecretEvent
@ -1620,6 +1697,7 @@ export type Event =
| UpdateWebhookStatusEvent
| DeleteWebhookEvent
| GetSecretImportsEvent
| GetSecretImportEvent
| CreateSecretImportEvent
| UpdateSecretImportEvent
| DeleteSecretImportEvent
@ -1680,4 +1758,12 @@ export type Event =
| GetSlackIntegration
| UpdateProjectSlackConfig
| GetProjectSlackConfig
| IntegrationSyncedEvent;
| IntegrationSyncedEvent
| CreateCmekEvent
| UpdateCmekEvent
| DeleteCmekEvent
| GetCmeksEvent
| CmekEncryptEvent
| CmekDecryptEvent
| GetExternalGroupOrgRoleMappingsEvent
| UpdateExternalGroupOrgRoleMappingsEvent;

@ -34,7 +34,7 @@ export const certificateAuthorityCrlServiceFactory = ({
*/
const getCrlById = async (crlId: TGetCrlById) => {
const caCrl = await certificateAuthorityCrlDAL.findById(crlId);
if (!caCrl) throw new NotFoundError({ message: "CRL not found" });
if (!caCrl) throw new NotFoundError({ message: `CRL with ID '${crlId}' not found` });
const ca = await certificateAuthorityDAL.findById(caCrl.caId);
@ -64,7 +64,7 @@ export const certificateAuthorityCrlServiceFactory = ({
*/
const getCaCrls = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => {
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new NotFoundError({ message: "CA not found" });
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,

@ -211,7 +211,7 @@ export const certificateEstServiceFactory = ({
const certTemplate = await certificateTemplateDAL.findById(certificateTemplateId);
if (!certTemplate) {
throw new NotFoundError({
message: "Certificate template not found"
message: `Certificate template with ID '${certificateTemplateId}' not found`
});
}
@ -236,7 +236,7 @@ export const certificateEstServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(certTemplate.caId);
if (!ca) {
throw new NotFoundError({
message: "Certificate Authority not found"
message: `Certificate Authority with ID '${certTemplate.caId}' not found`
});
}

@ -61,7 +61,7 @@ export const dynamicSecretLeaseServiceFactory = ({
}: TCreateDynamicSecretLeaseDTO) => {
const appCfg = getConfig();
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
@ -84,10 +84,16 @@ export const dynamicSecretLeaseServiceFactory = ({
}
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
});
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
if (!dynamicSecretCfg)
throw new NotFoundError({
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
});
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
@ -134,7 +140,7 @@ export const dynamicSecretLeaseServiceFactory = ({
leaseId
}: TRenewDynamicSecretLeaseDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
@ -157,10 +163,15 @@ export const dynamicSecretLeaseServiceFactory = ({
}
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
});
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease not found" });
if (!dynamicSecretLease) {
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
}
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
@ -208,7 +219,7 @@ export const dynamicSecretLeaseServiceFactory = ({
isForced
}: TDeleteDynamicSecretLeaseDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
@ -224,10 +235,14 @@ export const dynamicSecretLeaseServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
});
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease not found" });
if (!dynamicSecretLease)
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
@ -273,7 +288,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorAuthMethod
}: TListDynamicSecretLeasesDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
@ -289,10 +304,16 @@ export const dynamicSecretLeaseServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
});
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
if (!dynamicSecretCfg)
throw new NotFoundError({
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
});
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
return dynamicSecretLeases;
@ -309,7 +330,7 @@ export const dynamicSecretLeaseServiceFactory = ({
actorAuthMethod
}: TDetailsDynamicSecretLeaseDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
@ -325,10 +346,11 @@ export const dynamicSecretLeaseServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder) throw new NotFoundError({ message: `Folder with path '${path}' not found` });
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease not found" });
if (!dynamicSecretLease)
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
return dynamicSecretLease;
};

@ -66,7 +66,7 @@ export const dynamicSecretServiceFactory = ({
actorAuthMethod
}: TCreateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
@ -89,7 +89,9 @@ export const dynamicSecretServiceFactory = ({
}
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder) {
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
}
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
if (existingDynamicSecret)
@ -134,7 +136,7 @@ export const dynamicSecretServiceFactory = ({
actorAuthMethod
}: TUpdateDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
@ -158,11 +160,15 @@ export const dynamicSecretServiceFactory = ({
}
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
if (!dynamicSecretCfg) {
throw new NotFoundError({
message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found`
});
}
if (newName) {
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
if (existingDynamicSecret)
@ -213,7 +219,7 @@ export const dynamicSecretServiceFactory = ({
isForced
}: TDeleteDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
@ -230,10 +236,13 @@ export const dynamicSecretServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
if (!dynamicSecretCfg) {
throw new NotFoundError({ message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found` });
}
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
// when not forced we check with the external system to first remove the things
@ -271,7 +280,7 @@ export const dynamicSecretServiceFactory = ({
actor
}: TDetailsDynamicSecretDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const { permission } = await permissionService.getProjectPermission(
@ -287,10 +296,13 @@ export const dynamicSecretServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
if (!dynamicSecretCfg) {
throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` });
}
const decryptedStoredInput = JSON.parse(
infisicalSymmetricDecrypt({
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
@ -335,7 +347,11 @@ export const dynamicSecretServiceFactory = ({
}
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
if (!folders.length) throw new NotFoundError({ message: "Folders not found" });
if (!folders.length) {
throw new NotFoundError({
message: `Folders with path '${path}' in environments with slugs '${environmentSlugs.join(", ")}' not found`
});
}
const dynamicSecretCfg = await dynamicSecretDAL.find(
{ $in: { folderId: folders.map((folder) => folder.id) }, $search: search ? { name: `%${search}%` } : undefined },
@ -369,7 +385,9 @@ export const dynamicSecretServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder) {
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
}
const dynamicSecretCfg = await dynamicSecretDAL.find(
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
@ -398,7 +416,7 @@ export const dynamicSecretServiceFactory = ({
if (!projectId) {
if (!projectSlug) throw new BadRequestError({ message: "Project ID or slug required" });
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
projectId = project.id;
}
@ -415,7 +433,8 @@ export const dynamicSecretServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
const dynamicSecretCfg = await dynamicSecretDAL.find(
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
@ -459,7 +478,10 @@ export const dynamicSecretServiceFactory = ({
}
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
if (!folders.length) throw new NotFoundError({ message: "Folders not found" });
if (!folders.length)
throw new NotFoundError({
message: `Folders with path '${path} in environments with slugs '${environmentSlugs.join(", ")}' not found`
});
const dynamicSecretCfg = await dynamicSecretDAL.listDynamicSecretsByFolderIds({
folderIds: folders.map((folder) => folder.id),

@ -1,6 +1,6 @@
import handlebars from "handlebars";
import ldapjs from "ldapjs";
import ldif from "ldif";
import mustache from "mustache";
import { customAlphabet } from "nanoid";
import { z } from "zod";
@ -40,7 +40,8 @@ const generateLDIF = ({
EncodedPassword: encodePassword(password)
};
const renderedLdif = mustache.render(ldifTemplate, data);
const renderTemplate = handlebars.compile(ldifTemplate);
const renderedLdif = renderTemplate(data);
return renderedLdif;
};

@ -30,7 +30,7 @@ export const externalKmsDALFactory = (db: TDbClient) => {
isDisabled: el.isDisabled,
isReserved: el.isReserved,
orgId: el.orgId,
slug: el.slug,
name: el.name,
createdAt: el.createdAt,
updatedAt: el.updatedAt,
externalKms: {

@ -43,7 +43,7 @@ export const externalKmsServiceFactory = ({
provider,
description,
actor,
slug,
name,
actorId,
actorOrgId,
actorAuthMethod
@ -64,7 +64,7 @@ export const externalKmsServiceFactory = ({
});
}
const kmsSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
const kmsName = name ? slugify(name) : slugify(alphaNumericNanoId(8).toLowerCase());
let sanitizedProviderInput = "";
switch (provider.type) {
@ -96,7 +96,7 @@ export const externalKmsServiceFactory = ({
{
isReserved: false,
description,
slug: kmsSlug,
name: kmsName,
orgId: actorOrgId
},
tx
@ -120,7 +120,7 @@ export const externalKmsServiceFactory = ({
description,
actor,
id: kmsId,
slug,
name,
actorId,
actorOrgId,
actorAuthMethod
@ -142,10 +142,10 @@ export const externalKmsServiceFactory = ({
});
}
const kmsSlug = slug ? slugify(slug) : undefined;
const kmsName = name ? slugify(name) : undefined;
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
let sanitizedProviderInput = "";
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
@ -188,7 +188,7 @@ export const externalKmsServiceFactory = ({
kmsDoc.id,
{
description,
slug: kmsSlug
name: kmsName
},
tx
);
@ -220,7 +220,7 @@ export const externalKmsServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
const externalKms = await externalKmsDAL.transaction(async (tx) => {
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
@ -258,7 +258,7 @@ export const externalKmsServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
@ -280,14 +280,14 @@ export const externalKmsServiceFactory = ({
}
};
const findBySlug = async ({
const findByName = async ({
actor,
actorId,
actorOrgId,
actorAuthMethod,
slug: kmsSlug
name: kmsName
}: TGetExternalKmsBySlugDTO) => {
const kmsDoc = await kmsDAL.findOne({ slug: kmsSlug, orgId: actorOrgId });
const kmsDoc = await kmsDAL.findOne({ name: kmsName, orgId: actorOrgId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
@ -298,7 +298,7 @@ export const externalKmsServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsDoc.id}' not found` });
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.Organization,
@ -327,6 +327,6 @@ export const externalKmsServiceFactory = ({
deleteById,
list,
findById,
findBySlug
findByName
};
};

@ -3,14 +3,14 @@ import { TOrgPermission } from "@app/lib/types";
import { TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "./providers/model";
export type TCreateExternalKmsDTO = {
slug?: string;
name?: string;
description?: string;
provider: TExternalKmsInputSchema;
} & Omit<TOrgPermission, "orgId">;
export type TUpdateExternalKmsDTO = {
id: string;
slug?: string;
name?: string;
description?: string;
provider?: TExternalKmsInputUpdateSchema;
} & Omit<TOrgPermission, "orgId">;
@ -26,5 +26,5 @@ export type TGetExternalKmsByIdDTO = {
} & Omit<TOrgPermission, "orgId">;
export type TGetExternalKmsBySlugDTO = {
slug: string;
name: string;
} & Omit<TOrgPermission, "orgId">;

@ -65,16 +65,18 @@ export const groupDALFactory = (db: TDbClient) => {
groupId,
offset = 0,
limit,
username
username, // depreciated in favor of search
search
}: {
orgId: string;
groupId: string;
offset?: number;
limit?: number;
username?: string;
search?: string;
}) => {
try {
let query = db
const query = db
.replicaNode()(TableName.OrgMembership)
.where(`${TableName.OrgMembership}.orgId`, orgId)
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
@ -92,31 +94,39 @@ export const groupDALFactory = (db: TDbClient) => {
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
db.ref("id").withSchema(TableName.Users).as("userId")
db.ref("id").withSchema(TableName.Users).as("userId"),
db.raw(`count(*) OVER() as total_count`)
)
.where({ isGhost: false })
.offset(offset);
.offset(offset)
.orderBy("firstName", "asc");
if (limit) {
query = query.limit(limit);
void query.limit(limit);
}
if (username) {
query = query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
if (search) {
void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", "username") ilike '%${search}%'`);
} else if (username) {
void query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
}
const members = await query;
return members.map(
({ email, username: memberUsername, firstName, lastName, userId, groupId: memberGroupId }) => ({
id: userId,
email,
username: memberUsername,
firstName,
lastName,
isPartOfGroup: !!memberGroupId
})
);
return {
members: members.map(
({ email, username: memberUsername, firstName, lastName, userId, groupId: memberGroupId }) => ({
id: userId,
email,
username: memberUsername,
firstName,
lastName,
isPartOfGroup: !!memberGroupId
})
),
// @ts-expect-error col select is raw and not strongly typed
totalCount: Number(members?.[0]?.total_count ?? 0)
};
} catch (error) {
throw new DatabaseError({ error, name: "Find all org members" });
}

@ -74,7 +74,7 @@ const addAcceptedUsersToGroup = async ({
if (!ghostUser) {
throw new NotFoundError({
message: "Failed to find project owner"
message: `Failed to find project owner of project with ID '${projectId}'`
});
}
@ -82,7 +82,7 @@ const addAcceptedUsersToGroup = async ({
if (!ghostUserLatestKey) {
throw new NotFoundError({
message: "Failed to find project owner's latest key"
message: `Failed to find project owner's latest key in project with ID '${projectId}'`
});
}
@ -90,7 +90,7 @@ const addAcceptedUsersToGroup = async ({
if (!bot) {
throw new NotFoundError({
message: "Failed to find project bot"
message: `Failed to find project bot in project with ID '${projectId}'`
});
}

@ -221,7 +221,8 @@ export const groupServiceFactory = ({
actor,
actorId,
actorAuthMethod,
actorOrgId
actorOrgId,
search
}: TListGroupUsersDTO) => {
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
@ -244,17 +245,16 @@ export const groupServiceFactory = ({
message: `Failed to find group with ID ${id}`
});
const users = await groupDAL.findAllGroupPossibleMembers({
const { members, totalCount } = await groupDAL.findAllGroupPossibleMembers({
orgId: group.orgId,
groupId: group.id,
offset,
limit,
username
username,
search
});
const count = await orgDAL.countAllOrgMembers(group.orgId);
return { users, totalCount: count };
return { users: members, totalCount };
};
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {

@ -38,6 +38,7 @@ export type TListGroupUsersDTO = {
offset: number;
limit: number;
username?: string;
search?: string;
} & TGenericPermission;
export type TAddUserToGroupDTO = {

@ -65,7 +65,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
...dto
}: TCreateIdentityPrivilegeDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
@ -137,7 +137,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorAuthMethod
}: TUpdateIdentityPrivilegeDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
@ -167,7 +167,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
if (!identityPrivilege) {
throw new NotFoundError({
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
});
}
if (data?.slug) {
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
slug: data.slug,
@ -218,7 +222,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorAuthMethod
}: TDeleteIdentityPrivilegeDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
@ -248,7 +252,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
if (!identityPrivilege) {
throw new NotFoundError({
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
});
}
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
return {
@ -268,7 +276,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
actorAuthMethod
}: TGetIdentityPrivilegeDetailsDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
@ -287,8 +295,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
slug,
projectMembershipId: identityProjectMembership.id
});
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
if (!identityPrivilege) {
throw new NotFoundError({
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
});
}
return {
...identityPrivilege,
permissions: unpackPermissions(identityPrivilege.permissions)
@ -304,7 +315,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
projectSlug
}: TListIdentityPrivilegesDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new NotFoundError({ message: "Project not found" });
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
const projectId = project.id;
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });

@ -1,14 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import {
OrgMembershipRole,
OrgMembershipStatus,
SecretKeyEncoding,
TableName,
TLdapConfigsUpdate,
TUsers
} from "@app/db/schemas";
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TLdapConfigsUpdate, TUsers } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -28,6 +21,7 @@ import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
@ -253,7 +247,11 @@ export const ldapConfigServiceFactory = ({
};
const orgBot = await orgBotDAL.findOne({ orgId });
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
if (!orgBot)
throw new NotFoundError({
message: `Organization bot in organization with ID '${orgId}' not found`,
name: "OrgBotNotFound"
});
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
@ -289,10 +287,19 @@ export const ldapConfigServiceFactory = ({
const getLdapCfg = async (filter: { orgId: string; isActive?: boolean; id?: string }) => {
const ldapConfig = await ldapConfigDAL.findOne(filter);
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
if (!ldapConfig) {
throw new NotFoundError({
message: `Failed to find organization LDAP data in organization with ID '${filter.orgId}'`
});
}
const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId });
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
if (!orgBot) {
throw new NotFoundError({
message: `Organization bot not found in organization with ID ${ldapConfig.orgId}`,
name: "OrgBotNotFound"
});
}
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
@ -375,7 +382,7 @@ export const ldapConfigServiceFactory = ({
const bootLdap = async (organizationSlug: string) => {
const organization = await orgDAL.findOne({ slug: organizationSlug });
if (!organization) throw new NotFoundError({ message: "Organization not found" });
if (!organization) throw new NotFoundError({ message: `Organization with slug '${organizationSlug}' not found` });
const ldapConfig = await getLdapCfg({
orgId: organization.id,
@ -432,7 +439,7 @@ export const ldapConfigServiceFactory = ({
});
const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new NotFoundError({ message: "Organization not found" });
if (!organization) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
if (userAlias) {
await userDAL.transaction(async (tx) => {
@ -444,11 +451,14 @@ export const ldapConfigServiceFactory = ({
{ tx }
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgDAL.createMembership(
{
userId: userAlias.userId,
orgId,
role: OrgMembershipRole.Member,
role,
roleId,
status: OrgMembershipStatus.Accepted,
isActive: true
},
@ -529,12 +539,15 @@ export const ldapConfigServiceFactory = ({
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
{
userId: newUser.id,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
role,
roleId,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
@ -700,7 +713,11 @@ export const ldapConfigServiceFactory = ({
orgId
});
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
if (!ldapConfig) {
throw new NotFoundError({
message: `Failed to find organization LDAP data with ID '${ldapConfigId}' in organization with ID ${orgId}`
});
}
const groupMaps = await ldapGroupMapDAL.findLdapGroupMapsByLdapConfigId(ldapConfigId);
@ -747,7 +764,11 @@ export const ldapConfigServiceFactory = ({
}
const group = await groupDAL.findOne({ slug: groupSlug, orgId });
if (!group) throw new NotFoundError({ message: "Failed to find group" });
if (!group) {
throw new NotFoundError({
message: `Failed to find group with slug '${groupSlug}' in organization with ID '${orgId}'`
});
}
const groupMap = await ldapGroupMapDAL.create({
ldapConfigId,
@ -781,7 +802,11 @@ export const ldapConfigServiceFactory = ({
orgId
});
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
if (!ldapConfig) {
throw new NotFoundError({
message: `Failed to find organization LDAP data with ID '${ldapConfigId}' in organization with ID ${orgId}`
});
}
const [deletedGroupMap] = await ldapGroupMapDAL.delete({
ldapConfigId: ldapConfig.id,

@ -46,7 +46,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
writeLimit: 200,
secretsLimit: 40
},
pkiEst: false
pkiEst: false,
enforceMfa: false
});
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {

@ -145,7 +145,7 @@ export const licenseServiceFactory = ({
if (cachedPlan) return JSON.parse(cachedPlan) as TFeatureSet;
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: "Organization not found" });
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
const {
data: { currentPlan }
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
@ -204,7 +204,7 @@ export const licenseServiceFactory = ({
const updateSubscriptionOrgMemberCount = async (orgId: string, tx?: Knex) => {
if (instanceType === InstanceType.Cloud) {
const org = await orgDAL.findOrgById(orgId);
if (!org) throw new NotFoundError({ message: "Organization not found" });
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
const quantity = await licenseDAL.countOfOrgMembers(orgId, tx);
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(orgId, tx);
@ -267,7 +267,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
@ -341,7 +341,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
const { data } = await licenseServerCloudApi.request.get(
@ -358,7 +358,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
const { data } = await licenseServerCloudApi.request.get(
@ -374,7 +374,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
@ -399,7 +399,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
const { data } = await licenseServerCloudApi.request.patch(
@ -419,7 +419,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
@ -446,7 +446,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
const {
@ -475,7 +475,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
@ -492,7 +492,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
const {
@ -510,7 +510,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
@ -531,7 +531,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
@ -548,7 +548,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}
@ -565,7 +565,7 @@ export const licenseServiceFactory = ({
const organization = await orgDAL.findOrgById(orgId);
if (!organization) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with ID '${orgId}' not found`
});
}

@ -64,6 +64,7 @@ export type TFeatureSet = {
secretsLimit: number;
};
pkiEst: boolean;
enforceMfa: boolean;
};
export type TOrgPlansTableDTO = {

@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
@ -23,6 +23,7 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
@ -78,7 +79,7 @@ export const oidcConfigServiceFactory = ({
const org = await orgDAL.findOne({ slug: dto.orgSlug });
if (!org) {
throw new NotFoundError({
message: "Organization not found",
message: `Organization with slug '${dto.orgSlug}' not found`,
name: "OrgNotFound"
});
}
@ -99,14 +100,17 @@ export const oidcConfigServiceFactory = ({
if (!oidcCfg) {
throw new NotFoundError({
message: "Failed to find organization OIDC configuration"
message: `OIDC configuration for organization with slug '${dto.orgSlug}' not found`
});
}
// decrypt and return cfg
const orgBot = await orgBotDAL.findOne({ orgId: oidcCfg.orgId });
if (!orgBot) {
throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
throw new NotFoundError({
message: `Organization bot for organization with ID '${oidcCfg.orgId}' not found`,
name: "OrgBotNotFound"
});
}
const key = infisicalSymmetricDecrypt({
@ -173,7 +177,7 @@ export const oidcConfigServiceFactory = ({
});
const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new NotFoundError({ message: "Organization not found" });
if (!organization) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
let user: TUsers;
if (userAlias) {
@ -187,12 +191,15 @@ export const oidcConfigServiceFactory = ({
{ tx }
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
{
userId: userAlias.userId,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
role,
roleId,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
@ -261,12 +268,15 @@ export const oidcConfigServiceFactory = ({
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
{
userId: newUser.id,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
role,
roleId,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
@ -359,7 +369,7 @@ export const oidcConfigServiceFactory = ({
if (!org) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with slug '${orgSlug}' not found`
});
}
@ -380,7 +390,11 @@ export const oidcConfigServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
const orgBot = await orgBotDAL.findOne({ orgId: org.id });
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
if (!orgBot)
throw new NotFoundError({
message: `Organization bot for organization with ID '${org.id}' not found`,
name: "OrgBotNotFound"
});
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
@ -448,7 +462,7 @@ export const oidcConfigServiceFactory = ({
});
if (!org) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with slug '${orgSlug}' not found`
});
}
@ -554,7 +568,7 @@ export const oidcConfigServiceFactory = ({
if (!org) {
throw new NotFoundError({
message: "Organization not found."
message: `Organization with slug '${orgSlug}' not found`
});
}

@ -64,7 +64,7 @@ export const permissionServiceFactory = ({
permissions as PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[]
);
default:
throw new NotFoundError({ name: "OrgRoleInvalid", message: "Organization role not found" });
throw new NotFoundError({ name: "OrgRoleInvalid", message: `Organization role '${role}' not found` });
}
})
.reduce((curr, prev) => prev.concat(curr), []);
@ -94,7 +94,7 @@ export const permissionServiceFactory = ({
default:
throw new NotFoundError({
name: "ProjectRoleInvalid",
message: "Project role not found"
message: `Project role '${role}' not found`
});
}
})
@ -145,7 +145,7 @@ export const permissionServiceFactory = ({
const membership = await permissionDAL.getOrgIdentityPermission(identityId, orgId);
if (!membership) throw new ForbiddenRequestError({ name: "Identity is not apart of this organization" });
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
throw new NotFoundError({ name: "Custom organization permission not found" });
throw new NotFoundError({ name: `Custom organization permission not found for identity ${identityId}` });
}
return {
permission: buildOrgPermission([{ role: membership.role, permissions: membership.permissions }]),
@ -179,7 +179,10 @@ export const permissionServiceFactory = ({
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
if (isCustomRole) {
const orgRole = await orgRoleDAL.findOne({ slug: role, orgId });
if (!orgRole) throw new NotFoundError({ message: "Specified role was not found" });
if (!orgRole)
throw new NotFoundError({
message: `Specified role '${role}' was not found in the organization with ID '${orgId}'`
});
return {
permission: buildOrgPermission([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]),
role: orgRole
@ -264,7 +267,9 @@ export const permissionServiceFactory = ({
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
if (!identityProjectPermission)
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified project" });
throw new ForbiddenRequestError({
name: `Identity is not a member of the specified project with ID '${projectId}'`
});
if (
identityProjectPermission.roles.some(
@ -326,7 +331,7 @@ export const permissionServiceFactory = ({
actorOrgId: string | undefined
) => {
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
if (!serviceToken) throw new NotFoundError({ message: "Service token not found" });
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
@ -337,11 +342,15 @@ export const permissionServiceFactory = ({
}
if (serviceToken.projectId !== projectId) {
throw new ForbiddenRequestError({ name: "Service token not a part of the specified project" });
throw new ForbiddenRequestError({
name: `Service token not a part of the specified project with ID ${projectId}`
});
}
if (serviceTokenProject.orgId !== actorOrgId) {
throw new ForbiddenRequestError({ message: "Service token not a part of the specified organization" });
throw new ForbiddenRequestError({
message: `Service token not a part of the specified organization with ID ${actorOrgId}`
});
}
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);

@ -14,6 +14,15 @@ export enum ProjectPermissionActions {
Delete = "delete"
}
export enum ProjectPermissionCmekActions {
Read = "read",
Create = "create",
Edit = "edit",
Delete = "delete",
Encrypt = "encrypt",
Decrypt = "decrypt"
}
export enum ProjectPermissionSub {
Role = "role",
Member = "member",
@ -38,7 +47,8 @@ export enum ProjectPermissionSub {
CertificateTemplates = "certificate-templates",
PkiAlerts = "pki-alerts",
PkiCollections = "pki-collections",
Kms = "kms"
Kms = "kms",
Cmek = "cmek"
}
export type SecretSubjectFields = {
@ -95,6 +105,7 @@ export type ProjectPermissionSet =
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
@ -282,6 +293,12 @@ export const ProjectPermissionSchema = z.discriminatedUnion("subject", [
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read]).describe(
"Describe what action an entity can take."
)
}),
z.object({
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
"Describe what action an entity can take."
)
})
]);
@ -325,6 +342,17 @@ const buildAdminPermissionRules = () => {
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt
],
ProjectPermissionSub.Cmek
);
return rules;
};
@ -444,6 +472,18 @@ const buildMemberPermissionRules = () => {
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
can(
[
ProjectPermissionCmekActions.Create,
ProjectPermissionCmekActions.Edit,
ProjectPermissionCmekActions.Delete,
ProjectPermissionCmekActions.Read,
ProjectPermissionCmekActions.Encrypt,
ProjectPermissionCmekActions.Decrypt
],
ProjectPermissionSub.Cmek
);
return rules;
};
@ -470,6 +510,7 @@ const buildViewerPermissionRules = () => {
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
return rules;
};

@ -42,7 +42,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
...dto
}: TCreateUserPrivilegeDTO) => {
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID '${projectMembershipId}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@ -94,14 +95,18 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
...dto
}: TUpdateUserPrivilegeDTO) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege not found" });
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID '${privilegeId}' not found` });
const projectMembership = await projectMembershipDAL.findOne({
userId: userPrivilege.userId,
projectId: userPrivilege.projectId
});
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
if (!projectMembership)
throw new NotFoundError({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
actor,
@ -147,13 +152,17 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege not found" });
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID '${privilegeId}' not found` });
const projectMembership = await projectMembershipDAL.findOne({
userId: userPrivilege.userId,
projectId: userPrivilege.projectId
});
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
if (!projectMembership)
throw new NotFoundError({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
actor,
@ -176,13 +185,17 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorAuthMethod
}: TGetUserPrivilegeDetailsDTO) => {
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege not found" });
if (!userPrivilege)
throw new NotFoundError({ message: `User additional privilege with ID '${privilegeId}' not found` });
const projectMembership = await projectMembershipDAL.findOne({
userId: userPrivilege.userId,
projectId: userPrivilege.projectId
});
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
if (!projectMembership)
throw new NotFoundError({
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
});
const { permission } = await permissionService.getProjectPermission(
actor,
@ -204,7 +217,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
actorAuthMethod
}: TListUserPrivilegesDTO) => {
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
if (!projectMembership)
throw new NotFoundError({ message: `Project membership with ID '${projectMembershipId}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,

@ -2,7 +2,6 @@ import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import {
OrgMembershipRole,
OrgMembershipStatus,
SecretKeyEncoding,
TableName,
@ -26,6 +25,7 @@ import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
@ -191,7 +191,11 @@ export const samlConfigServiceFactory = ({
const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null };
const orgBot = await orgBotDAL.findOne({ orgId });
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
if (!orgBot)
throw new NotFoundError({
message: `Organization bot not found for organization with ID '${orgId}'`,
name: "OrgBotNotFound"
});
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
@ -257,7 +261,7 @@ export const samlConfigServiceFactory = ({
ssoConfig = await samlConfigDAL.findById(id);
}
if (!ssoConfig) throw new NotFoundError({ message: "Failed to find organization SSO data" });
if (!ssoConfig) throw new NotFoundError({ message: `Failed to find SSO data` });
// when dto is type id means it's internally used
if (dto.type === "org") {
@ -283,7 +287,11 @@ export const samlConfigServiceFactory = ({
} = ssoConfig;
const orgBot = await orgBotDAL.findOne({ orgId: ssoConfig.orgId });
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
if (!orgBot)
throw new NotFoundError({
message: `Organization bot not found in organization with ID '${ssoConfig.orgId}'`,
name: "OrgBotNotFound"
});
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
@ -355,7 +363,7 @@ export const samlConfigServiceFactory = ({
});
const organization = await orgDAL.findOrgById(orgId);
if (!organization) throw new NotFoundError({ message: "Organization not found" });
if (!organization) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
let user: TUsers;
if (userAlias) {
@ -369,12 +377,15 @@ export const samlConfigServiceFactory = ({
{ tx }
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
{
userId: userAlias.userId,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
role,
roleId,
status: foundUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
@ -472,12 +483,15 @@ export const samlConfigServiceFactory = ({
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(organization.defaultMembershipRole);
await orgMembershipDAL.create(
{
userId: newUser.id,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
role,
roleId,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},

@ -3,7 +3,7 @@ import slugify from "@sindresorhus/slugify";
import jwt from "jsonwebtoken";
import { scimPatch } from "scim-patch";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TOrgMemberships, TUsers } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus, TableName, TGroups, TOrgMemberships, TUsers } from "@app/db/schemas";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
@ -13,9 +13,11 @@ import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } f
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { TOrgPermission } from "@app/lib/types";
import { AuthTokenType } from "@app/services/auth/auth-type";
import { TExternalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { deleteOrgMembershipFn } from "@app/services/org/org-fns";
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
@ -70,7 +72,10 @@ type TScimServiceFactoryDep = {
| "transaction"
| "updateMembershipById"
>;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "findOne" | "create" | "updateById" | "findById">;
orgMembershipDAL: Pick<
TOrgMembershipDALFactory,
"find" | "findOne" | "create" | "updateById" | "findById" | "update"
>;
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
groupDAL: Pick<
@ -101,6 +106,7 @@ type TScimServiceFactoryDep = {
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
smtpService: Pick<TSmtpService, "sendMail">;
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
};
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
@ -121,7 +127,8 @@ export const scimServiceFactory = ({
projectBotDAL,
permissionService,
projectUserAdditionalPrivilegeDAL,
smtpService
smtpService,
externalGroupOrgRoleMappingDAL
}: TScimServiceFactoryDep) => {
const createScimToken = async ({
actor,
@ -176,7 +183,7 @@ export const scimServiceFactory = ({
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => {
let scimToken = await scimDAL.findById(scimTokenId);
if (!scimToken) throw new NotFoundError({ message: "Failed to find SCIM token to delete" });
if (!scimToken) throw new NotFoundError({ message: `SCIM token with ID '${scimTokenId}' not found` });
const { permission } = await permissionService.getOrgPermission(
actor,
@ -318,12 +325,15 @@ export const scimServiceFactory = ({
);
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
orgMembership = await orgMembershipDAL.create(
{
userId: userAlias.userId,
inviteEmail: email,
orgId,
role: OrgMembershipRole.NoAccess,
role,
roleId,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
@ -391,12 +401,15 @@ export const scimServiceFactory = ({
orgMembership = foundOrgMembership;
if (!orgMembership) {
const { role, roleId } = await getDefaultOrgMembershipRole(org.defaultMembershipRole);
orgMembership = await orgMembershipDAL.create(
{
userId: user.id,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
role,
roleId,
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited, // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
isActive: true
},
@ -685,6 +698,43 @@ export const scimServiceFactory = ({
});
};
const $syncNewMembersRoles = async (group: TGroups, members: TScimGroup["members"]) => {
// this function handles configuring newly provisioned users org membership if an external group mapping exists
if (!members.length) return;
const externalGroupMapping = await externalGroupOrgRoleMappingDAL.findOne({
orgId: group.orgId,
groupName: group.name
});
// no mapping, user will have default org membership
if (!externalGroupMapping) return;
// only get org memberships that are new (invites)
const newOrgMemberships = await orgMembershipDAL.find({
status: "invited",
$in: {
id: members.map((member) => member.value)
}
});
if (!newOrgMemberships.length) return;
// set new membership roles to group mapping value
await orgMembershipDAL.update(
{
$in: {
id: newOrgMemberships.map((membership) => membership.id)
}
},
{
role: externalGroupMapping.role,
roleId: externalGroupMapping.roleId
}
);
};
const createScimGroup = async ({ displayName, orgId, members }: TCreateScimGroupDTO) => {
const plan = await licenseService.getPlan(orgId);
if (!plan.groups)
@ -738,6 +788,8 @@ export const scimServiceFactory = ({
tx
});
await $syncNewMembersRoles(group, members);
return { group, newMembers };
}
@ -782,10 +834,12 @@ export const scimServiceFactory = ({
});
}
const users = await groupDAL.findAllGroupPossibleMembers({
orgId: group.orgId,
groupId: group.id
});
const users = await groupDAL
.findAllGroupPossibleMembers({
orgId: group.orgId,
groupId: group.id
})
.then((g) => g.members);
const orgMemberships = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
@ -813,22 +867,41 @@ export const scimServiceFactory = ({
orgId: string,
{ displayName, members = [] }: { displayName: string; members: { value: string }[] }
) => {
const updatedGroup = await groupDAL.transaction(async (tx) => {
const [group] = await groupDAL.update(
{
id: groupId,
orgId
},
{
name: displayName
}
);
let group = await groupDAL.findOne({
id: groupId,
orgId
});
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
if (!group) {
throw new ScimRequestError({
detail: "Group Not Found",
status: 404
});
}
const updatedGroup = await groupDAL.transaction(async (tx) => {
if (group.name !== displayName) {
await externalGroupOrgRoleMappingDAL.update(
{
groupName: group.name,
orgId
},
{
groupName: displayName
}
);
const [modifiedGroup] = await groupDAL.update(
{
id: groupId,
orgId
},
{
name: displayName
}
);
group = modifiedGroup;
}
const orgMemberships = members.length
@ -885,6 +958,8 @@ export const scimServiceFactory = ({
return group;
});
await $syncNewMembersRoles(group, members);
return updatedGroup;
};

@ -95,7 +95,10 @@ export const secretApprovalPolicyServiceFactory = ({
}
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
if (!env) throw new NotFoundError({ message: "Environment not found" });
if (!env)
throw new NotFoundError({
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
});
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
const doc = await secretApprovalPolicyDAL.create(
@ -178,7 +181,11 @@ export const secretApprovalPolicyServiceFactory = ({
.filter(Boolean) as string[];
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
if (!secretApprovalPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
if (!secretApprovalPolicy) {
throw new NotFoundError({
message: `Secret approval policy with ID '${secretPolicyId}' not found`
});
}
const { permission } = await permissionService.getProjectPermission(
actor,
@ -271,7 +278,8 @@ export const secretApprovalPolicyServiceFactory = ({
actorOrgId
}: TDeleteSapDTO) => {
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
if (!sapPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
if (!sapPolicy)
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@ -320,7 +328,11 @@ export const secretApprovalPolicyServiceFactory = ({
const getSecretApprovalPolicy = async (projectId: string, environment: string, path: string) => {
const secretPath = removeTrailingSlash(path);
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
if (!env) throw new NotFoundError({ message: "Environment not found" });
if (!env) {
throw new NotFoundError({
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
});
}
const policies = await secretApprovalPolicyDAL.find({ envId: env.id });
if (!policies.length) return;
@ -369,7 +381,7 @@ export const secretApprovalPolicyServiceFactory = ({
if (!sapPolicy) {
throw new NotFoundError({
message: "Cannot find secret approval policy"
message: `Secret approval policy with ID '${sapId}' not found`
});
}

@ -204,7 +204,8 @@ export const secretApprovalRequestServiceFactory = ({
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
if (!secretApprovalRequest)
throw new NotFoundError({ message: `Secret approval request with ID '${id}' not found` });
const { projectId } = secretApprovalRequest;
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
@ -271,7 +272,7 @@ export const secretApprovalRequestServiceFactory = ({
: undefined
}));
} else {
if (!botKey) throw new NotFoundError({ message: "Project bot key not found" });
if (!botKey) throw new NotFoundError({ message: `Project bot key not found`, name: "BotKeyNotFound" }); // CLI depends on this error message. TODO(daniel): Make API check for name BotKeyNotFound instead of message
const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
secrets = encrypedSecrets.map((el) => ({
...el,
@ -307,7 +308,9 @@ export const secretApprovalRequestServiceFactory = ({
actorOrgId
}: TReviewRequestDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
if (!secretApprovalRequest) {
throw new NotFoundError({ message: `Secret approval request with ID '${approvalId}' not found` });
}
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const plan = await licenseService.getPlan(actorOrgId);
@ -365,7 +368,9 @@ export const secretApprovalRequestServiceFactory = ({
actorAuthMethod
}: TStatusChangeDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
if (!secretApprovalRequest) {
throw new NotFoundError({ message: `Secret approval request with ID '${approvalId}' not found` });
}
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const plan = await licenseService.getPlan(actorOrgId);
@ -414,7 +419,8 @@ export const secretApprovalRequestServiceFactory = ({
bypassReason
}: TMergeSecretApprovalRequestDTO) => {
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
if (!secretApprovalRequest)
throw new NotFoundError({ message: `Secret approval request with ID '${approvalId}' not found` });
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const plan = await licenseService.getPlan(actorOrgId);
@ -462,7 +468,9 @@ export const secretApprovalRequestServiceFactory = ({
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
secretApprovalRequest.id
);
if (!secretApprovalSecrets) throw new NotFoundError({ message: "No secrets found" });
if (!secretApprovalSecrets) {
throw new NotFoundError({ message: `No secrets found in secret change request with ID '${approvalId}'` });
}
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
type: KmsDataKey.SecretManager,
@ -602,7 +610,9 @@ export const secretApprovalRequestServiceFactory = ({
});
} else {
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
if (!secretApprovalSecrets) throw new NotFoundError({ message: "No secrets found" });
if (!secretApprovalSecrets) {
throw new NotFoundError({ message: `No secrets found in secret change request with ID '${approvalId}'` });
}
const conflicts: Array<{ secretId: string; op: SecretOperations }> = [];
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create);
@ -610,10 +620,10 @@ export const secretApprovalRequestServiceFactory = ({
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
folderId,
secretDAL,
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
inputSecrets: secretCreationCommits.map(({ secretBlindIndex, secret }) => {
if (!secretBlindIndex) {
throw new NotFoundError({
message: "Secret blind index not found"
message: `Secret blind index not found on secret with ID '${secret.id}`
});
}
return { secretBlindIndex };
@ -637,10 +647,10 @@ export const secretApprovalRequestServiceFactory = ({
userId: "",
inputSecrets: secretUpdationCommits
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
.map(({ secretBlindIndex }) => {
.map(({ secretBlindIndex, secret }) => {
if (!secretBlindIndex) {
throw new NotFoundError({
message: "Secret blind index not found"
message: `Secret blind index not found on secret with ID '${secret.id}`
});
}
return { secretBlindIndex };
@ -760,10 +770,10 @@ export const secretApprovalRequestServiceFactory = ({
actorId: "",
secretDAL,
secretQueueService,
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex, secret }) => {
if (!secretBlindIndex) {
throw new NotFoundError({
message: "Secret blind index not found"
message: `Secret blind index not found on secret with ID '${secret.id}`
});
}
return { secretBlindIndex, type: SecretType.Shared };
@ -789,7 +799,9 @@ export const secretApprovalRequestServiceFactory = ({
await snapshotService.performSnapshot(folderId);
const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [folderId]);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder) {
throw new NotFoundError({ message: `Folder with ID '${folderId}' not found in project with ID '${projectId}'` });
}
await secretQueueService.syncSecrets({
projectId,
secretPath: folder.path,
@ -861,14 +873,18 @@ export const secretApprovalRequestServiceFactory = ({
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
throw new NotFoundError({
message: "Folder not found for the given environment slug & secret path",
message: `Folder not found for environment with slug '${environment}' & secret path '${secretPath}'`,
name: "GenSecretApproval"
});
const folderId = folder.id;
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
if (!blindIndexCfg) throw new NotFoundError({ message: "Blind index not found", name: "Update secret" });
if (!blindIndexCfg) {
throw new NotFoundError({
message: `Blind index not found for project with ID '${projectId}'`,
name: "Update secret"
});
}
const commits: Omit<TSecretApprovalRequestsSecretsInsert, "requestId">[] = [];
const commitTagIds: Record<string, string[]> = {};
// for created secret approval change
@ -961,7 +977,9 @@ export const secretApprovalRequestServiceFactory = ({
secretDAL
});
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
if (!i.secretBlindIndex) throw new NotFoundError({ message: "Secret blind index not found" });
if (!i.secretBlindIndex) {
throw new NotFoundError({ message: `Secret blind index not found for secret with ID '${i.id}'` });
}
return i.secretBlindIndex;
});
const deletedSecretIds = deletedSecrets.map(
@ -972,7 +990,7 @@ export const secretApprovalRequestServiceFactory = ({
...deletedSecrets.map((el) => {
const secretId = secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id;
if (!latestSecretVersions[secretId].secretBlindIndex)
throw new NotFoundError({ message: "Secret blind index not found" });
throw new NotFoundError({ message: `Secret blind index not found for secret with ID '${secretId}'` });
return {
op: SecretOperations.Delete as const,
...latestSecretVersions[secretId],
@ -988,7 +1006,7 @@ export const secretApprovalRequestServiceFactory = ({
const tagIds = unique(Object.values(commitTagIds).flat());
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "One or more tags not found" });
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
const doc = await secretApprovalRequestDAL.create(
@ -1054,7 +1072,7 @@ export const secretApprovalRequestServiceFactory = ({
const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => {
if (!i.secretBlindIndex) {
throw new NotFoundError({ message: "Secret blind index not found" });
throw new NotFoundError({ message: `Secret blind index not found for secret with ID '${i.id}'` });
}
return i.secretBlindIndex;
});
@ -1133,7 +1151,7 @@ export const secretApprovalRequestServiceFactory = ({
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder)
throw new NotFoundError({
message: "Folder not found for the given environment slug & secret path",
message: `Folder not found for the environment slug '${environment}' & secret path '${secretPath}'`,
name: "GenSecretApproval"
});
const folderId = folder.id;
@ -1291,7 +1309,7 @@ export const secretApprovalRequestServiceFactory = ({
const tagIds = unique(Object.values(commitTagIds).flat());
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "One or more tags not found" });
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
const doc = await secretApprovalRequestDAL.create(

@ -295,7 +295,10 @@ export const secretReplicationServiceFactory = ({
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
destinationSecretImport.folderId
]);
if (!destinationFolder) throw new NotFoundError({ message: "Imported folder not found" });
if (!destinationFolder)
throw new NotFoundError({
message: `Imported folder with ID '${destinationSecretImport.folderId}' not found in project with ID ${projectId}`
});
let destinationReplicationFolder = await folderDAL.findOne({
parentId: destinationFolder.id,
@ -506,7 +509,7 @@ export const secretReplicationServiceFactory = ({
return;
}
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
if (!botKey) throw new NotFoundError({ message: `Bot key not found for project with ID ${projectId}` });
// these are the secrets to be added in replicated folders
const sourceLocalSecrets = await secretDAL.find({ folderId: folder.id, type: SecretType.Shared });
const sourceSecretImports = await secretImportDAL.find({ folderId: folder.id });
@ -545,7 +548,11 @@ export const secretReplicationServiceFactory = ({
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
destinationSecretImport.folderId
]);
if (!destinationFolder) throw new NotFoundError({ message: "Imported folder not found" });
if (!destinationFolder) {
throw new NotFoundError({
message: `Imported folder with ID '${destinationSecretImport.folderId}' not found in project with ID ${projectId}`
});
}
let destinationReplicationFolder = await folderDAL.findOne({
parentId: destinationFolder.id,

@ -332,7 +332,10 @@ export const secretRotationQueueFactory = ({
);
});
} else {
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
if (!botKey)
throw new NotFoundError({
message: `Project bot not found for project with ID '${secretRotation.projectId}'`
});
const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({
secretId,
value: encryptSymmetric128BitHexKeyUTF8(
@ -372,7 +375,9 @@ export const secretRotationQueueFactory = ({
);
await secretVersionDAL.insertMany(
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => {
if (!el.secretBlindIndex) throw new NotFoundError({ message: "Secret blind index not found" });
if (!el.secretBlindIndex) {
throw new NotFoundError({ message: `Secret blind index not found on secret with ID '${id}` });
}
return {
...el,
secretId: id,

@ -94,7 +94,11 @@ export const secretRotationServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
if (!folder) throw new NotFoundError({ message: "Secret path not found" });
if (!folder) {
throw new NotFoundError({
message: `Secret path with path '${secretPath}' not found in environment with slug '${environment}'`
});
}
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Edit,
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
@ -108,14 +112,14 @@ export const secretRotationServiceFactory = ({
$in: { id: Object.values(outputs) }
});
if (selectedSecrets.length !== Object.values(outputs).length)
throw new NotFoundError({ message: "Secrets not found" });
throw new NotFoundError({ message: `Secrets not found in folder with ID '${folder.id}'` });
} else {
const selectedSecrets = await secretDAL.find({
folderId: folder.id,
$in: { id: Object.values(outputs) }
});
if (selectedSecrets.length !== Object.values(outputs).length)
throw new NotFoundError({ message: "Secrets not found" });
throw new NotFoundError({ message: `Secrets not found in folder with ID '${folder.id}'` });
}
const plan = await licenseService.getPlan(project.orgId);
@ -125,7 +129,7 @@ export const secretRotationServiceFactory = ({
});
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
if (!selectedTemplate) throw new NotFoundError({ message: "Provider not found" });
if (!selectedTemplate) throw new NotFoundError({ message: `Provider with name '${provider}' not found` });
const formattedInputs: Record<string, unknown> = {};
Object.entries(inputs).forEach(([key, value]) => {
const { type } = selectedTemplate.template.inputs.properties[key];
@ -198,7 +202,7 @@ export const secretRotationServiceFactory = ({
return docs;
}
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
if (!botKey) throw new NotFoundError({ message: `Project bot not found for project with ID '${projectId}'` });
const docs = await secretRotationDAL.find({ projectId });
return docs.map((el) => ({
...el,
@ -220,7 +224,7 @@ export const secretRotationServiceFactory = ({
const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => {
const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new NotFoundError({ message: "Rotation not found" });
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
const project = await projectDAL.findById(doc.projectId);
const plan = await licenseService.getPlan(project.orgId);
@ -244,7 +248,7 @@ export const secretRotationServiceFactory = ({
const deleteById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TDeleteDTO) => {
const doc = await secretRotationDAL.findById(rotationId);
if (!doc) throw new NotFoundError({ message: "Rotation not found" });
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,

@ -1,6 +1,6 @@
import { ProbotOctokit } from "probot";
import { OrgMembershipRole } from "@app/db/schemas";
import { OrgMembershipRole, TableName } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
@ -61,7 +61,7 @@ export const secretScanningQueueFactory = ({
const getOrgAdminEmails = async (organizationId: string) => {
// get emails of admins
const adminsOfWork = await orgMemberDAL.findMembership({
orgId: organizationId,
[`${TableName.Organization}.id` as string]: organizationId,
role: OrgMembershipRole.Admin
});
return adminsOfWork.filter((userObject) => userObject.email).map((userObject) => userObject.email as string);

@ -90,7 +90,7 @@ export const secretScanningServiceFactory = ({
const {
data: { repositories }
} = await octokit.apps.listReposAccessibleToInstallation();
if (!appCfg.DISABLE_SECRET_SCANNING) {
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(actorOrgId)) {
await Promise.all(
repositories.map(({ id, full_name }) =>
secretScanningQueue.startFullRepoScan({
@ -164,7 +164,7 @@ export const secretScanningServiceFactory = ({
});
if (!installationLink) return;
if (!appCfg.DISABLE_SECRET_SCANNING) {
if (appCfg.SECRET_SCANNING_ORG_WHITELIST?.includes(installationLink.orgId)) {
await secretScanningQueue.startPushEventScan({
commits,
pusher: { name: pusher.name, email: pusher.email },

@ -99,7 +99,11 @@ export const secretSnapshotServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder) {
throw new NotFoundError({
message: `Folder with path '${path}' not found in environment with slug '${environment}'`
});
}
return snapshotDAL.countOfSnapshotsByFolderId(folder.id);
};
@ -131,7 +135,10 @@ export const secretSnapshotServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder)
throw new NotFoundError({
message: `Folder with path '${path}' not found in environment with slug '${environment}'`
});
const snapshots = await snapshotDAL.find({ folderId: folder.id }, { limit, offset, sort: [["createdAt", "desc"]] });
return snapshots;
@ -139,7 +146,7 @@ export const secretSnapshotServiceFactory = ({
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
const snapshot = await snapshotDAL.findById(id);
if (!snapshot) throw new NotFoundError({ message: "Snapshot not found" });
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
@ -173,7 +180,8 @@ export const secretSnapshotServiceFactory = ({
} else {
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotDataById(id);
const { botKey } = await projectBotService.getBotKey(snapshot.projectId);
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
if (!botKey)
throw new NotFoundError({ message: `Project bot key not found for project with ID '${snapshot.projectId}'` });
snapshotDetails = {
...encryptedSnapshotDetails,
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
@ -225,7 +233,7 @@ export const secretSnapshotServiceFactory = ({
try {
if (!licenseService.isValidLicense) throw new InternalServerError({ message: "Invalid license" });
const folder = await folderDAL.findById(folderId);
if (!folder) throw new NotFoundError({ message: "Folder not found" });
if (!folder) throw new NotFoundError({ message: `Folder with ID '${folderId}' not found` });
const shouldUseSecretV2Bridge = folder.projectVersion === 3;
if (shouldUseSecretV2Bridge) {
@ -240,7 +248,8 @@ export const secretSnapshotServiceFactory = ({
},
tx
);
const snapshotSecrets = await snapshotSecretV2BridgeDAL.insertMany(
const snapshotSecrets = await snapshotSecretV2BridgeDAL.batchInsert(
secretVersions.map(({ id }) => ({
secretVersionId: id,
envId: folder.environment.envId,
@ -248,7 +257,8 @@ export const secretSnapshotServiceFactory = ({
})),
tx
);
const snapshotFolders = await snapshotFolderDAL.insertMany(
const snapshotFolders = await snapshotFolderDAL.batchInsert(
folderVersions.map(({ id }) => ({
folderVersionId: id,
envId: folder.environment.envId,
@ -309,7 +319,7 @@ export const secretSnapshotServiceFactory = ({
actorOrgId
}: TRollbackSnapshotDTO) => {
const snapshot = await snapshotDAL.findById(snapshotId);
if (!snapshot) throw new NotFoundError({ message: "Snapshot not found" });
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
const shouldUseBridge = snapshot.projectVersion === 3;
const { permission } = await permissionService.getProjectPermission(

@ -16,6 +16,9 @@ export const KeyStorePrefixes = {
WaitUntilReadyKmsOrgKeyCreation: "wait-until-ready-kms-org-key-creation-",
WaitUntilReadyKmsOrgDataKeyCreation: "wait-until-ready-kms-org-data-key-creation-",
WaitUntilReadyProjectEnvironmentOperation: (projectId: string) =>
`wait-until-ready-project-environments-operation-${projectId}`,
ProjectEnvironmentLock: (projectId: string) => `project-environment-lock-${projectId}` as const,
SyncSecretIntegrationLock: (projectId: string, environmentSlug: string, secretPath: string) =>
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>

@ -18,7 +18,8 @@ export const GROUPS = {
id: "The id of the group to list users for",
offset: "The offset to start from. If you enter 10, it will start from the 10th user.",
limit: "The number of users to return.",
username: "The username to search for."
username: "The username to search for.",
search: "The text string that user email or name will be filtered by."
},
ADD_USER: {
id: "The id of the group to add the user to.",
@ -533,7 +534,8 @@ export const ENVIRONMENTS = {
CREATE: {
workspaceId: "The ID of the project to create the environment in.",
name: "The name of the environment to create.",
slug: "The slug of the environment to create."
slug: "The slug of the environment to create.",
position: "The position of the environment. The lowest number will be displayed as the first environment."
},
UPDATE: {
workspaceId: "The ID of the project to update the environment in.",
@ -675,6 +677,9 @@ export const SECRET_IMPORTS = {
environment: "The slug of the environment to list secret imports from.",
path: "The path to list secret imports from."
},
GET: {
secretImportId: "The ID of the secret import to fetch."
},
CREATE: {
environment: "The slug of the environment to import into.",
path: "The path to import into.",
@ -1347,3 +1352,37 @@ export const PROJECT_ROLE = {
projectSlug: "The slug of the project to list the roles of."
}
};
export const KMS = {
CREATE_KEY: {
projectId: "The ID of the project to create the key in.",
name: "The name of the key to be created. Must be slug-friendly.",
description: "An optional description of the key.",
encryptionAlgorithm: "The algorithm to use when performing cryptographic operations with the key."
},
UPDATE_KEY: {
keyId: "The ID of the key to be updated.",
name: "The updated name of this key. Must be slug-friendly.",
description: "The updated description of this key.",
isDisabled: "The flag to enable or disable this key."
},
DELETE_KEY: {
keyId: "The ID of the key to be deleted."
},
LIST_KEYS: {
projectId: "The ID of the project to list keys from.",
offset: "The offset to start from. If you enter 10, it will start from the 10th key.",
limit: "The number of keys to return.",
orderBy: "The column to order keys by.",
orderDirection: "The direction to order keys in.",
search: "The text string to filter key names by."
},
ENCRYPT: {
keyId: "The ID of the key to encrypt the data with.",
plaintext: "The plaintext to be encrypted (base64 encoded)."
},
DECRYPT: {
keyId: "The ID of the key to decrypt the data with.",
ciphertext: "The ciphertext to be decrypted (base64 encoded)."
}
};

@ -0,0 +1,28 @@
// Credit: https://github.com/miguelmota/is-base64
export const isBase64 = (
v: string,
opts = { allowEmpty: false, mimeRequired: false, allowMime: true, paddingRequired: false }
) => {
if (opts.allowEmpty === false && v === "") {
return false;
}
let regex = "(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+/]{3}=)?";
const mimeRegex = "(data:\\w+\\/[a-zA-Z\\+\\-\\.]+;base64,)";
if (opts.mimeRequired === true) {
regex = mimeRegex + regex;
} else if (opts.allowMime === true) {
regex = `${mimeRegex}?${regex}`;
}
if (opts.paddingRequired === false) {
regex = "(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}(==)?|[A-Za-z0-9+\\/]{3}=?)?";
}
return new RegExp(`^${regex}$`, "gi").test(v);
};
export const getBase64SizeInBytes = (base64String: string) => {
return Buffer.from(base64String, "base64").length;
};

@ -34,6 +34,12 @@ const envSchema = z
DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database connection string")).default(
`postgresql://${process.env.DB_USER}:${process.env.DB_PASSWORD}@${process.env.DB_HOST}:${process.env.DB_PORT}/${process.env.DB_NAME}`
),
AUDIT_LOGS_DB_CONNECTION_URI: zpStr(
z.string().describe("Postgres database connection string for Audit logs").optional()
),
AUDIT_LOGS_DB_ROOT_CERT: zpStr(
z.string().describe("Postgres database base64-encoded CA cert for Audit logs").optional()
),
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
@ -111,9 +117,16 @@ const envSchema = z
// gcp secret manager
CLIENT_ID_GCP_SECRET_MANAGER: zpStr(z.string().optional()),
CLIENT_SECRET_GCP_SECRET_MANAGER: zpStr(z.string().optional()),
// github
// github oauth
CLIENT_ID_GITHUB: zpStr(z.string().optional()),
CLIENT_SECRET_GITHUB: zpStr(z.string().optional()),
// github app
CLIENT_ID_GITHUB_APP: zpStr(z.string().optional()),
CLIENT_SECRET_GITHUB_APP: zpStr(z.string().optional()),
CLIENT_PRIVATE_KEY_GITHUB_APP: zpStr(z.string().optional()),
CLIENT_APP_ID_GITHUB_APP: z.coerce.number().optional(),
CLIENT_SLUG_GITHUB_APP: zpStr(z.string().optional()),
// azure
CLIENT_ID_AZURE: zpStr(z.string().optional()),
CLIENT_SECRET_AZURE: zpStr(z.string().optional()),
@ -129,6 +142,7 @@ const envSchema = z
SECRET_SCANNING_WEBHOOK_SECRET: zpStr(z.string().optional()),
SECRET_SCANNING_GIT_APP_ID: zpStr(z.string().optional()),
SECRET_SCANNING_PRIVATE_KEY: zpStr(z.string().optional()),
SECRET_SCANNING_ORG_WHITELIST: zpStr(z.string().optional()),
// LICENSE
LICENSE_SERVER_URL: zpStr(z.string().optional().default("https://portal.infisical.com")),
LICENSE_SERVER_KEY: zpStr(z.string().optional()),
@ -164,7 +178,8 @@ const envSchema = z
Boolean(data.SECRET_SCANNING_GIT_APP_ID) &&
Boolean(data.SECRET_SCANNING_PRIVATE_KEY) &&
Boolean(data.SECRET_SCANNING_WEBHOOK_SECRET),
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG
samlDefaultOrgSlug: data.DEFAULT_SAML_ORG_SLUG,
SECRET_SCANNING_ORG_WHITELIST: data.SECRET_SCANNING_ORG_WHITELIST?.split(",")
}));
let envCfg: Readonly<z.infer<typeof envSchema>>;

@ -23,6 +23,18 @@ export class InternalServerError extends Error {
}
}
export class GatewayTimeoutError extends Error {
name: string;
error: unknown;
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
super(message || "Timeout error");
this.name = name || "GatewayTimeoutError";
this.error = error;
}
}
export class UnauthorizedError extends Error {
name: string;
@ -59,6 +71,13 @@ export class BadRequestError extends Error {
}
}
export class RateLimitError extends Error {
constructor({ message }: { message?: string }) {
super(message || "Rate limit exceeded");
this.name = "RateLimitExceeded";
}
}
export class NotFoundError extends Error {
name: string;

@ -70,3 +70,14 @@ export const objectify = <T, Key extends string | number | symbol, Value = T>(
{} as Record<Key, Value>
);
};
/**
* Chunks an array into smaller arrays of the given size.
*/
export const chunkArray = <T>(array: T[], chunkSize: number): T[][] => {
const chunks: T[][] = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
};

@ -8,12 +8,14 @@ const appendParentToGroupingOperator = (parentPath: string, filter: Filter) => {
return filter;
};
export const generateKnexQueryFromScim = (
const processDynamicQuery = (
rootQuery: Knex.QueryBuilder,
rootScimFilter: string,
getAttributeField: (attr: string) => string | null
scimRootFilterAst: Filter,
getAttributeField: (attr: string) => string | null,
depth = 0
) => {
const scimRootFilterAst = parse(rootScimFilter);
if (depth > 20) return;
const stack = [
{
scimFilterAst: scimRootFilterAst,
@ -75,42 +77,35 @@ export const generateKnexQueryFromScim = (
break;
}
case "and": {
void query.andWhere((subQueryBuilder) => {
scimFilterAst.filters.forEach((el) => {
stack.push({
query: subQueryBuilder,
scimFilterAst: el
});
scimFilterAst.filters.forEach((el) => {
void query.andWhere((subQueryBuilder) => {
processDynamicQuery(subQueryBuilder, el, getAttributeField, depth + 1);
});
});
break;
}
case "or": {
void query.orWhere((subQueryBuilder) => {
scimFilterAst.filters.forEach((el) => {
stack.push({
query: subQueryBuilder,
scimFilterAst: el
});
scimFilterAst.filters.forEach((el) => {
void query.orWhere((subQueryBuilder) => {
processDynamicQuery(subQueryBuilder, el, getAttributeField, depth + 1);
});
});
break;
}
case "not": {
void query.whereNot((subQueryBuilder) => {
stack.push({
query: subQueryBuilder,
scimFilterAst: scimFilterAst.filter
});
processDynamicQuery(subQueryBuilder, scimFilterAst.filter, getAttributeField, depth + 1);
});
break;
}
case "[]": {
void query.whereNot((subQueryBuilder) => {
stack.push({
query: subQueryBuilder,
scimFilterAst: appendParentToGroupingOperator(scimFilterAst.attrPath, scimFilterAst.valFilter)
});
void query.where((subQueryBuilder) => {
processDynamicQuery(
subQueryBuilder,
appendParentToGroupingOperator(scimFilterAst.attrPath, scimFilterAst.valFilter),
getAttributeField,
depth + 1
);
});
break;
}
@ -119,3 +114,12 @@ export const generateKnexQueryFromScim = (
}
}
};
export const generateKnexQueryFromScim = (
rootQuery: Knex.QueryBuilder,
rootScimFilter: string,
getAttributeField: (attr: string) => string | null
) => {
const scimRootFilterAst = parse(rootScimFilter);
return processDynamicQuery(rootQuery, scimRootFilterAst, getAttributeField);
};

@ -1,7 +1,7 @@
import dotenv from "dotenv";
import path from "path";
import { initDbConnection } from "./db";
import { initAuditLogDbConnection, initDbConnection } from "./db";
import { keyStoreFactory } from "./keystore/keystore";
import { formatSmtpConfig, initEnvConfig, IS_PACKAGED } from "./lib/config/env";
import { isMigrationMode } from "./lib/fn";
@ -25,6 +25,13 @@ const run = async () => {
}))
});
const auditLogDb = appCfg.AUDIT_LOGS_DB_CONNECTION_URI
? initAuditLogDbConnection({
dbConnectionUri: appCfg.AUDIT_LOGS_DB_CONNECTION_URI,
dbRootCert: appCfg.AUDIT_LOGS_DB_ROOT_CERT
})
: undefined;
// Case: App is running in packaged mode (binary), and migration mode is enabled.
// Run the migrations and exit the process after completion.
if (IS_PACKAGED && isMigrationMode()) {
@ -46,7 +53,7 @@ const run = async () => {
const queue = queueServiceFactory(appCfg.REDIS_URL);
const keyStore = keyStoreFactory(appCfg.REDIS_URL);
const server = await main({ db, smtp, logger, queue, keyStore });
const server = await main({ db, auditLogDb, smtp, logger, queue, keyStore });
const bootstrap = await bootstrapCheck({ db });
// eslint-disable-next-line

@ -1,7 +1,7 @@
import { Job, JobsOptions, Queue, QueueOptions, RepeatOptions, Worker, WorkerListener } from "bullmq";
import Redis from "ioredis";
import { SecretKeyEncoding } from "@app/db/schemas";
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
import {
TScanFullRepoEventPayload,
@ -32,7 +32,8 @@ export enum QueueName {
SecretReplication = "secret-replication",
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
ProjectV3Migration = "project-v3-migration",
AccessTokenStatusUpdate = "access-token-status-update"
AccessTokenStatusUpdate = "access-token-status-update",
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
}
export enum QueueJobs {
@ -56,7 +57,8 @@ export enum QueueJobs {
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
ProjectV3Migration = "project-v3-migration",
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
ServiceTokenStatusUpdate = "service-token-status-update"
ServiceTokenStatusUpdate = "service-token-status-update",
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
}
export type TQueueJobTypes = {
@ -166,6 +168,19 @@ export type TQueueJobTypes = {
name: QueueJobs.ProjectV3Migration;
payload: { projectId: string };
};
[QueueName.ImportSecretsFromExternalSource]: {
name: QueueJobs.ImportSecretsFromExternalSource;
payload: {
actorEmail: string;
data: {
iv: string;
tag: string;
ciphertext: string;
algorithm: SecretEncryptionAlgo;
encoding: SecretKeyEncoding;
};
};
};
};
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;

@ -30,6 +30,7 @@ import { fastifySwagger } from "./plugins/swagger";
import { registerRoutes } from "./routes";
type TMain = {
auditLogDb?: Knex;
db: Knex;
smtp: TSmtpService;
logger?: Logger;
@ -38,7 +39,7 @@ type TMain = {
};
// Run the server!
export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
export const main = async ({ db, auditLogDb, smtp, logger, queue, keyStore }: TMain) => {
const appCfg = getConfig();
const server = fastify({
logger: appCfg.NODE_ENV === "test" ? false : logger,
@ -94,7 +95,7 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
await server.register(maintenanceMode);
await server.register(registerRoutes, { smtp, queue, db, keyStore });
await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore });
if (appCfg.isProductionMode) {
await server.register(registerExternalNextjs, {

@ -2,6 +2,7 @@ import type { RateLimitOptions, RateLimitPluginOptions } from "@fastify/rate-lim
import { Redis } from "ioredis";
import { getConfig } from "@app/lib/config/env";
import { RateLimitError } from "@app/lib/errors";
export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
const appCfg = getConfig();
@ -10,6 +11,11 @@ export const globalRateLimiterCfg = (): RateLimitPluginOptions => {
: null;
return {
errorResponseBuilder: (_, context) => {
throw new RateLimitError({
message: `Rate limit exceeded. Please try again in ${context.after}`
});
},
timeWindow: 60 * 1000,
max: 600,
redis,

@ -3,9 +3,12 @@ import fp from "fastify-plugin";
import { DefaultResponseErrorsSchema } from "../routes/sanitizedSchemas";
const isScimRoutes = (pathname: string) =>
pathname.startsWith("/api/v1/scim/Users") || pathname.startsWith("/api/v1/scim/Groups");
export const addErrorsToResponseSchemas = fp(async (server) => {
server.addHook("onRoute", (routeOptions) => {
if (routeOptions.schema && routeOptions.schema.response) {
if (routeOptions.schema && routeOptions.schema.response && !isScimRoutes(routeOptions.path)) {
routeOptions.schema.response = {
...DefaultResponseErrorsSchema,
...routeOptions.schema.response

@ -18,6 +18,7 @@ export type TAuthMode =
user: TUsers;
orgId: string;
authMethod: AuthMethod;
isMfaVerified?: boolean;
}
| {
authMode: AuthMode.API_KEY;
@ -121,7 +122,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
tokenVersionId,
actor,
orgId: orgId as string,
authMethod: token.authMethod
authMethod: token.authMethod,
isMfaVerified: token.isMfaVerified
};
break;
}

@ -7,8 +7,10 @@ import {
BadRequestError,
DatabaseError,
ForbiddenRequestError,
GatewayTimeoutError,
InternalServerError,
NotFoundError,
RateLimitError,
ScimRequestError,
UnauthorizedError
} from "@app/lib/errors";
@ -25,7 +27,9 @@ enum HttpStatusCodes {
Unauthorized = 401,
Forbidden = 403,
// eslint-disable-next-line @typescript-eslint/no-shadow
InternalServerError = 500
InternalServerError = 500,
GatewayTimeout = 504,
TooManyRequests = 429
}
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
@ -47,6 +51,10 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
void res
.status(HttpStatusCodes.InternalServerError)
.send({ statusCode: HttpStatusCodes.InternalServerError, message: "Something went wrong", error: error.name });
} else if (error instanceof GatewayTimeoutError) {
void res
.status(HttpStatusCodes.GatewayTimeout)
.send({ statusCode: HttpStatusCodes.GatewayTimeout, message: error.message, error: error.name });
} else if (error instanceof ZodError) {
void res
.status(HttpStatusCodes.Unauthorized)
@ -63,6 +71,12 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
message: error.message,
error: error.name
});
} else if (error instanceof RateLimitError) {
void res.status(HttpStatusCodes.TooManyRequests).send({
statusCode: HttpStatusCodes.TooManyRequests,
message: error.message,
error: error.name
});
} else if (error instanceof ScimRequestError) {
void res.status(error.status).send({
schemas: error.schemas,
@ -91,7 +105,11 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
message
});
} else {
void res.send(error);
void res.status(HttpStatusCodes.InternalServerError).send({
statusCode: HttpStatusCodes.InternalServerError,
error: "InternalServerError",
message: "Something went wrong"
});
}
});
});

@ -96,6 +96,10 @@ import { certificateAuthorityServiceFactory } from "@app/services/certificate-au
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
@ -213,16 +217,15 @@ import { registerV3Routes } from "./v3";
export const registerRoutes = async (
server: FastifyZodProvider,
{
auditLogDb,
db,
smtp: smtpService,
queue: queueService,
keyStore
}: { db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory }
}: { auditLogDb?: Knex; db: Knex; smtp: TSmtpService; queue: TQueueServiceFactory; keyStore: TKeyStoreFactory }
) => {
const appCfg = getConfig();
if (!appCfg.DISABLE_SECRET_SCANNING) {
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
}
await server.register(registerSecretScannerGhApp, { prefix: "/ss-webhook" });
// db layers
const userDAL = userDALFactory(db);
@ -282,7 +285,7 @@ export const registerRoutes = async (
const identityOidcAuthDAL = identityOidcAuthDALFactory(db);
const identityAzureAuthDAL = identityAzureAuthDALFactory(db);
const auditLogDAL = auditLogDALFactory(db);
const auditLogDAL = auditLogDALFactory(auditLogDb ?? db);
const auditLogStreamDAL = auditLogStreamDALFactory(db);
const trustedIpDAL = trustedIpDALFactory(db);
const telemetryDAL = telemetryDALFactory(db);
@ -333,6 +336,8 @@ export const registerRoutes = async (
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
const workflowIntegrationDAL = workflowIntegrationDALFactory(db);
const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
orgRoleDAL,
@ -439,7 +444,8 @@ export const registerRoutes = async (
projectKeyDAL,
projectBotDAL,
permissionService,
smtpService
smtpService,
externalGroupOrgRoleMappingDAL
});
const ldapService = ldapConfigServiceFactory({
@ -490,6 +496,9 @@ export const registerRoutes = async (
authDAL,
userDAL
});
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const orgService = orgServiceFactory({
userAliasDAL,
identityMetadataDAL,
@ -512,7 +521,8 @@ export const registerRoutes = async (
userDAL,
groupDAL,
orgBotDAL,
oidcConfigDAL
oidcConfigDAL,
projectBotService
});
const signupService = authSignupServiceFactory({
tokenService,
@ -530,7 +540,12 @@ export const registerRoutes = async (
orgService,
licenseService
});
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDAL });
const orgRoleService = orgRoleServiceFactory({
permissionService,
orgRoleDAL,
orgDAL,
externalGroupOrgRoleMappingDAL
});
const superAdminService = superAdminServiceFactory({
userDAL,
authService: loginService,
@ -571,7 +586,6 @@ export const registerRoutes = async (
secretScanningDAL,
secretScanningQueue
});
const projectBotService = projectBotServiceFactory({ permissionService, projectBotDAL, projectDAL });
const projectMembershipService = projectMembershipServiceFactory({
projectMembershipDAL,
@ -748,6 +762,7 @@ export const registerRoutes = async (
const projectEnvService = projectEnvServiceFactory({
permissionService,
projectEnvDAL,
keyStore,
licenseService,
projectDAL,
folderDAL
@ -834,7 +849,10 @@ export const registerRoutes = async (
integrationAuthDAL,
snapshotDAL,
snapshotSecretV2BridgeDAL,
secretApprovalRequestDAL
secretApprovalRequestDAL,
projectKeyDAL,
projectUserMembershipRoleDAL,
orgService
});
const secretImportService = secretImportServiceFactory({
licenseService,
@ -1193,12 +1211,39 @@ export const registerRoutes = async (
workflowIntegrationDAL
});
const migrationService = externalMigrationServiceFactory({
projectService,
orgService,
const cmekService = cmekServiceFactory({
kmsDAL,
kmsService,
permissionService
});
const externalMigrationQueue = externalMigrationQueueFactory({
projectEnvService,
projectDAL,
projectService,
smtpService,
kmsService,
projectEnvDAL,
secretVersionDAL: secretVersionV2BridgeDAL,
secretTagDAL,
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
folderDAL,
secretDAL: secretV2BridgeDAL,
queueService,
secretV2BridgeService
});
const migrationService = externalMigrationServiceFactory({
externalMigrationQueue,
userDAL,
permissionService
});
const externalGroupOrgRoleMappingService = externalGroupOrgRoleMappingServiceFactory({
permissionService,
secretService
licenseService,
orgRoleDAL,
externalGroupOrgRoleMappingDAL
});
await superAdminService.initServerCfg();
@ -1282,10 +1327,12 @@ export const registerRoutes = async (
secretSharing: secretSharingService,
userEngagement: userEngagementService,
externalKms: externalKmsService,
cmek: cmekService,
orgAdmin: orgAdminService,
slack: slackService,
workflowIntegration: workflowIntegrationService,
migration: migrationService
migration: migrationService,
externalGroupOrgRoleMapping: externalGroupOrgRoleMappingService
});
const cronJobs: CronJob[] = [];

@ -109,7 +109,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
firstName: true,
lastName: true,
email: true,
id: true
id: true,
superAdmin: true
}).array()
})
}

@ -107,7 +107,8 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
userId: decodedToken.userId,
tokenVersionId: tokenVersion.id,
accessVersion: tokenVersion.accessVersion,
organizationId: decodedToken.organizationId
organizationId: decodedToken.organizationId,
isMfaVerified: decodedToken.isMfaVerified
},
appCfg.AUTH_SECRET,
{ expiresIn: appCfg.JWT_AUTH_LIFETIME }

@ -0,0 +1,331 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { InternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { KMS } from "@app/lib/api-docs";
import { getBase64SizeInBytes, isBase64 } from "@app/lib/base64";
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
import { OrderByDirection } from "@app/lib/types";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CmekOrderBy } from "@app/services/cmek/cmek-types";
const keyNameSchema = z
.string()
.trim()
.min(1)
.max(32)
.toLowerCase()
.refine((v) => slugify(v) === v, {
message: "Name must be slug friendly"
});
const keyDescriptionSchema = z.string().trim().max(500).optional();
const base64Schema = z.string().superRefine((val, ctx) => {
if (!isBase64(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "plaintext must be base64 encoded"
});
}
if (getBase64SizeInBytes(val) > 4096) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "data cannot exceed 4096 bytes"
});
}
});
export const registerCmekRouter = async (server: FastifyZodProvider) => {
// create encryption key
server.route({
method: "POST",
url: "/keys",
config: {
rateLimit: writeLimit
},
schema: {
description: "Create KMS key",
body: z.object({
projectId: z.string().describe(KMS.CREATE_KEY.projectId),
name: keyNameSchema.describe(KMS.CREATE_KEY.name),
description: keyDescriptionSchema.describe(KMS.CREATE_KEY.description),
encryptionAlgorithm: z
.nativeEnum(SymmetricEncryption)
.optional()
.default(SymmetricEncryption.AES_GCM_256)
.describe(KMS.CREATE_KEY.encryptionAlgorithm) // eventually will support others
}),
response: {
200: z.object({
key: KmsKeysSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
body: { projectId, name, description, encryptionAlgorithm },
permission
} = req;
const cmek = await server.services.cmek.createCmek(
{ orgId: permission.orgId, projectId, name, description, encryptionAlgorithm },
permission
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.CREATE_CMEK,
metadata: {
keyId: cmek.id,
name,
description,
encryptionAlgorithm
}
}
});
return { key: cmek };
}
});
// update KMS key
server.route({
method: "PATCH",
url: "/keys/:keyId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Update KMS key",
params: z.object({
keyId: z.string().uuid().describe(KMS.UPDATE_KEY.keyId)
}),
body: z.object({
name: keyNameSchema.optional().describe(KMS.UPDATE_KEY.name),
isDisabled: z.boolean().optional().describe(KMS.UPDATE_KEY.isDisabled),
description: keyDescriptionSchema.describe(KMS.UPDATE_KEY.description)
}),
response: {
200: z.object({
key: KmsKeysSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
params: { keyId },
body,
permission
} = req;
const cmek = await server.services.cmek.updateCmekById({ keyId, ...body }, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: permission.orgId,
event: {
type: EventType.UPDATE_CMEK,
metadata: {
keyId,
...body
}
}
});
return { key: cmek };
}
});
// delete KMS key
server.route({
method: "DELETE",
url: "/keys/:keyId",
config: {
rateLimit: writeLimit
},
schema: {
description: "Delete KMS key",
params: z.object({
keyId: z.string().uuid().describe(KMS.DELETE_KEY.keyId)
}),
response: {
200: z.object({
key: KmsKeysSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
params: { keyId },
permission
} = req;
const cmek = await server.services.cmek.deleteCmekById(keyId, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: permission.orgId,
event: {
type: EventType.DELETE_CMEK,
metadata: {
keyId
}
}
});
return { key: cmek };
}
});
// list KMS keys
server.route({
method: "GET",
url: "/keys",
config: {
rateLimit: readLimit
},
schema: {
description: "List KMS keys",
querystring: z.object({
projectId: z.string().describe(KMS.LIST_KEYS.projectId),
offset: z.coerce.number().min(0).optional().default(0).describe(KMS.LIST_KEYS.offset),
limit: z.coerce.number().min(1).max(100).optional().default(100).describe(KMS.LIST_KEYS.limit),
orderBy: z.nativeEnum(CmekOrderBy).optional().default(CmekOrderBy.Name).describe(KMS.LIST_KEYS.orderBy),
orderDirection: z
.nativeEnum(OrderByDirection)
.optional()
.default(OrderByDirection.ASC)
.describe(KMS.LIST_KEYS.orderDirection),
search: z.string().trim().optional().describe(KMS.LIST_KEYS.search)
}),
response: {
200: z.object({
keys: KmsKeysSchema.merge(InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true })).array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId, ...dto },
permission
} = req;
const { cmeks, totalCount } = await server.services.cmek.listCmeksByProjectId({ projectId, ...dto }, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_CMEKS,
metadata: {
keyIds: cmeks.map((key) => key.id)
}
}
});
return { keys: cmeks, totalCount };
}
});
// encrypt data
server.route({
method: "POST",
url: "/keys/:keyId/encrypt",
config: {
rateLimit: writeLimit
},
schema: {
description: "Encrypt data with KMS key",
params: z.object({
keyId: z.string().uuid().describe(KMS.ENCRYPT.keyId)
}),
body: z.object({
plaintext: base64Schema.describe(KMS.ENCRYPT.plaintext)
}),
response: {
200: z.object({
ciphertext: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
params: { keyId },
body: { plaintext },
permission
} = req;
const ciphertext = await server.services.cmek.cmekEncrypt({ keyId, plaintext }, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: permission.orgId,
event: {
type: EventType.CMEK_ENCRYPT,
metadata: {
keyId
}
}
});
return { ciphertext };
}
});
server.route({
method: "POST",
url: "/keys/:keyId/decrypt",
config: {
rateLimit: writeLimit
},
schema: {
description: "Decrypt data with KMS key",
params: z.object({
keyId: z.string().uuid().describe(KMS.ENCRYPT.keyId)
}),
body: z.object({
ciphertext: base64Schema.describe(KMS.ENCRYPT.plaintext)
}),
response: {
200: z.object({
plaintext: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
params: { keyId },
body: { ciphertext },
permission
} = req;
const plaintext = await server.services.cmek.cmekDecrypt({ keyId, ciphertext }, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
orgId: permission.orgId,
event: {
type: EventType.CMEK_DECRYPT,
metadata: {
keyId
}
}
});
return { plaintext };
}
});
};

@ -0,0 +1,83 @@
import slugify from "@sindresorhus/slugify";
import { z } from "zod";
import { ExternalGroupOrgRoleMappingsSchema } from "@app/db/schemas/external-group-org-role-mappings";
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 registerExternalGroupOrgRoleMappingRouter = async (server: FastifyZodProvider) => {
// get mappings for current org
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: ExternalGroupOrgRoleMappingsSchema.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const mappings = server.services.externalGroupOrgRoleMapping.listExternalGroupOrgRoleMappings(req.permission);
await server.services.auditLog.createAuditLog({
orgId: req.permission.orgId,
...req.auditLogInfo,
event: {
type: EventType.GET_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS
}
});
return mappings;
}
});
// update mappings for current org
server.route({
method: "PUT", // using put since this endpoint creates, updates and deletes mappings
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
mappings: z
.object({
groupName: z.string().trim().min(1),
roleSlug: z
.string()
.min(1)
.toLowerCase()
.refine((v) => slugify(v) === v, {
message: "Role must be a valid slug"
})
})
.array()
}),
response: {
200: ExternalGroupOrgRoleMappingsSchema.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { body, permission } = req;
const mappings = server.services.externalGroupOrgRoleMapping.updateExternalGroupOrgRoleMappings(body, permission);
await server.services.auditLog.createAuditLog({
orgId: permission.orgId,
...req.auditLogInfo,
event: {
type: EventType.UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS,
metadata: body
}
});
return mappings;
}
});
};

@ -22,7 +22,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
schema: {
description: "Login with AWS Auth",
body: z.object({
identityId: z.string().describe(AWS_AUTH.LOGIN.identityId),
identityId: z.string().trim().describe(AWS_AUTH.LOGIN.identityId),
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)

@ -21,7 +21,7 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
schema: {
description: "Login with Azure Auth",
body: z.object({
identityId: z.string().describe(AZURE_AUTH.LOGIN.identityId),
identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId),
jwt: z.string()
}),
response: {

@ -19,7 +19,7 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
schema: {
description: "Login with GCP Auth",
body: z.object({
identityId: z.string().describe(GCP_AUTH.LOGIN.identityId),
identityId: z.string().trim().describe(GCP_AUTH.LOGIN.identityId).trim(),
jwt: z.string()
}),
response: {

@ -1,3 +1,4 @@
import { registerCmekRouter } from "@app/server/routes/v1/cmek-router";
import { registerDashboardRouter } from "@app/server/routes/v1/dashboard-router";
import { registerAdminRouter } from "./admin-router";
@ -6,6 +7,7 @@ import { registerProjectBotRouter } from "./bot-router";
import { registerCaRouter } from "./certificate-authority-router";
import { registerCertRouter } from "./certificate-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
import { registerIdentityAccessTokenRouter } from "./identity-access-token-router";
import { registerIdentityAwsAuthRouter } from "./identity-aws-iam-auth-router";
import { registerIdentityAzureAuthRouter } from "./identity-azure-auth-router";
@ -103,6 +105,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(registerIdentityRouter, { prefix: "/identities" });
await server.register(registerSecretSharingRouter, { prefix: "/secret-sharing" });
await server.register(registerUserEngagementRouter, { prefix: "/user-engagement" });
await server.register(registerDashboardRouter, { prefix: "/dashboard" });
await server.register(registerCmekRouter, { prefix: "/kms" });
await server.register(registerExternalGroupOrgRoleMappingRouter, { prefix: "/external-group-mappings" });
};

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