Compare commits

...

146 Commits

Author SHA1 Message Date
Sheen Capadngan
ac0cb6d96f misc: updated docs 2024-10-24 20:37:39 +08:00
Sheen Capadngan
f71f894de8 feat: added rotation handling for static ldap 2024-10-24 02:45:09 +08:00
Sheen Capadngan
66d2cc8947 misc: updated ldap edit 2024-10-24 02:10:55 +08:00
Sheen Capadngan
e034aa381a feat: initial schema setup 2024-10-24 00:29:41 +08:00
Maidul Islam
ca3b64bf6c Merge pull request #2637 from mtariqsajid/patch-1
jenkins integration is now available
2024-10-23 08:32:37 -04:00
Maidul Islam
c01ea048ce Merge pull request #2638 from scott-ray-wilson/fix-region-display
Fix: refine check of when to display region select
2024-10-22 23:58:15 -04:00
Scott Wilson
7e7d9a2bd5 fix: refine check of when to display region select 2024-10-22 20:56:10 -07:00
Tariq Sajid
782e3a8985 jenkins integration is now available 2024-10-23 01:25:13 +05:00
Daniel Hougaard
8497ac831f Merge pull request #2635 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
Maidul Islam
e5821122d5 Merge pull request #2597 from Infisical/feat/moved-mfa-to-org-level
feat: moved mfa to org level
2024-10-22 14:14:48 -04:00
Sheen Capadngan
340693cfcd feat: allow approvers to bypass secret change requests 2024-10-23 01:54:40 +08:00
Vlad Matsiiako
014b9585e0 Merge pull request #2634 from Infisical/azure-permission-docs
Add permission note for Azure Key Vault (KV) integration documentation
2024-10-22 09:48:25 -07:00
Tuan Dang
67373b0883 Add permission note for azure kv integration 2024-10-22 09:43:36 -07:00
Sheen Capadngan
2101040a77 misc: updated e2e 2024-10-23 00:13:10 +08:00
Sheen Capadngan
2e2fea304b Merge remote-tracking branch 'origin/main' into feat/moved-mfa-to-org-level 2024-10-23 00:04:09 +08:00
Sheen Capadngan
571709370d misc: addressed ux issues 2024-10-23 00:00:15 +08:00
Maidul Islam
4c2ed1cc8b Merge pull request #2631 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
Scott Wilson
067b0f4232 fix: cap width for tw container size on uw monitors 2024-10-21 20:58:28 -07:00
Maidul Islam
6ed786e6d0 Merge pull request #2627 from Infisical/daniel/go-sdk-docs-update
docs: go SDK refreshing docs
2024-10-21 19:23:00 -04:00
Maidul Islam
d187cc3d4d Merge pull request #2622 from Infisical/daniel/error-context
feat: more contextual not found errors
2024-10-21 19:09:12 -04:00
Maidul Islam
764446a2d9 update small missing ' 2024-10-21 19:06:02 -04:00
Maidul Islam
614e4934a2 Merge pull request #2630 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
Scott Wilson
14e92f895a fix: only override secret value/id by key if same environment 2024-10-21 10:55:39 -07:00
Daniel Hougaard
0a38374a73 Merge pull request #2629 from Infisical/daniel/cli-snapshot-update
fix: cli snapshot test error message change
2024-10-21 21:29:34 +04:00
Daniel Hougaard
ec3b94a335 fix: snapshot error message change 2024-10-21 21:27:16 +04:00
Scott Wilson
ca0241bb51 improvement: improve empty table labeling for org group/users 2024-10-21 10:20:43 -07:00
Scott Wilson
7403385e7c fix: fix select all rows checkbox being checked if no folders/secrets 2024-10-21 09:45:56 -07:00
Scott Wilson
2cd1141a65 Merge pull request #2610 from scott-ray-wilson/group-tables-fixes/improvements
Fix: Group Tables/Pagination Fixes and Improvements
2024-10-21 09:39:00 -07:00
Daniel Hougaard
256627b2cc Update go.mdx 2024-10-21 20:38:32 +04:00
Maidul Islam
fd7e196f8b Merge pull request #2628 from Infisical/misc/export-org-data-feature
feat: add migration script to migrate org
2024-10-21 10:16:41 -04:00
Sheen Capadngan
212748f140 misc: added cleanup of global/instance-level resources 2024-10-21 21:19:55 +08:00
Sheen Capadngan
b61582a60e Merge remote-tracking branch 'origin/main' into misc/export-org-data-feature 2024-10-21 19:04:02 +08:00
Daniel Hougaard
9ca8da152b Update go.mdx 2024-10-21 12:08:33 +04:00
Maidul Islam
c5aa1b8664 Merge pull request #2626 from Infisical/vmatsiiako-patch-docsimage-1
Update group-mappings.mdx
2024-10-20 21:18:48 -04:00
Vlad Matsiiako
90dbb417ac Update group-mappings.mdx 2024-10-20 18:17:20 -07:00
Daniel Hougaard
946651496f Merge pull request #2623 from Infisical/daniel/rate-limit-error
fix: better rate limit errors
2024-10-19 07:25:46 +04:00
Daniel Hougaard
5a8ac850b5 fix: variable naming 2024-10-19 06:41:29 +04:00
Daniel Hougaard
77a88f1575 feat: better rate limit errors 2024-10-19 06:35:49 +04:00
Daniel Hougaard
c6f66226c8 feat: more contextual not found errors 2024-10-19 05:00:14 +04:00
Maidul Islam
be00d13a46 Merge pull request #2621 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
Scott Wilson
84814a0012 improvement: improve handling of expanded secret when table overflows 2024-10-18 16:06:25 -07:00
Maidul Islam
de03692469 Merge pull request #2600 from scott-ray-wilson/select-all-secrets-page
Feat: Select All Rows for Secrets Tables
2024-10-18 18:30:35 -04:00
Maidul Islam
fb2d3e4eb7 Merge pull request #2618 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
Maidul Islam
29150e809d Merge pull request #2617 from Infisical/misc/allow-secret-scanning-whitelist
misc: added secret scanning whitelist configuration
2024-10-18 18:03:54 -04:00
Scott Wilson
e18a606b23 improvements: adjust UI for alignment and remove checkbox separator 2024-10-18 14:47:31 -07:00
Maidul Islam
67708411cd update tooltip for k8 2024-10-18 17:43:37 -04:00
Scott Wilson
3e4bd28916 Merge pull request #2619 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
Scott Wilson
a2e16370fa fix: set default value for color in tags modal 2024-10-18 14:06:38 -07:00
Scott Wilson
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
Sheen Capadngan
903fac1005 misc: added infisical cli to docker and fixed redirect 2024-10-19 03:18:13 +08:00
Scott Wilson
ff045214d6 improve readability 2024-10-18 11:59:23 -07:00
Scott Wilson
57dcf5ab28 docs: scim group mapping and scim/org improvements 2024-10-18 11:57:36 -07:00
Sheen Capadngan
959a5ec55b misc: added secret scanning whitelist conig 2024-10-19 01:59:45 +08:00
Akhil Mohan
b22a93a175 Merge pull request #2604 from akhilmhdh/feat/org-kms-ui
feat: added organization kms in org role permission section
2024-10-18 21:59:56 +05:30
Sheen Capadngan
5debeb421d Merge remote-tracking branch 'origin/main' into feat/moved-mfa-to-org-level 2024-10-18 20:07:59 +08:00
Sheen Capadngan
25b30e441a misc: added missing enforcement checks 2024-10-18 19:51:31 +08:00
Vlad Matsiiako
d7d88f3356 Merge pull request #2613 from Infisical/vmatsiiako-patch-scim-docs
Update azure.mdx
2024-10-17 21:50:00 -07:00
Vlad Matsiiako
dbaef9d227 Update azure.mdx 2024-10-17 21:42:45 -07:00
Maidul Islam
38d8b14b03 Merge pull request #2608 from Infisical/revert-2557-feat/permission-phase-2
Revert "Permission phase 2"
2024-10-17 17:38:08 -04:00
Maidul Islam
8b9244b079 Revert "Permission phase 2" 2024-10-17 17:37:41 -04:00
Maidul Islam
3d938ea62f Merge pull request #2607 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
Maidul Islam
78f668bd7f Revert "feat: added filter folder to remove read only in migration" 2024-10-17 17:36:25 -04:00
Maidul Islam
13c0b315a4 Merge pull request #2605 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
Maidul Islam
96bad7bf90 Merge pull request #2557 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
Sheen Capadngan
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
Scott Wilson
154ea9e55d fix: correct delete secret UI permission check with path included 2024-10-18 00:33:38 +05:30
Scott Wilson
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
Scott Wilson
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
Scott Wilson
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
Sheen Capadngan
bb079b3e46 misc: updated cli interactive to support mfa in org select 2024-10-18 01:59:24 +08:00
Scott Wilson
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
Sheen Capadngan
7a77dc7343 feat: added mfa popup for all select org 2024-10-17 22:29:38 +08:00
Maidul Islam
2cff772caa Merge pull request #2589 from scott-ray-wilson/entra-group-role-mapping
Feature: SCIM Group to Organization Role Mapping
2024-10-16 20:31:26 -04:00
Maidul Islam
849cad054e Merge pull request #2599 from scott-ray-wilson/admin-doc-revisions
Improvements: Revise Admin Console Docs and Server Admin Badge
2024-10-16 17:49:56 -04:00
Maidul Islam
518ca5fe58 Fix grammar 2024-10-16 17:44:27 -04:00
Scott Wilson
65e42f980c improvements: revise admin console docs and display server admin badge on users tables 2024-10-16 14:20:40 -07:00
Daniel Hougaard
f95957d534 Merge pull request #2588 from Infisical/daniel/cli-eu-region
feat: cloud EU region support
2024-10-17 00:11:00 +04:00
Sheen Capadngan
bd1ed2614e feat: added enforceMfa toggle for orgs 2024-10-17 03:02:26 +08:00
Daniel Hougaard
01920d7a50 fix: proper errors on failed to find env 2024-10-16 22:38:36 +04:00
Daniel Hougaard
83ac8abf81 Update init.go 2024-10-16 22:27:11 +04:00
Scott Wilson
44544e0491 fix: use put instead of post and improve var naming 2024-10-16 11:05:53 -07:00
Sheen
c47e0d661b Merge pull request #2566 from Infisical/feat/github-integration-app-auth
feat: github integration with Github app auth
2024-10-17 02:02:08 +08:00
Sheen Capadngan
9192c5caa2 feat: created reusable mfa flow 2024-10-17 02:01:22 +08:00
Scott Wilson
b0fc5c7e27 fix: correct boolean check for orgId error and improve visual separation of github connections 2024-10-16 10:42:22 -07:00
Akhil Mohan
bf5d7b2ba1 Merge pull request #2595 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
Sheen Capadngan
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
Sheen Capadngan
080cf67b8c misc: addressed review comments 2024-10-16 19:54:35 +08:00
Akhil Mohan
36bb954373 Merge pull request #2577 from AdityaGoyal1999/docs-fix
Updated docs to use docker compose instead of docker-compose
2024-10-16 13:09:08 +05:30
Akhil Mohan
93afa91239 Merge pull request #2435 from akhilmhdh/doc/docker-integration
chore: updated documentation for docker compose and docker for machine identity
2024-10-16 13:06:21 +05:30
Maidul Islam
73fbf66d4c Merge pull request #2591 from Infisical/maidul-uhdgwqudy
prevent sync of empty secret in ssm
2024-10-16 00:27:10 -04:00
Maidul Islam
8ae0d97973 prevent sync of empty secret in ssm 2024-10-15 18:36:06 -04:00
Maidul Islam
ca5ec94082 Merge pull request #2590 from Infisical/daniel/fix-envkey-missing-project
fix: envkey project imports
2024-10-15 18:05:59 -04:00
Daniel Hougaard
5d5da97b45 Update external-migration-fns.ts 2024-10-16 01:58:06 +04:00
Daniel Hougaard
d61f36bca8 requested changes 2024-10-16 01:33:57 +04:00
Daniel Hougaard
96f5dc7300 Update external-migration-fns.ts 2024-10-16 01:05:45 +04:00
Maidul Islam
8e5debca90 update password reset 2024-10-15 14:11:28 -04:00
Sheen Capadngan
08ed544e52 misc: added missing section regarding enabling of user auth 2024-10-16 01:38:13 +08:00
Scott Wilson
8c4a26b0e2 feature: scim group org role mapping 2024-10-15 07:57:26 -07:00
Sheen
bda0681dee Merge pull request #2587 from Infisical/misc/increase-identity-metadata-col-length
misc: increase identity metadata col length
2024-10-15 21:06:01 +08:00
Sheen Capadngan
cf092d8b4f doc: updated github action docs 2024-10-15 21:01:37 +08:00
Akhil Mohan
a11bcab0db Merge pull request #2574 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
Daniel Hougaard
986bcaf0df feat: cloud EU region support 2024-10-15 16:20:48 +04:00
Sheen Capadngan
192d1b0be3 misc: finalized ui design 2024-10-15 19:07:39 +08:00
Sheen Capadngan
82c8ca9c3d misc: added auto redirect to new connection flow 2024-10-15 19:04:40 +08:00
Sheen Capadngan
4a1adb76ab misc: finalized auth method selection ui/ux 2024-10-15 18:21:02 +08:00
Sheen Capadngan
94b799e80b misc: finalized variable names 2024-10-15 18:17:57 +08:00
Sheen Capadngan
bdae136bed misc: added proper selection of existing github oauth 2024-10-15 17:20:23 +08:00
Sheen Capadngan
73382c5363 feat: added handling of using same connection with different projects 2024-10-15 03:37:11 +08:00
Sheen Capadngan
faf2c6df90 misc: moved metadata parsing into github scope 2024-10-14 17:06:28 +08:00
Sheen Capadngan
b8f3814df0 feat: added support for app octokit 2024-10-14 16:17:39 +08:00
Aditya Goyal
c176a20010 Updated docs to use docker compose instead of docker-compose 2024-10-12 15:31:41 -04:00
=
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
Sheen Capadngan
059c552307 misc: initial setup for github integration with Github app auth 2024-10-10 03:22:25 +08:00
Meet
9f6d837a9b feat: add migration script to migrate org 2024-10-07 17:28:32 +05:30
Daniel Hougaard
ccbf09398e docs: minor rewriting 2024-09-16 16:56:47 +04:00
Daniel Hougaard
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
256 changed files with 5716 additions and 2172 deletions

View File

@@ -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=

View File

@@ -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

View File

@@ -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();
});
});

View File

@@ -28,6 +28,7 @@
"@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",
@@ -5000,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",
@@ -5026,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",
@@ -5136,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",
@@ -5272,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": {
@@ -5286,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"
},
@@ -5298,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",
@@ -14184,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",
@@ -18176,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",

View File

@@ -58,6 +58,7 @@
"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"
@@ -132,6 +133,7 @@
"@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",

View File

@@ -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();

View File

@@ -39,6 +39,7 @@ import { TCertificateServiceFactory } from "@app/services/certificate/certificat
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";
@@ -185,6 +186,7 @@ declare module "fastify" {
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

View File

@@ -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
>;
}
}

View File

@@ -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);
}
}

View File

@@ -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");
});
}
}

View File

@@ -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);
});
}
}

View File

@@ -0,0 +1,27 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const 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>
>;

View File

@@ -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",

View File

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

View File

@@ -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({

View File

@@ -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(),

View File

@@ -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;
}
});

View File

@@ -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}`
});
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -190,7 +190,9 @@ export enum EventType {
DELETE_CMEK = "delete-cmek",
GET_CMEKS = "get-cmeks",
CMEK_ENCRYPT = "cmek-encrypt",
CMEK_DECRYPT = "cmek-decrypt"
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 {
@@ -1604,6 +1606,18 @@ interface CmekDecryptEvent {
};
}
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
@@ -1750,4 +1764,6 @@ export type Event =
| DeleteCmekEvent
| GetCmeksEvent
| CmekEncryptEvent
| CmekDecryptEvent;
| CmekDecryptEvent
| GetExternalGroupOrgRoleMappingsEvent
| UpdateExternalGroupOrgRoleMappingsEvent;

View File

@@ -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,

View File

@@ -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`
});
}

View File

@@ -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;
};

View File

@@ -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),

View File

@@ -7,7 +7,7 @@ import { z } from "zod";
import { BadRequestError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { LdapSchema, TDynamicProviderFns } from "./models";
import { LdapCredentialType, LdapSchema, TDynamicProviderFns } from "./models";
const generatePassword = () => {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
@@ -193,29 +193,76 @@ export const LdapProvider = (): TDynamicProviderFns => {
const providerInputs = await validateProviderInputs(inputs);
const client = await getClient(providerInputs);
const username = generateUsername();
const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
if (providerInputs.credentialType === LdapCredentialType.Static) {
const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m);
try {
const dnArray = await executeLdif(client, generatedLdif);
if (dnMatch) {
const username = dnMatch[1];
const password = generatePassword();
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
} catch (err) {
if (providerInputs.rollbackLdif) {
const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif });
await executeLdif(client, rollbackLdif);
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rotationLdif });
try {
const dnArray = await executeLdif(client, generatedLdif);
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
} catch (err) {
throw new BadRequestError({ message: (err as Error).message });
}
} else {
throw new BadRequestError({
message: "Invalid rotation LDIF, missing DN."
});
}
} else {
const username = generateUsername();
const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
try {
const dnArray = await executeLdif(client, generatedLdif);
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
} catch (err) {
if (providerInputs.rollbackLdif) {
const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif });
await executeLdif(client, rollbackLdif);
}
throw new BadRequestError({ message: (err as Error).message });
}
throw new BadRequestError({ message: (err as Error).message });
}
};
const revoke = async (inputs: unknown, entityId: string) => {
const providerInputs = await validateProviderInputs(inputs);
const connection = await getClient(providerInputs);
const client = await getClient(providerInputs);
if (providerInputs.credentialType === LdapCredentialType.Static) {
const dnMatch = providerInputs.rotationLdif.match(/^dn:\s*(.+)/m);
if (dnMatch) {
const username = dnMatch[1];
const password = generatePassword();
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rotationLdif });
try {
const dnArray = await executeLdif(client, generatedLdif);
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
} catch (err) {
throw new BadRequestError({ message: (err as Error).message });
}
} else {
throw new BadRequestError({
message: "Invalid rotation LDIF, missing DN."
});
}
}
const revocationLdif = generateLDIF({ username: entityId, ldifTemplate: providerInputs.revocationLdif });
await executeLdif(connection, revocationLdif);
await executeLdif(client, revocationLdif);
return { entityId };
};

View File

@@ -12,6 +12,11 @@ export enum ElasticSearchAuthTypes {
ApiKey = "api-key"
}
export enum LdapCredentialType {
Dynamic = "dynamic",
Static = "static"
}
export const DynamicSecretRedisDBSchema = z.object({
host: z.string().trim().toLowerCase(),
port: z.number(),
@@ -174,16 +179,26 @@ export const AzureEntraIDSchema = z.object({
clientSecret: z.string().trim().min(1)
});
export const LdapSchema = z.object({
url: z.string().trim().min(1),
binddn: z.string().trim().min(1),
bindpass: z.string().trim().min(1),
ca: z.string().optional(),
creationLdif: z.string().min(1),
revocationLdif: z.string().min(1),
rollbackLdif: z.string().optional()
});
export const LdapSchema = z.union([
z.object({
url: z.string().trim().min(1),
binddn: z.string().trim().min(1),
bindpass: z.string().trim().min(1),
ca: z.string().optional(),
credentialType: z.literal(LdapCredentialType.Dynamic).optional().default(LdapCredentialType.Dynamic),
creationLdif: z.string().min(1),
revocationLdif: z.string().min(1),
rollbackLdif: z.string().optional()
}),
z.object({
url: z.string().trim().min(1),
binddn: z.string().trim().min(1),
bindpass: z.string().trim().min(1),
ca: z.string().optional(),
credentialType: z.literal(LdapCredentialType.Static),
rotationLdif: z.string().min(1)
})
]);
export enum DynamicSecretProviders {
SqlDatabase = "sql-database",

View File

@@ -145,7 +145,7 @@ export const externalKmsServiceFactory = ({
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 } =
@@ -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,
@@ -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,

View File

@@ -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" });
}

View File

@@ -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}'`
});
}

View File

@@ -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) => {

View File

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

View File

@@ -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 });

View File

@@ -247,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,
@@ -283,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,
@@ -369,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,
@@ -426,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) => {
@@ -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,

View File

@@ -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) => {

View File

@@ -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`
});
}

View File

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

View File

@@ -79,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"
});
}
@@ -100,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({
@@ -174,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) {
@@ -366,7 +369,7 @@ export const oidcConfigServiceFactory = ({
if (!org) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with slug '${orgSlug}' not found`
});
}
@@ -387,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,
@@ -455,7 +462,7 @@ export const oidcConfigServiceFactory = ({
});
if (!org) {
throw new NotFoundError({
message: "Organization not found"
message: `Organization with slug '${orgSlug}' not found`
});
}
@@ -561,7 +568,7 @@ export const oidcConfigServiceFactory = ({
if (!org) {
throw new NotFoundError({
message: "Organization not found."
message: `Organization with slug '${orgSlug}' not found`
});
}

View File

@@ -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 || []);

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,6 +13,7 @@ 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";
@@ -71,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<
@@ -102,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>;
@@ -122,7 +127,8 @@ export const scimServiceFactory = ({
projectBotDAL,
permissionService,
projectUserAdditionalPrivilegeDAL,
smtpService
smtpService,
externalGroupOrgRoleMappingDAL
}: TScimServiceFactoryDep) => {
const createScimToken = async ({
actor,
@@ -177,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,
@@ -692,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)
@@ -745,6 +788,8 @@ export const scimServiceFactory = ({
tx
});
await $syncNewMembersRoles(group, members);
return { group, newMembers };
}
@@ -789,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,
@@ -820,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
@@ -892,6 +958,8 @@ export const scimServiceFactory = ({
return group;
});
await $syncNewMembersRoles(group, members);
return updatedGroup;
};

View File

@@ -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`
});
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);

View File

@@ -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 },

View File

@@ -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) {
@@ -311,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(

View File

@@ -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.",

View File

@@ -117,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()),
@@ -135,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()),
@@ -170,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>>;

View File

@@ -71,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;

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -10,6 +10,7 @@ import {
GatewayTimeoutError,
InternalServerError,
NotFoundError,
RateLimitError,
ScimRequestError,
UnauthorizedError
} from "@app/lib/errors";
@@ -27,7 +28,8 @@ enum HttpStatusCodes {
Forbidden = 403,
// eslint-disable-next-line @typescript-eslint/no-shadow
InternalServerError = 500,
GatewayTimeout = 504
GatewayTimeout = 504,
TooManyRequests = 429
}
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
@@ -69,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,

View File

@@ -97,6 +97,8 @@ import { certificateTemplateDALFactory } from "@app/services/certificate-templat
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";
@@ -223,9 +225,7 @@ export const registerRoutes = async (
}: { 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);
@@ -336,6 +336,8 @@ export const registerRoutes = async (
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
const workflowIntegrationDAL = workflowIntegrationDALFactory(db);
const externalGroupOrgRoleMappingDAL = externalGroupOrgRoleMappingDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
orgRoleDAL,
@@ -442,7 +444,8 @@ export const registerRoutes = async (
projectKeyDAL,
projectBotDAL,
permissionService,
smtpService
smtpService,
externalGroupOrgRoleMappingDAL
});
const ldapService = ldapConfigServiceFactory({
@@ -537,7 +540,12 @@ export const registerRoutes = async (
orgService,
licenseService
});
const orgRoleService = orgRoleServiceFactory({ permissionService, orgRoleDAL, orgDAL });
const orgRoleService = orgRoleServiceFactory({
permissionService,
orgRoleDAL,
orgDAL,
externalGroupOrgRoleMappingDAL
});
const superAdminService = superAdminServiceFactory({
userDAL,
authService: loginService,
@@ -1231,6 +1239,13 @@ export const registerRoutes = async (
permissionService
});
const externalGroupOrgRoleMappingService = externalGroupOrgRoleMappingServiceFactory({
permissionService,
licenseService,
orgRoleDAL,
externalGroupOrgRoleMappingDAL
});
await superAdminService.initServerCfg();
//
// setup the communication with license key server
@@ -1316,7 +1331,8 @@ export const registerRoutes = async (
orgAdmin: orgAdminService,
slack: slackService,
workflowIntegration: workflowIntegrationService,
migration: migrationService
migration: migrationService,
externalGroupOrgRoleMapping: externalGroupOrgRoleMappingService
});
const cronJobs: CronJob[] = [];

View File

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

View File

@@ -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 }

View File

@@ -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;
}
});
};

View File

@@ -7,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";
@@ -106,4 +107,5 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
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" });
};

View File

@@ -189,6 +189,7 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
workspaceId: z.string().trim(),
code: z.string().trim(),
integration: z.string().trim(),
installationId: z.string().trim().optional(),
url: z.string().trim().url().optional()
}),
response: {
@@ -452,6 +453,40 @@ export const registerIntegrationAuthRouter = async (server: FastifyZodProvider)
}
});
server.route({
method: "POST",
url: "/:integrationAuthId/duplicate",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
params: z.object({
integrationAuthId: z.string().trim()
}),
body: z.object({
projectId: z.string().trim()
}),
response: {
200: z.object({
integrationAuth: integrationAuthPubSchema
})
}
},
handler: async (req) => {
const integrationAuth = await server.services.integrationAuth.duplicateIntegrationAuth({
actorId: req.permission.id,
actor: req.permission.type,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
id: req.params.integrationAuthId,
projectId: req.body.projectId
});
return { integrationAuth };
}
});
server.route({
method: "GET",
url: "/:integrationAuthId/github/envs",

View File

@@ -17,6 +17,8 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
import { integrationAuthPubSchema } from "../sanitizedSchemas";
export const registerOrgRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
@@ -68,6 +70,35 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "GET",
url: "/:organizationId/integration-authorizations",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
organizationId: z.string().trim()
}),
response: {
200: z.object({
authorizations: integrationAuthPubSchema.array()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const authorizations = await server.services.integrationAuth.listOrgIntegrationAuth({
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type,
actorOrgId: req.permission.orgId
});
return { authorizations };
}
});
server.route({
method: "GET",
url: "/audit-logs",
@@ -180,7 +211,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
email: true,
firstName: true,
lastName: true,
id: true
id: true,
superAdmin: true
}).merge(z.object({ publicKey: z.string().nullable() }))
})
)
@@ -226,7 +258,8 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
.refine((v) => slugify(v) === v, {
message: "Membership role must be a valid slug"
})
.optional()
.optional(),
enforceMfa: z.boolean().optional()
}),
response: {
200: z.object({

View File

@@ -65,7 +65,7 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
const appCfg = getConfig();
await server.services.password.changePassword({ ...req.body, userId: req.permission.id });
void res.cookie("jid", appCfg.COOKIE_SECRET_SIGN_KEY, {
void res.cookie("jid", "", {
httpOnly: true,
path: "/",
sameSite: "strict",

View File

@@ -280,10 +280,6 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
providerAuthToken: req.body.providerAuthToken
});
if (data.isMfaEnabled) {
return { mfaEnabled: true, token: data.token } as const; // for discriminated union
}
void res.setCookie("jid", data.token.refresh, {
httpOnly: true,
path: "/",
@@ -292,7 +288,6 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
});
return {
mfaEnabled: false,
encryptionVersion: data.user.encryptionVersion,
token: data.token.access,
publicKey: data.user.publicKey,

View File

@@ -47,7 +47,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
token: z.string()
token: z.string(),
isMfaEnabled: z.boolean()
})
}
},
@@ -60,6 +61,13 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
ipAddress: req.realIp
});
if (tokens.isMfaEnabled) {
return {
token: tokens.mfa as string,
isMfaEnabled: true
};
}
void res.setCookie("jid", tokens.refresh, {
httpOnly: true,
path: "/",
@@ -67,7 +75,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
secure: cfg.HTTPS_ENABLED
});
return { token: tokens.access };
return { token: tokens.access, isMfaEnabled: false };
}
});
@@ -86,21 +94,17 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
password: z.string().optional()
}),
response: {
200: z.discriminatedUnion("mfaEnabled", [
z.object({ mfaEnabled: z.literal(true), token: z.string() }),
z.object({
mfaEnabled: z.literal(false),
encryptionVersion: z.number().default(1).nullable().optional(),
protectedKey: z.string().nullable(),
protectedKeyIV: z.string().nullable(),
protectedKeyTag: z.string().nullable(),
publicKey: z.string(),
encryptedPrivateKey: z.string(),
iv: z.string(),
tag: z.string(),
token: z.string()
})
])
200: z.object({
encryptionVersion: z.number().default(1).nullable().optional(),
protectedKey: z.string().nullable(),
protectedKeyIV: z.string().nullable(),
protectedKeyTag: z.string().nullable(),
publicKey: z.string(),
encryptedPrivateKey: z.string(),
iv: z.string(),
tag: z.string(),
token: z.string()
})
}
},
handler: async (req, res) => {
@@ -118,10 +122,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
password: req.body.password
});
if (data.isMfaEnabled) {
return { mfaEnabled: true, token: data.token } as const; // for discriminated union
}
void res.setCookie("jid", data.token.refresh, {
httpOnly: true,
path: "/",
@@ -130,7 +130,6 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
});
return {
mfaEnabled: false,
encryptionVersion: data.user.encryptionVersion,
token: data.token.access,
publicKey: data.user.publicKey,

View File

@@ -45,7 +45,7 @@ export const apiKeyServiceFactory = ({ apiKeyDAL, userDAL }: TApiKeyServiceFacto
const deleteApiKey = async (userId: string, apiKeyId: string) => {
const [apiKeyData] = await apiKeyDAL.delete({ id: apiKeyId, userId });
if (!apiKeyData) throw new NotFoundError({ message: "API key not found" });
if (!apiKeyData) throw new NotFoundError({ message: `API key with ID '${apiKeyId}' not found` });
return formatApiKey(apiKeyData);
};

View File

@@ -156,7 +156,7 @@ export const tokenServiceFactory = ({ tokenDAL, userDAL, orgMembershipDAL }: TAu
}
const user = await userDAL.findById(session.userId);
if (!user || !user.isAccepted) throw new NotFoundError({ message: "User not found" });
if (!user || !user.isAccepted) throw new NotFoundError({ message: `User with ID '${session.userId}' not found` });
if (token.organizationId) {
const orgMembership = await orgMembershipDAL.findOne({

View File

@@ -99,13 +99,15 @@ export const authLoginServiceFactory = ({
ip,
userAgent,
organizationId,
authMethod
authMethod,
isMfaVerified
}: {
user: TUsers;
ip: string;
userAgent: string;
organizationId?: string;
authMethod: AuthMethod;
isMfaVerified?: boolean;
}) => {
const cfg = getConfig();
await updateUserDeviceSession(user, ip, userAgent);
@@ -123,7 +125,8 @@ export const authLoginServiceFactory = ({
userId: user.id,
tokenVersionId: tokenSession.id,
accessVersion: tokenSession.accessVersion,
organizationId
organizationId,
isMfaVerified
},
cfg.AUTH_SECRET,
{ expiresIn: cfg.JWT_AUTH_LIFETIME }
@@ -136,7 +139,8 @@ export const authLoginServiceFactory = ({
userId: user.id,
tokenVersionId: tokenSession.id,
refreshVersion: tokenSession.refreshVersion,
organizationId
organizationId,
isMfaVerified
},
cfg.AUTH_SECRET,
{ expiresIn: cfg.JWT_REFRESH_LIFETIME }
@@ -298,30 +302,6 @@ export const authLoginServiceFactory = ({
});
}
// send multi factor auth token if they it enabled
if (userEnc.isMfaEnabled && userEnc.email) {
enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd);
const mfaToken = jwt.sign(
{
authMethod,
authTokenType: AuthTokenType.MFA_TOKEN,
userId: userEnc.userId
},
cfg.AUTH_SECRET,
{
expiresIn: cfg.JWT_MFA_LIFETIME
}
);
await sendUserMfaCode({
userId: userEnc.userId,
email: userEnc.email
});
return { isMfaEnabled: true, token: mfaToken } as const;
}
const token = await generateUserTokens({
user: {
...userEnc,
@@ -333,7 +313,7 @@ export const authLoginServiceFactory = ({
organizationId
});
return { token, isMfaEnabled: false, user: userEnc } as const;
return { token, user: userEnc } as const;
};
const selectOrganization = async ({
@@ -373,15 +353,43 @@ export const authLoginServiceFactory = ({
});
}
// send multi factor auth token if they it enabled
if ((selectedOrg.enforceMfa || user.isMfaEnabled) && user.email && !decodedToken.isMfaVerified) {
enforceUserLockStatus(Boolean(user.isLocked), user.temporaryLockDateEnd);
const mfaToken = jwt.sign(
{
authMethod: decodedToken.authMethod,
authTokenType: AuthTokenType.MFA_TOKEN,
userId: user.id
},
cfg.AUTH_SECRET,
{
expiresIn: cfg.JWT_MFA_LIFETIME
}
);
await sendUserMfaCode({
userId: user.id,
email: user.email
});
return { isMfaEnabled: true, mfa: mfaToken } as const;
}
const tokens = await generateUserTokens({
authMethod: decodedToken.authMethod,
user,
userAgent,
ip: ipAddress,
organizationId
organizationId,
isMfaVerified: decodedToken.isMfaVerified
});
return tokens;
return {
...tokens,
isMfaEnabled: false
};
};
/*
@@ -504,7 +512,8 @@ export const authLoginServiceFactory = ({
ip,
userAgent,
organizationId: orgId,
authMethod: decodedToken.authMethod
authMethod: decodedToken.authMethod,
isMfaVerified: true
});
return { token, user: userEnc };
@@ -629,7 +638,6 @@ export const authLoginServiceFactory = ({
const oauth2TokenExchange = async ({ userAgent, ip, providerAuthToken, email }: TOauthTokenExchangeDTO) => {
const decodedProviderToken = validateProviderAuthToken(providerAuthToken, email);
const appCfg = getConfig();
const { authMethod, userName } = decodedProviderToken;
if (!userName) throw new BadRequestError({ message: "Missing user name" });
const organizationId =
@@ -644,29 +652,6 @@ export const authLoginServiceFactory = ({
if (!userEnc) throw new BadRequestError({ message: "Invalid token" });
if (!userEnc.serverEncryptedPrivateKey)
throw new BadRequestError({ message: "Key handoff incomplete. Please try logging in again." });
// send multi factor auth token if they it enabled
if (userEnc.isMfaEnabled && userEnc.email) {
enforceUserLockStatus(Boolean(userEnc.isLocked), userEnc.temporaryLockDateEnd);
const mfaToken = jwt.sign(
{
authMethod,
authTokenType: AuthTokenType.MFA_TOKEN,
userId: userEnc.userId
},
appCfg.AUTH_SECRET,
{
expiresIn: appCfg.JWT_MFA_LIFETIME
}
);
await sendUserMfaCode({
userId: userEnc.userId,
email: userEnc.email
});
return { isMfaEnabled: true, token: mfaToken } as const;
}
const token = await generateUserTokens({
user: { ...userEnc, id: userEnc.userId },

View File

@@ -52,6 +52,7 @@ export type AuthModeJwtTokenPayload = {
tokenVersionId: string;
accessVersion: number;
organizationId?: string;
isMfaVerified?: boolean;
};
export type AuthModeMfaJwtTokenPayload = {
@@ -69,6 +70,7 @@ export type AuthModeRefreshJwtTokenPayload = {
tokenVersionId: string;
refreshVersion: number;
organizationId?: string;
isMfaVerified?: boolean;
};
export type AuthModeProviderJwtTokenPayload = {

View File

@@ -113,10 +113,10 @@ export const getCaCredentials = async ({
kmsService
}: TGetCaCredentialsDTO) => {
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 caSecret = await certificateAuthoritySecretDAL.findOne({ caId });
if (!caSecret) throw new NotFoundError({ message: "CA secret not found" });
if (!caSecret) throw new NotFoundError({ message: `CA secret for CA with ID '${caId}' not found` });
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
@@ -165,7 +165,7 @@ export const getCaCertChains = async ({
kmsService
}: TGetCaCertChainsDTO) => {
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 keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
@@ -256,7 +256,7 @@ export const rebuildCaCrl = async ({
kmsService
}: TRebuildCaCrlDTO) => {
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 caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });

View File

@@ -76,7 +76,7 @@ export const certificateAuthorityQueueFactory = ({
logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
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 caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });

View File

@@ -122,7 +122,7 @@ export const certificateAuthorityServiceFactory = ({
actorOrgId
}: TCreateCaDTO) => {
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 { permission } = await permissionService.getProjectPermission(
actor,
@@ -290,7 +290,7 @@ export const certificateAuthorityServiceFactory = ({
*/
const getCaById = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaDTO) => {
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,
@@ -321,7 +321,7 @@ export const certificateAuthorityServiceFactory = ({
actorOrgId
}: TUpdateCaDTO) => {
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,
@@ -346,7 +346,7 @@ export const certificateAuthorityServiceFactory = ({
*/
const deleteCaById = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TDeleteCaDTO) => {
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,
@@ -371,7 +371,7 @@ export const certificateAuthorityServiceFactory = ({
*/
const getCaCsr = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCsrDTO) => {
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,
@@ -430,7 +430,7 @@ export const certificateAuthorityServiceFactory = ({
*/
const renewCaCert = async ({ caId, notAfter, actorId, actorAuthMethod, actor, actorOrgId }: TRenewCaCertDTO) => {
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` });
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
@@ -702,7 +702,7 @@ export const certificateAuthorityServiceFactory = ({
const getCaCerts = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCertsDTO) => {
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,
@@ -736,7 +736,7 @@ export const certificateAuthorityServiceFactory = ({
*/
const getCaCert = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCertDTO) => {
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` });
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
const { permission } = await permissionService.getProjectPermission(
@@ -778,7 +778,7 @@ export const certificateAuthorityServiceFactory = ({
});
if (!caCert) {
throw new NotFoundError({ message: "CA certificate not found" });
throw new NotFoundError({ message: `Ca certificate with ID '${caCertId}' not found for CA with ID '${caId}'` });
}
const ca = await certificateAuthorityDAL.findById(caId);
@@ -963,7 +963,7 @@ export const certificateAuthorityServiceFactory = ({
certificateChain
}: TImportCertToCaDTO) => {
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,
@@ -1115,7 +1115,7 @@ export const certificateAuthorityServiceFactory = ({
certificateTemplate = await certificateTemplateDAL.getById(certificateTemplateId);
if (!certificateTemplate) {
throw new NotFoundError({
message: "Certificate template not found"
message: `Certificate template with ID '${certificateTemplateId}' not found`
});
}
@@ -1124,7 +1124,7 @@ export const certificateAuthorityServiceFactory = ({
}
if (!ca) {
throw new NotFoundError({ message: "CA not found" });
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
}
const { permission } = await permissionService.getProjectPermission(
@@ -1442,7 +1442,7 @@ export const certificateAuthorityServiceFactory = ({
certificateTemplate = await certificateTemplateDAL.getById(certificateTemplateId);
if (!certificateTemplate) {
throw new NotFoundError({
message: "Certificate template not found"
message: `Certificate template with ID '${certificateTemplateId}' not found`
});
}
@@ -1451,7 +1451,7 @@ export const certificateAuthorityServiceFactory = ({
}
if (!ca) {
throw new NotFoundError({ message: "CA not found" });
throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
}
if (!dto.isInternal) {
@@ -1484,7 +1484,7 @@ export const certificateAuthorityServiceFactory = ({
// check PKI collection
if (pkiCollectionId) {
const pkiCollection = await pkiCollectionDAL.findById(pkiCollectionId);
if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" });
if (!pkiCollection) throw new NotFoundError({ message: `PKI collection with ID '${pkiCollectionId}' not found` });
if (pkiCollection.projectId !== ca.projectId) throw new BadRequestError({ message: "Invalid PKI collection" });
}
@@ -1810,7 +1810,7 @@ export const certificateAuthorityServiceFactory = ({
actorOrgId
}: TGetCaCertificateTemplatesDTO) => {
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,

View File

@@ -64,7 +64,7 @@ export const certificateTemplateServiceFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) {
throw new NotFoundError({
message: "CA not found"
message: `CA with ID ${caId} not found`
});
}
const { permission } = await permissionService.getProjectPermission(
@@ -98,7 +98,7 @@ export const certificateTemplateServiceFactory = ({
const certificateTemplate = await certificateTemplateDAL.getById(id, tx);
if (!certificateTemplate) {
throw new NotFoundError({
message: "Certificate template not found"
message: `Certificate template with ID ${id} not found`
});
}
@@ -124,7 +124,7 @@ export const certificateTemplateServiceFactory = ({
const certTemplate = await certificateTemplateDAL.getById(id);
if (!certTemplate) {
throw new NotFoundError({
message: "Certificate template not found."
message: `Certificate template with ID ${id} not found`
});
}
@@ -169,7 +169,7 @@ export const certificateTemplateServiceFactory = ({
const updatedTemplate = await certificateTemplateDAL.getById(id, tx);
if (!updatedTemplate) {
throw new NotFoundError({
message: "Certificate template not found"
message: `Certificate template with ID ${id} not found`
});
}
@@ -181,7 +181,7 @@ export const certificateTemplateServiceFactory = ({
const certTemplate = await certificateTemplateDAL.getById(id);
if (!certTemplate) {
throw new NotFoundError({
message: "Certificate template not found."
message: `Certificate template with ID ${id} not found`
});
}
@@ -207,7 +207,7 @@ export const certificateTemplateServiceFactory = ({
const certTemplate = await certificateTemplateDAL.getById(id);
if (!certTemplate) {
throw new NotFoundError({
message: "Certificate template not found."
message: `Certificate template with ID ${id} not found`
});
}
@@ -247,7 +247,7 @@ export const certificateTemplateServiceFactory = ({
const certTemplate = await certificateTemplateDAL.getById(certificateTemplateId);
if (!certTemplate) {
throw new NotFoundError({
message: "Certificate template not found."
message: `Certificate template with ID ${certificateTemplateId} not found`
});
}
@@ -324,7 +324,7 @@ export const certificateTemplateServiceFactory = ({
const certTemplate = await certificateTemplateDAL.getById(certificateTemplateId);
if (!certTemplate) {
throw new NotFoundError({
message: "Certificate template not found."
message: `Certificate template with ID ${certificateTemplateId} not found`
});
}
@@ -347,7 +347,7 @@ export const certificateTemplateServiceFactory = ({
if (!originalCaEstConfig) {
throw new NotFoundError({
message: "EST configuration not found"
message: `EST configuration with certificate template ID ${certificateTemplateId} not found`
});
}
@@ -403,7 +403,7 @@ export const certificateTemplateServiceFactory = ({
const certTemplate = await certificateTemplateDAL.getById(certificateTemplateId);
if (!certTemplate) {
throw new NotFoundError({
message: "Certificate template not found."
message: `Certificate template with ID ${certificateTemplateId} not found`
});
}
@@ -428,7 +428,7 @@ export const certificateTemplateServiceFactory = ({
if (!estConfig) {
throw new NotFoundError({
message: "EST configuration not found"
message: `EST configuration with certificate template ID ${certificateTemplateId} not found`
});
}

View File

@@ -46,7 +46,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
const updateCmekById = async ({ keyId, ...data }: TUpdabteCmekByIdDTO, actor: FastifyRequest["permission"]) => {
const key = await kmsDAL.findById(keyId);
if (!key) throw new NotFoundError({ message: "Key not found" });
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
@@ -68,7 +68,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
const deleteCmekById = async (keyId: string, actor: FastifyRequest["permission"]) => {
const key = await kmsDAL.findById(keyId);
if (!key) throw new NotFoundError({ message: "Key not found" });
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
@@ -109,7 +109,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
const cmekEncrypt = async ({ keyId, plaintext }: TCmekEncryptDTO, actor: FastifyRequest["permission"]) => {
const key = await kmsDAL.findById(keyId);
if (!key) throw new NotFoundError({ message: "Key not found" });
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
@@ -135,7 +135,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TC
const cmekDecrypt = async ({ keyId, ciphertext }: TCmekDecryptDTO, actor: FastifyRequest["permission"]) => {
const key = await kmsDAL.findById(keyId);
if (!key) throw new NotFoundError({ message: "Key not found" });
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });

View File

@@ -0,0 +1,46 @@
import { Tables } from "knex/types/tables";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { TExternalGroupOrgRoleMappings } from "@app/db/schemas/external-group-org-role-mappings";
import { ormify } from "@app/lib/knex";
export type TExternalGroupOrgRoleMappingDALFactory = ReturnType<typeof externalGroupOrgRoleMappingDALFactory>;
export const externalGroupOrgRoleMappingDALFactory = (db: TDbClient) => {
const externalGroupOrgRoleMappingOrm = ormify(db, TableName.ExternalGroupOrgRoleMapping);
const updateExternalGroupOrgRoleMappingForOrg = async (
orgId: string,
newMappings: readonly Tables[TableName.ExternalGroupOrgRoleMapping]["insert"][]
) => {
const currentMappings = await externalGroupOrgRoleMappingOrm.find({ orgId });
const newMap = new Map(newMappings.map((mapping) => [mapping.groupName, mapping]));
const currentMap = new Map(currentMappings.map((mapping) => [mapping.groupName, mapping]));
const mappingsToDelete = currentMappings.filter((mapping) => !newMap.has(mapping.groupName));
const mappingsToUpdate = currentMappings
.filter((mapping) => newMap.has(mapping.groupName))
.map((mapping) => ({ id: mapping.id, ...newMap.get(mapping.groupName) }));
const mappingsToInsert = newMappings.filter((mapping) => !currentMap.has(mapping.groupName));
const mappings = await externalGroupOrgRoleMappingOrm.transaction(async (tx) => {
await externalGroupOrgRoleMappingOrm.delete({ $in: { id: mappingsToDelete.map((mapping) => mapping.id) } }, tx);
const updatedMappings: TExternalGroupOrgRoleMappings[] = [];
for await (const { id, ...mappingData } of mappingsToUpdate) {
const updatedMapping = await externalGroupOrgRoleMappingOrm.update({ id }, mappingData, tx);
updatedMappings.push(updatedMapping[0]);
}
const insertedMappings = await externalGroupOrgRoleMappingOrm.insertMany(mappingsToInsert, tx);
return [...updatedMappings, ...insertedMappings];
});
return mappings;
};
return { ...externalGroupOrgRoleMappingOrm, updateExternalGroupOrgRoleMappingForOrg };
};

View File

@@ -0,0 +1,67 @@
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { isCustomOrgRole } from "@app/services/org/org-role-fns";
import { TExternalGroupOrgMembershipRoleMappingDTO } from "./external-group-org-role-mapping-types";
export const constructGroupOrgMembershipRoleMappings = async ({
mappingsDTO,
orgId,
orgRoleDAL,
licenseService
}: {
mappingsDTO: TExternalGroupOrgMembershipRoleMappingDTO[];
orgRoleDAL: TOrgRoleDALFactory;
licenseService: TLicenseServiceFactory;
orgId: string;
}) => {
const plan = await licenseService.getPlan(orgId);
// prevent setting custom values if not in plan
if (mappingsDTO.some((map) => isCustomOrgRole(map.roleSlug)) && !plan?.rbac)
throw new BadRequestError({
message:
"Failed to set group organization role mapping due to plan RBAC restriction. Upgrade plan to set custom role mapping."
});
const customRoleSlugs = mappingsDTO
.filter((mapping) => isCustomOrgRole(mapping.roleSlug))
.map((mapping) => mapping.roleSlug);
let customRolesMap: Map<string, TOrgRoles> = new Map();
if (customRoleSlugs.length > 0) {
const customRoles = await orgRoleDAL.find({
$in: {
slug: customRoleSlugs
}
});
customRolesMap = new Map(customRoles.map((role) => [role.slug, role]));
}
const mappings = mappingsDTO.map(({ roleSlug, groupName }) => {
if (isCustomOrgRole(roleSlug)) {
const customRole = customRolesMap.get(roleSlug);
if (!customRole) throw new NotFoundError({ message: `Custom role ${roleSlug} not found.` });
return {
groupName,
role: OrgMembershipRole.Custom,
roleId: customRole.id,
orgId
};
}
return {
groupName,
role: roleSlug,
roleId: null, // need to set explicitly null for updates
orgId
};
});
return mappings;
};

View File

@@ -0,0 +1,78 @@
import { ForbiddenError } from "@casl/ability";
import { FastifyRequest } from "fastify";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { constructGroupOrgMembershipRoleMappings } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-fns";
import { TSyncExternalGroupOrgMembershipRoleMappingsDTO } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-types";
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
import { TExternalGroupOrgRoleMappingDALFactory } from "./external-group-org-role-mapping-dal";
type TExternalGroupOrgRoleMappingServiceFactoryDep = {
externalGroupOrgRoleMappingDAL: TExternalGroupOrgRoleMappingDALFactory;
permissionService: TPermissionServiceFactory;
licenseService: TLicenseServiceFactory;
orgRoleDAL: TOrgRoleDALFactory;
};
export type TExternalGroupOrgRoleMappingServiceFactory = ReturnType<typeof externalGroupOrgRoleMappingServiceFactory>;
export const externalGroupOrgRoleMappingServiceFactory = ({
externalGroupOrgRoleMappingDAL,
licenseService,
permissionService,
orgRoleDAL
}: TExternalGroupOrgRoleMappingServiceFactoryDep) => {
const listExternalGroupOrgRoleMappings = async (actor: FastifyRequest["permission"]) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
// TODO: will need to change if we add support for ldap, oidc, etc.
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Scim);
const mappings = await externalGroupOrgRoleMappingDAL.find({
orgId: actor.orgId
});
return mappings;
};
const updateExternalGroupOrgRoleMappings = async (
dto: TSyncExternalGroupOrgMembershipRoleMappingsDTO,
actor: FastifyRequest["permission"]
) => {
const { permission } = await permissionService.getOrgPermission(
actor.type,
actor.id,
actor.orgId,
actor.authMethod,
actor.orgId
);
// TODO: will need to change if we add support for ldap, oidc, etc.
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
const mappings = await constructGroupOrgMembershipRoleMappings({
mappingsDTO: dto.mappings,
orgRoleDAL,
licenseService,
orgId: actor.orgId
});
const data = await externalGroupOrgRoleMappingDAL.updateExternalGroupOrgRoleMappingForOrg(actor.orgId, mappings);
return data;
};
return {
updateExternalGroupOrgRoleMappings,
listExternalGroupOrgRoleMappings
};
};

View File

@@ -0,0 +1,8 @@
export type TExternalGroupOrgMembershipRoleMappingDTO = {
groupName: string;
roleSlug: string;
};
export type TSyncExternalGroupOrgMembershipRoleMappingsDTO = {
mappings: TExternalGroupOrgMembershipRoleMappingDTO[];
};

View File

@@ -126,6 +126,7 @@ export const importDataIntoInfisicalFn = async ({
const originalToNewProjectId = new Map<string, string>();
const originalToNewEnvironmentId = new Map<string, string>();
const projectsNotImported: string[] = [];
await projectDAL.transaction(async (tx) => {
for await (const project of data.projects) {
@@ -143,16 +144,21 @@ export const importDataIntoInfisicalFn = async ({
logger.error(e, `Failed to import to project [name:${project.name}]`);
throw new BadRequestError({ message: `Failed to import to project [name:${project.name}]` });
});
originalToNewProjectId.set(project.id, newProject.id);
}
// Import environments
if (data.environments) {
for await (const environment of data.environments) {
const projectId = originalToNewProjectId.get(environment.projectId)!;
const projectId = originalToNewProjectId.get(environment.projectId);
const slug = slugify(`${environment.name}-${alphaNumericNanoId(4)}`);
if (!projectId) {
projectsNotImported.push(environment.projectId);
// eslint-disable-next-line no-continue
continue;
}
const existingEnv = await projectEnvDAL.findOne({ projectId, slug }, tx);
if (existingEnv) {
@@ -180,6 +186,11 @@ export const importDataIntoInfisicalFn = async ({
>();
for (const secret of data.secrets) {
if (!originalToNewEnvironmentId.get(secret.environmentId)) {
// eslint-disable-next-line no-continue
continue;
}
if (!mappedToEnvironmentId.has(secret.environmentId)) {
mappedToEnvironmentId.set(secret.environmentId, []);
}
@@ -254,4 +265,6 @@ export const importDataIntoInfisicalFn = async ({
}
}
});
return { projectsNotImported };
};

View File

@@ -97,7 +97,7 @@ export const externalMigrationQueueFactory = ({
const decryptedJson = JSON.parse(decrypted) as TImportInfisicalDataCreate;
await importDataIntoInfisicalFn({
const { projectsNotImported } = await importDataIntoInfisicalFn({
input: decryptedJson,
projectDAL,
projectEnvDAL,
@@ -112,6 +112,17 @@ export const externalMigrationQueueFactory = ({
secretV2BridgeService
});
if (projectsNotImported.length) {
logger.info(
{
actorEmail,
actorOrgId: decryptedJson.actorOrgId,
projectsNotImported
},
"One or more projects were not imported during import from external source"
);
}
await smtpService.sendMail({
recipients: [actorEmail],
subjectLine: "Infisical import successful",

View File

@@ -169,7 +169,7 @@ export const groupProjectServiceFactory = ({
if (!ghostUser) {
throw new NotFoundError({
message: "Failed to find project owner"
message: `Failed to find project owner of project with name ${project.name}`
});
}
@@ -177,7 +177,7 @@ export const groupProjectServiceFactory = ({
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 name ${project.name}`
});
}
@@ -185,7 +185,7 @@ export const groupProjectServiceFactory = ({
if (!bot) {
throw new NotFoundError({
message: "Failed to find project bot"
message: `Failed to find project bot in project with name ${project.name}`
});
}
@@ -425,7 +425,7 @@ export const groupProjectServiceFactory = ({
if (!groupMembership) {
throw new NotFoundError({
message: "Cannot find group membership"
message: `Group membership with ID ${groupId} not found in project with ID ${projectId}`
});
}

View File

@@ -154,7 +154,7 @@ export const identityAwsAuthServiceFactory = ({
actorOrgId
}: TAttachAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add AWS Auth to already configured identity"
@@ -233,7 +233,7 @@ export const identityAwsAuthServiceFactory = ({
actorOrgId
}: TUpdateAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({
message: "Failed to update AWS Auth"
@@ -292,7 +292,7 @@ export const identityAwsAuthServiceFactory = ({
const getAwsAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({
message: "The identity does not have AWS Auth attached"
@@ -319,7 +319,7 @@ export const identityAwsAuthServiceFactory = ({
actorOrgId
}: TRevokeAwsAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_AUTH)
throw new BadRequestError({
message: "The identity does not have aws auth"

View File

@@ -125,7 +125,7 @@ export const identityAzureAuthServiceFactory = ({
actorOrgId
}: TAttachAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add Azure Auth to already configured identity"
@@ -203,7 +203,7 @@ export const identityAzureAuthServiceFactory = ({
actorOrgId
}: TUpdateAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
throw new BadRequestError({
message: "Failed to update Azure Auth"
@@ -265,7 +265,7 @@ export const identityAzureAuthServiceFactory = ({
const getAzureAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
throw new BadRequestError({
message: "The identity does not have Azure Auth attached"
@@ -293,7 +293,7 @@ export const identityAzureAuthServiceFactory = ({
actorOrgId
}: TRevokeAzureAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AZURE_AUTH)
throw new BadRequestError({
message: "The identity does not have azure auth"

View File

@@ -167,7 +167,7 @@ export const identityGcpAuthServiceFactory = ({
actorOrgId
}: TAttachGcpAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add GCP Auth to already configured identity"
@@ -247,7 +247,7 @@ export const identityGcpAuthServiceFactory = ({
actorOrgId
}: TUpdateGcpAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH)
throw new BadRequestError({
message: "Failed to update GCP Auth"
@@ -310,7 +310,7 @@ export const identityGcpAuthServiceFactory = ({
const getGcpAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetGcpAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH)
throw new BadRequestError({
message: "The identity does not have GCP Auth attached"
@@ -338,7 +338,7 @@ export const identityGcpAuthServiceFactory = ({
actorOrgId
}: TRevokeGcpAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.GCP_AUTH)
throw new BadRequestError({
message: "The identity does not have gcp auth"

View File

@@ -72,10 +72,19 @@ export const identityKubernetesAuthServiceFactory = ({
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({
identityId: identityKubernetesAuth.identityId
});
if (!identityMembershipOrg) throw new NotFoundError({ message: "Identity organization membership not found" });
if (!identityMembershipOrg) {
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityKubernetesAuth.identityId}' not found`
});
}
const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.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 ${identityMembershipOrg.orgId}`,
name: "OrgBotNotFound"
});
}
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
@@ -250,7 +259,7 @@ export const identityKubernetesAuthServiceFactory = ({
actorOrgId
}: TAttachKubernetesAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add Kubernetes Auth to already configured identity"
@@ -394,7 +403,7 @@ export const identityKubernetesAuthServiceFactory = ({
actorOrgId
}: TUpdateKubernetesAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH)
throw new BadRequestError({
message: "Failed to update Kubernetes Auth"
@@ -451,8 +460,12 @@ export const identityKubernetesAuthServiceFactory = ({
};
const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId });
if (!orgBot) throw new NotFoundError({ message: "Org bot not found", name: "OrgBotNotFound" });
if (!orgBot) {
throw new NotFoundError({
message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`,
name: "OrgBotNotFound"
});
}
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
iv: orgBot.symmetricKeyIV,
@@ -518,7 +531,7 @@ export const identityKubernetesAuthServiceFactory = ({
actorOrgId
}: TGetKubernetesAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH)
throw new BadRequestError({
message: "The identity does not have Kubernetes Auth attached"
@@ -536,7 +549,11 @@ export const identityKubernetesAuthServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.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 ${identityMembershipOrg.orgId}`,
name: "OrgBotNotFound"
});
const key = infisicalSymmetricDecrypt({
ciphertext: orgBot.encryptedSymmetricKey,
@@ -579,7 +596,7 @@ export const identityKubernetesAuthServiceFactory = ({
actorOrgId
}: TRevokeKubernetesAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.KUBERNETES_AUTH)
throw new BadRequestError({
message: "The identity does not have kubernetes auth"

View File

@@ -68,12 +68,17 @@ export const identityOidcAuthServiceFactory = ({
identityId: identityOidcAuth.identityId
});
if (!identityMembershipOrg) {
throw new NotFoundError({ message: "Identity organization membership not found" });
throw new NotFoundError({
message: `Identity organization membership for identity with ID '${identityOidcAuth.identityId}' not found`
});
}
const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId });
if (!orgBot) {
throw new NotFoundError({ message: "Organization bot was not found", name: "OrgBotNotFound" });
throw new NotFoundError({
message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`,
name: "OrgBotNotFound"
});
}
const key = infisicalSymmetricDecrypt({
@@ -221,7 +226,7 @@ export const identityOidcAuthServiceFactory = ({
}: TAttachOidcAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) {
throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
@@ -360,7 +365,7 @@ export const identityOidcAuthServiceFactory = ({
}: TUpdateOidcAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) {
throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.OIDC_AUTH) {
@@ -422,7 +427,10 @@ export const identityOidcAuthServiceFactory = ({
const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId });
if (!orgBot) {
throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
throw new NotFoundError({
message: `Organization bot not found for organization with ID '${identityMembershipOrg.orgId}'`,
name: "OrgBotNotFound"
});
}
const key = infisicalSymmetricDecrypt({
@@ -460,7 +468,7 @@ export const identityOidcAuthServiceFactory = ({
const getOidcAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetOidcAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) {
throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.OIDC_AUTH) {
@@ -482,7 +490,10 @@ export const identityOidcAuthServiceFactory = ({
const orgBot = await orgBotDAL.findOne({ orgId: identityMembershipOrg.orgId });
if (!orgBot) {
throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
throw new NotFoundError({
message: `Organization bot not found for organization with ID ${identityMembershipOrg.orgId}`,
name: "OrgBotNotFound"
});
}
const key = infisicalSymmetricDecrypt({

View File

@@ -66,7 +66,7 @@ export const identityProjectServiceFactory = ({
const existingIdentity = await identityProjectDAL.findOne({ identityId, projectId });
if (existingIdentity)
throw new BadRequestError({
message: `Identity with id ${identityId} already exists in project with id ${projectId}`
message: `Identity with ID ${identityId} already exists in project with ID ${projectId}`
});
const project = await projectDAL.findById(projectId);
@@ -76,7 +76,7 @@ export const identityProjectServiceFactory = ({
});
if (!identityOrgMembership)
throw new NotFoundError({
message: `Failed to find identity with id ${identityId}`
message: `Failed to find identity with ID ${identityId}`
});
for await (const { role: requestedRoleChange } of roles) {
@@ -104,7 +104,7 @@ export const identityProjectServiceFactory = ({
})
: [];
if (customRoles.length !== customInputRoles.length)
throw new NotFoundError({ message: "Custom project roles not found" });
throw new NotFoundError({ message: "One or more custom project roles not found" });
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
const projectIdentity = await identityProjectDAL.transaction(async (tx) => {
@@ -166,7 +166,7 @@ export const identityProjectServiceFactory = ({
const projectIdentity = await identityProjectDAL.findOne({ identityId, projectId });
if (!projectIdentity)
throw new NotFoundError({
message: `Identity with id ${identityId} doesn't exists in project with id ${projectId}`
message: `Identity with ID ${identityId} doesn't exists in project with ID ${projectId}`
});
for await (const { role: requestedRoleChange } of roles) {
@@ -192,7 +192,7 @@ export const identityProjectServiceFactory = ({
})
: [];
if (customRoles.length !== customInputRoles.length)
throw new NotFoundError({ message: "Custom project roles not found" });
throw new NotFoundError({ message: "One or more custom project roles not found" });
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
@@ -237,8 +237,9 @@ export const identityProjectServiceFactory = ({
projectId
}: TDeleteProjectIdentityDTO) => {
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
if (!identityProjectMembership)
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
if (!identityProjectMembership) {
throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
}
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -314,7 +315,10 @@ export const identityProjectServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
const [identityMembership] = await identityProjectDAL.findByProjectId(projectId, { identityId });
if (!identityMembership) throw new NotFoundError({ message: `Membership not found for identity ${identityId}` });
if (!identityMembership)
throw new NotFoundError({
message: `Project membership for identity with ID '${identityId} in project with ID '${projectId}' not found`
});
return identityMembership;
};

View File

@@ -64,7 +64,7 @@ export const identityTokenAuthServiceFactory = ({
actorOrgId
}: TAttachTokenAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add Token Auth to already configured identity"
@@ -136,7 +136,7 @@ export const identityTokenAuthServiceFactory = ({
actorOrgId
}: TUpdateTokenAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
throw new BadRequestError({
message: "Failed to update Token Auth"
@@ -196,7 +196,7 @@ export const identityTokenAuthServiceFactory = ({
const getTokenAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetTokenAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
throw new BadRequestError({
message: "The identity does not have Token Auth attached"
@@ -224,7 +224,7 @@ export const identityTokenAuthServiceFactory = ({
actorOrgId
}: TRevokeTokenAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
throw new BadRequestError({
message: "The identity does not have Token Auth"
@@ -269,7 +269,7 @@ export const identityTokenAuthServiceFactory = ({
name
}: TCreateTokenAuthTokenDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
throw new BadRequestError({
message: "The identity does not have Token Auth"
@@ -343,7 +343,7 @@ export const identityTokenAuthServiceFactory = ({
actorOrgId
}: TGetTokenAuthTokensDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
throw new BadRequestError({
message: "The identity does not have Token Auth"
@@ -376,9 +376,11 @@ export const identityTokenAuthServiceFactory = ({
actorOrgId
}: TUpdateTokenAuthTokenDTO) => {
const foundToken = await identityAccessTokenDAL.findById(tokenId);
if (!foundToken) throw new NotFoundError({ message: "Failed to find token" });
if (!foundToken) throw new NotFoundError({ message: `Token with ID ${tokenId} not found` });
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: foundToken.identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) {
throw new NotFoundError({ message: `Failed to find identity with ID ${foundToken.identityId}` });
}
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.TOKEN_AUTH)
throw new BadRequestError({
message: "The identity does not have Token Auth"
@@ -431,7 +433,7 @@ export const identityTokenAuthServiceFactory = ({
});
if (!identityAccessToken)
throw new NotFoundError({
message: "Failed to find token"
message: `Token with ID ${tokenId} not found or already revoked`
});
const identityOrgMembership = await identityOrgMembershipDAL.findOne({
@@ -439,7 +441,7 @@ export const identityTokenAuthServiceFactory = ({
});
if (!identityOrgMembership) {
throw new NotFoundError({ message: "No identity organization membership found" });
throw new NotFoundError({ message: `Failed to find identity with ID ${identityAccessToken.identityId}` });
}
const { permission } = await permissionService.getOrgPermission(

View File

@@ -155,7 +155,7 @@ export const identityUaServiceFactory = ({
actorOrgId
}: TAttachUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add universal auth to already configured identity"
@@ -246,7 +246,7 @@ export const identityUaServiceFactory = ({
actorOrgId
}: TUpdateUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "Failed to updated universal auth"
@@ -320,7 +320,7 @@ export const identityUaServiceFactory = ({
const getIdentityUniversalAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
@@ -347,7 +347,7 @@ export const identityUaServiceFactory = ({
actorOrgId
}: TRevokeUaDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
@@ -392,7 +392,7 @@ export const identityUaServiceFactory = ({
numUsesLimit
}: TCreateUaClientSecretDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
@@ -452,7 +452,7 @@ export const identityUaServiceFactory = ({
identityId
}: TGetUaClientSecretsDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
@@ -499,7 +499,7 @@ export const identityUaServiceFactory = ({
clientSecretId
}: TGetUniversalAuthClientSecretByIdDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"
@@ -538,7 +538,7 @@ export const identityUaServiceFactory = ({
clientSecretId
}: TRevokeUaClientSecretDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new NotFoundError({ message: "Failed to find identity" });
if (!identityMembershipOrg) throw new NotFoundError({ message: `Failed to find identity with ID ${identityId}` });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.Univeral)
throw new BadRequestError({
message: "The identity does not have universal auth"

View File

@@ -1,9 +1,13 @@
/* eslint-disable no-await-in-loop */
import { createAppAuth } from "@octokit/auth-app";
import { Octokit } from "@octokit/rest";
import { TIntegrationAuths } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { NotFoundError } from "@app/lib/errors";
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
import { Integrations, IntegrationUrls } from "./integration-list";
// akhilmhdh: check this part later. Copied from old base
@@ -230,7 +234,13 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
/**
* Return list of repositories for Github integration
*/
const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
const getAppsGithub = async ({
accessToken,
authMetadata
}: {
accessToken: string;
authMetadata?: TIntegrationAuthMetadata;
}) => {
interface GitHubApp {
id: string;
name: string;
@@ -242,6 +252,29 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
};
}
if (authMetadata?.installationId) {
const appCfg = getConfig();
const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
installationId: authMetadata.installationId
}
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const repos = await octokit.paginate("GET /installation/repositories", {
per_page: 100
});
return repos.map((a) => ({
appId: String(a.id),
name: a.name,
owner: a.owner.login
}));
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const repos = (await new Octokit({
auth: accessToken
@@ -1056,6 +1089,7 @@ const getAppsAzureDevOps = async ({ accessToken, orgName }: { accessToken: strin
export const getApps = async ({
integration,
integrationAuth,
accessToken,
accessId,
teamId,
@@ -1066,6 +1100,7 @@ export const getApps = async ({
integration: string;
accessToken: string;
accessId?: string;
integrationAuth: TIntegrationAuths;
teamId?: string | null;
azureDevOpsOrgName?: string | null;
workspaceSlug?: string;
@@ -1099,7 +1134,8 @@ export const getApps = async ({
case Integrations.GITHUB:
return getAppsGithub({
accessToken
accessToken,
authMetadata: IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {})
});
case Integrations.GITLAB:
@@ -1223,6 +1259,6 @@ export const getApps = async ({
});
default:
throw new NotFoundError({ message: "integration not found" });
throw new NotFoundError({ message: `Integration '${integration}' not found` });
}
};

View File

@@ -3,7 +3,7 @@ import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName, TIntegrationAuths, TIntegrationAuthsUpdate } from "@app/db/schemas";
import { BadRequestError, DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TIntegrationAuthDALFactory = ReturnType<typeof integrationAuthDALFactory>;
@@ -28,8 +28,23 @@ export const integrationAuthDALFactory = (db: TDbClient) => {
}
};
const getByOrg = async (orgId: string, tx?: Knex) => {
try {
const integrationAuths = await (tx || db)(TableName.IntegrationAuth)
.join(TableName.Project, `${TableName.Project}.id`, `${TableName.IntegrationAuth}.projectId`)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.Project}.orgId`)
.where(`${TableName.Organization}.id`, "=", orgId)
.select(selectAllTableCols(TableName.IntegrationAuth));
return integrationAuths;
} catch (error) {
throw new DatabaseError({ error, name: "get by org" });
}
};
return {
...integrationAuthOrm,
bulkUpdate
bulkUpdate,
getByOrg
};
};

View File

@@ -0,0 +1,7 @@
import { z } from "zod";
export const IntegrationAuthMetadataSchema = z.object({
installationId: z.string().optional()
});
export type TIntegrationAuthMetadata = z.infer<typeof IntegrationAuthMetadataSchema>;

View File

@@ -1,14 +1,16 @@
import { ForbiddenError } from "@casl/ability";
import { createAppAuth } from "@octokit/auth-app";
import { Octokit } from "@octokit/rest";
import AWS from "aws-sdk";
import { SecretEncryptionAlgo, SecretKeyEncoding, TIntegrationAuths, TIntegrationAuthsInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TProjectPermission } from "@app/lib/types";
import { TGenericPermission, TProjectPermission } from "@app/lib/types";
import { TIntegrationDALFactory } from "../integration/integration-dal";
import { TKmsServiceFactory } from "../kms/kms-service";
@@ -16,11 +18,13 @@ import { KmsDataKey } from "../kms/kms-types";
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
import { getApps } from "./integration-app-list";
import { TIntegrationAuthDALFactory } from "./integration-auth-dal";
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
import {
TBitbucketWorkspace,
TChecklyGroups,
TDeleteIntegrationAuthByIdDTO,
TDeleteIntegrationAuthsDTO,
TDuplicateGithubIntegrationAuthDTO,
TGetIntegrationAuthDTO,
TGetIntegrationAuthTeamCityBuildConfigDTO,
THerokuPipelineCoupling,
@@ -86,9 +90,27 @@ export const integrationAuthServiceFactory = ({
return authorizations;
};
const listOrgIntegrationAuth = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGenericPermission) => {
const authorizations = await integrationAuthDAL.getByOrg(actorOrgId as string);
return Promise.all(
authorizations.filter(async (auth) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
auth.projectId,
actorAuthMethod,
actorOrgId
);
return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
})
);
};
const getIntegrationAuth = async ({ actor, id, actorId, actorAuthMethod, actorOrgId }: TGetIntegrationAuthDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -109,7 +131,8 @@ export const integrationAuthServiceFactory = ({
actorAuthMethod,
integration,
url,
code
code,
installationId
}: TOauthExchangeDTO) => {
if (!Object.values(Integrations).includes(integration as Integrations))
throw new BadRequestError({ message: "Invalid integration" });
@@ -123,7 +146,7 @@ export const integrationAuthServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
const tokenExchange = await exchangeCode({ integration, code, url });
const tokenExchange = await exchangeCode({ integration, code, url, installationId });
const updateDoc: TIntegrationAuthsInsert = {
projectId,
integration,
@@ -141,6 +164,16 @@ export const integrationAuthServiceFactory = ({
updateDoc.metadata = {
authMethod: "oauth2"
};
} else if (integration === Integrations.GITHUB && installationId) {
updateDoc.metadata = {
installationId,
installationName: tokenExchange.installationName,
authMethod: "app"
};
}
if (installationId && integration === Integrations.GITHUB) {
return integrationAuthDAL.create(updateDoc);
}
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(projectId);
@@ -162,7 +195,7 @@ export const integrationAuthServiceFactory = ({
updateDoc.encryptedAccess = accessToken;
}
} else {
if (!botKey) throw new NotFoundError({ message: "Project bot key not found" });
if (!botKey) throw new NotFoundError({ message: `Project bot key for project with ID '${projectId}' not found` });
if (tokenExchange.refreshToken) {
const refreshEncToken = encryptSymmetric128BitHexKeyUTF8(tokenExchange.refreshToken, botKey);
updateDoc.refreshIV = refreshEncToken.iv;
@@ -176,12 +209,23 @@ export const integrationAuthServiceFactory = ({
updateDoc.accessCiphertext = accessEncToken.ciphertext;
}
}
return integrationAuthDAL.transaction(async (tx) => {
const doc = await integrationAuthDAL.findOne({ projectId, integration }, tx);
if (!doc) {
const integrationAuths = await integrationAuthDAL.find({ projectId, integration }, { tx });
let existingIntegrationAuth: TIntegrationAuths | undefined;
// we need to ensure that the integration auth that we use for Github is actually Oauth
if (integration === Integrations.GITHUB) {
existingIntegrationAuth = integrationAuths.find((integAuth) => !integAuth.metadata);
} else {
[existingIntegrationAuth] = integrationAuths;
}
if (!existingIntegrationAuth) {
return integrationAuthDAL.create(updateDoc, tx);
}
return integrationAuthDAL.updateById(doc.id, updateDoc, tx);
return integrationAuthDAL.updateById(existingIntegrationAuth.id, updateDoc, tx);
});
};
@@ -273,7 +317,7 @@ export const integrationAuthServiceFactory = ({
}
}
} else {
if (!botKey) throw new NotFoundError({ message: "Project bot key not found" });
if (!botKey) throw new NotFoundError({ message: `Project bot key for project with ID '${projectId}' not found` });
if (refreshToken) {
const tokenDetails = await exchangeRefresh(
integration,
@@ -334,6 +378,13 @@ export const integrationAuthServiceFactory = ({
) {
return { accessToken: "", accessId: "" };
}
if (
integrationAuth.integration === Integrations.GITHUB &&
IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {}).installationId
) {
return { accessToken: "", accessId: "" };
}
if (shouldUseSecretV2Bridge) {
const { decryptor: secretManagerDecryptor, encryptor: secretManagerEncryptor } =
await kmsService.createCipherPairWithDataKey({
@@ -445,7 +496,7 @@ export const integrationAuthServiceFactory = ({
workspaceSlug
}: TIntegrationAuthAppsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -460,6 +511,7 @@ export const integrationAuthServiceFactory = ({
const { accessToken, accessId } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
const apps = await getApps({
integration: integrationAuth.integration,
integrationAuth,
accessToken,
accessId,
teamId,
@@ -478,7 +530,7 @@ export const integrationAuthServiceFactory = ({
id
}: TIntegrationAuthTeamsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -508,7 +560,7 @@ export const integrationAuthServiceFactory = ({
actorOrgId
}: TIntegrationAuthVercelBranchesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -549,7 +601,7 @@ export const integrationAuthServiceFactory = ({
accountId
}: TIntegrationAuthChecklyGroupsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -575,8 +627,9 @@ export const integrationAuthServiceFactory = ({
};
const getGithubOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthGithubOrgsDTO) => {
const appCfg = getConfig();
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -587,9 +640,44 @@ export const integrationAuthServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
const octokit = new Octokit({
let octokit: Octokit;
const { installationId } = (integrationAuth.metadata as TIntegrationAuthMetadata) || {};
if (installationId) {
octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
installationId
}
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const repos = await octokit.paginate("GET /installation/repositories", {
per_page: 100
});
const orgSet: Set<string> = new Set();
return repos
.filter((repo) => repo.owner.type === "Organization")
.map((repo) => ({
name: repo.owner.login,
orgId: String(repo.owner.id)
}))
.filter((org) => {
const isOrgProcessed = orgSet.has(org.orgId);
if (!isOrgProcessed) {
orgSet.add(org.orgId);
}
return !isOrgProcessed;
});
}
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
octokit = new Octokit({
auth: accessToken
});
@@ -598,7 +686,9 @@ export const integrationAuthServiceFactory = ({
"X-GitHub-Api-Version": "2022-11-28"
}
});
if (!data) return [];
if (!data) {
return [];
}
return data.map(({ login: name, id: orgId }) => ({ name, orgId: String(orgId) }));
};
@@ -613,7 +703,7 @@ export const integrationAuthServiceFactory = ({
repoName
}: TIntegrationAuthGithubEnvsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -626,9 +716,24 @@ export const integrationAuthServiceFactory = ({
const { shouldUseSecretV2Bridge, botKey } = await projectBotService.getBotKey(integrationAuth.projectId);
const { accessToken } = await getIntegrationAccessToken(integrationAuth, shouldUseSecretV2Bridge, botKey);
const octokit = new Octokit({
auth: accessToken
});
let octokit: Octokit;
const appCfg = getConfig();
const authMetadata = IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {});
if (authMetadata.installationId) {
octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
installationId: authMetadata.installationId
}
});
} else {
octokit = new Octokit({
auth: accessToken
});
}
const {
data: { environments }
@@ -645,7 +750,7 @@ export const integrationAuthServiceFactory = ({
const getQoveryOrgs = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TIntegrationAuthQoveryOrgsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -679,7 +784,7 @@ export const integrationAuthServiceFactory = ({
region
}: TIntegrationAuthAwsKmsKeyDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -737,7 +842,7 @@ export const integrationAuthServiceFactory = ({
orgId
}: TIntegrationAuthQoveryProjectDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -773,7 +878,7 @@ export const integrationAuthServiceFactory = ({
actorOrgId
}: TIntegrationAuthQoveryEnvironmentsDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -814,7 +919,7 @@ export const integrationAuthServiceFactory = ({
environmentId
}: TIntegrationAuthQoveryScopesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -854,7 +959,7 @@ export const integrationAuthServiceFactory = ({
environmentId
}: TIntegrationAuthQoveryScopesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -894,7 +999,7 @@ export const integrationAuthServiceFactory = ({
environmentId
}: TIntegrationAuthQoveryScopesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID ${id} not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -933,7 +1038,7 @@ export const integrationAuthServiceFactory = ({
actorOrgId
}: TIntegrationAuthHerokuPipelinesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -973,7 +1078,7 @@ export const integrationAuthServiceFactory = ({
appId
}: TIntegrationAuthRailwayEnvDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -1041,7 +1146,7 @@ export const integrationAuthServiceFactory = ({
appId
}: TIntegrationAuthRailwayServicesDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -1115,7 +1220,7 @@ export const integrationAuthServiceFactory = ({
id
}: TIntegrationAuthBitbucketWorkspaceDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -1164,7 +1269,7 @@ export const integrationAuthServiceFactory = ({
appId
}: TIntegrationAuthNorthflankSecretGroupDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -1232,7 +1337,7 @@ export const integrationAuthServiceFactory = ({
actor
}: TGetIntegrationAuthTeamCityBuildConfigDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -1294,7 +1399,7 @@ export const integrationAuthServiceFactory = ({
actorOrgId
}: TDeleteIntegrationAuthByIdDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) throw new NotFoundError({ message: "Failed to find integration" });
if (!integrationAuth) throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -1307,7 +1412,7 @@ export const integrationAuthServiceFactory = ({
const delIntegrationAuth = await integrationAuthDAL.transaction(async (tx) => {
const doc = await integrationAuthDAL.deleteById(integrationAuth.id, tx);
if (!doc) throw new NotFoundError({ message: "Faled to find integration" });
if (!doc) throw new NotFoundError({ message: `Integration auth with ID '${integrationAuth.id}' not found` });
await integrationDAL.delete({ integrationAuthId: doc.id }, tx);
return doc;
});
@@ -1315,8 +1420,58 @@ export const integrationAuthServiceFactory = ({
return delIntegrationAuth;
};
// At the moment, we only use this for Github App integration as it's a special case
const duplicateIntegrationAuth = async ({
id,
actorId,
actor,
actorAuthMethod,
actorOrgId,
projectId
}: TDuplicateGithubIntegrationAuthDTO) => {
const integrationAuth = await integrationAuthDAL.findById(id);
if (!integrationAuth) {
throw new NotFoundError({ message: `Integration auth with ID '${id}' not found` });
}
const { permission: sourcePermission } = await permissionService.getProjectPermission(
actor,
actorId,
integrationAuth.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(sourcePermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const { permission: targetPermission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(targetPermission).throwUnlessCan(
ProjectPermissionActions.Create,
ProjectPermissionSub.Integrations
);
const newIntegrationAuth: Omit<typeof integrationAuth, "id"> & { id?: string } = {
...integrationAuth,
id: undefined,
projectId
};
return integrationAuthDAL.create(newIntegrationAuth);
};
return {
listIntegrationAuthByProjectId,
listOrgIntegrationAuth,
getIntegrationOptions,
getIntegrationAuth,
oauthExchange,
@@ -1343,6 +1498,7 @@ export const integrationAuthServiceFactory = ({
getNorthFlankSecretGroups,
getTeamcityBuildConfigs,
getBitbucketWorkspaces,
getIntegrationAccessToken
getIntegrationAccessToken,
duplicateIntegrationAuth
};
};

View File

@@ -9,6 +9,7 @@ export type TOauthExchangeDTO = {
integration: string;
code: string;
url?: string;
installationId?: string;
} & TProjectPermission;
export type TSaveIntegrationAccessTokenDTO = {
@@ -107,6 +108,10 @@ export type TDeleteIntegrationAuthByIdDTO = {
id: string;
} & Omit<TProjectPermission, "projectId">;
export type TDuplicateGithubIntegrationAuthDTO = {
id: string;
} & TProjectPermission;
export type TGetIntegrationAuthTeamCityBuildConfigDTO = {
id: string;
appId: string;

View File

@@ -1,7 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { createAppAuth } from "@octokit/auth-app";
import { retry } from "@octokit/plugin-retry";
import { Octokit } from "@octokit/rest";
import { TIntegrationAuths, TIntegrations } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
@@ -15,6 +18,7 @@ import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
import { IntegrationAuthMetadataSchema, TIntegrationAuthMetadata } from "./integration-auth-schema";
import { TIntegrationAuthServiceFactory } from "./integration-auth-service";
import { Integrations } from "./integration-list";
@@ -154,10 +158,12 @@ const getIntegrationSecretsV1 = async (
export const deleteGithubSecrets = async ({
integration,
authMetadata,
secrets,
accessToken
}: {
integration: Omit<TIntegrations, "envId">;
authMetadata: TIntegrationAuthMetadata;
secrets: Record<string, boolean>;
accessToken: string;
}) => {
@@ -170,9 +176,23 @@ export const deleteGithubSecrets = async ({
}
const OctokitWithRetry = Octokit.plugin(retry);
const octokit = new OctokitWithRetry({
auth: accessToken
});
let octokit: Octokit;
const appCfg = getConfig();
if (authMetadata.installationId) {
octokit = new OctokitWithRetry({
authStrategy: createAppAuth,
auth: {
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
installationId: authMetadata.installationId
}
});
} else {
octokit = new OctokitWithRetry({
auth: accessToken
});
}
enum GithubScope {
Repo = "github-repo",
@@ -192,6 +212,7 @@ export const deleteGithubSecrets = async ({
break;
}
case GithubScope.Env: {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
encryptedGithubSecrets = (
await octokit.request("GET /repositories/{repository_id}/environments/{environment_name}/secrets", {
repository_id: Number(integration.appId),
@@ -293,7 +314,7 @@ export const deleteIntegrationSecrets = async ({
if (!folder) {
throw new NotFoundError({
message: "Folder not found."
message: `Folder with path '${integration.secretPath}' not found in environment with slug '${integration.environment.slug}'`
});
}
@@ -346,6 +367,7 @@ export const deleteIntegrationSecrets = async ({
case Integrations.GITHUB: {
await deleteGithubSecrets({
integration,
authMetadata: IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {}),
accessToken,
secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets
});

View File

@@ -96,7 +96,9 @@ export enum IntegrationUrls {
GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com",
GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`,
GCP_SERVICE_USAGE_URL = "https://serviceusage.googleapis.com",
GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform"
GCP_CLOUD_PLATFORM_SCOPE = "https://www.googleapis.com/auth/cloud-platform",
GITHUB_USER_INSTALLATIONS = "https://api.github.com/user/installations"
}
export const getIntegrationOptions = async () => {
@@ -138,6 +140,7 @@ export const getIntegrationOptions = async () => {
isAvailable: true,
type: "oauth",
clientId: appCfg.CLIENT_ID_GITHUB,
clientSlug: appCfg.CLIENT_SLUG_GITHUB_APP,
docsLink: ""
},
{

View File

@@ -19,6 +19,7 @@ import {
UpdateSecretCommand
} from "@aws-sdk/client-secrets-manager";
import { AssumeRoleCommand, STSClient } from "@aws-sdk/client-sts";
import { createAppAuth } from "@octokit/auth-app";
import { Octokit } from "@octokit/rest";
import AWS, { AWSError } from "aws-sdk";
import { AxiosError } from "axios";
@@ -36,6 +37,7 @@ import { TCreateManySecretsRawFn, TUpdateManySecretsRawFn } from "@app/services/
import { TIntegrationDALFactory } from "../integration/integration-dal";
import { IntegrationMetadataSchema } from "../integration/integration-schema";
import { IntegrationAuthMetadataSchema } from "./integration-auth-schema";
import { TIntegrationsWithEnvironment } from "./integration-auth-types";
import {
IntegrationInitialSyncBehavior,
@@ -728,7 +730,7 @@ const syncSecretsAWSParameterStore = async ({
awsParameterStoreSecretsObj[key].KeyId !== metadata.kmsKeyId;
// we ensure that the KMS key configured in the integration is applied for ALL parameters on AWS
if (shouldUpdateKms || awsParameterStoreSecretsObj[key].Value !== secrets[key].value) {
if (secrets[key].value && (shouldUpdateKms || awsParameterStoreSecretsObj[key].Value !== secrets[key].value)) {
await ssm
.putParameter({
Name: `${integration.path}${key}`,
@@ -789,7 +791,7 @@ const syncSecretsAWSParameterStore = async ({
logger.info(
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=2]`
);
if (!(key in secrets)) {
if (!(key in secrets) || !secrets[key].value) {
logger.info(
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=3]`
);
@@ -1542,11 +1544,13 @@ const syncSecretsNetlify = async ({
*/
const syncSecretsGitHub = async ({
integration,
integrationAuth,
secrets,
accessToken,
appendices
}: {
integration: TIntegrations;
integrationAuth: TIntegrationAuths;
secrets: Record<string, { value: string; comment?: string }>;
accessToken: string;
appendices?: { prefix: string; suffix: string };
@@ -1568,9 +1572,24 @@ const syncSecretsGitHub = async ({
selected_repositories_url?: string | undefined;
}
const octokit = new Octokit({
auth: accessToken
});
const authMetadata = IntegrationAuthMetadataSchema.parse(integrationAuth.metadata || {});
let octokit: Octokit;
const appCfg = getConfig();
if (authMetadata.installationId) {
octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: appCfg.CLIENT_APP_ID_GITHUB_APP,
privateKey: appCfg.CLIENT_PRIVATE_KEY_GITHUB_APP,
installationId: authMetadata.installationId
}
});
} else {
octokit = new Octokit({
auth: accessToken
});
}
enum GithubScope {
Repo = "github-repo",
@@ -4069,6 +4088,7 @@ export const syncIntegrationSecrets = async ({
case Integrations.GITHUB:
await syncSecretsGitHub({
integration,
integrationAuth,
secrets,
accessToken,
appendices

View File

@@ -2,7 +2,7 @@ import jwt from "jsonwebtoken";
import { getConfig } from "@app/lib/config/env";
import { request } from "@app/lib/config/request";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { BadRequestError, ForbiddenRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
import { Integrations, IntegrationUrls } from "./integration-list";
@@ -234,12 +234,73 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
};
};
const exchangeCodeGithub = async ({ code }: { code: string }) => {
const exchangeCodeGithub = async ({ code, installationId }: { code: string; installationId?: string }) => {
const appCfg = getConfig();
if (!appCfg.CLIENT_ID_GITHUB || !appCfg.CLIENT_SECRET_GITHUB) {
throw new BadRequestError({ message: "Missing client id and client secret" });
if (!installationId && (!appCfg.CLIENT_ID_GITHUB || !appCfg.CLIENT_SECRET_GITHUB)) {
throw new InternalServerError({ message: "Missing client id and client secret" });
}
if (installationId && (!appCfg.CLIENT_ID_GITHUB_APP || !appCfg.CLIENT_SECRET_GITHUB_APP)) {
throw new InternalServerError({
message: "Missing Github app client ID and client secret"
});
}
if (installationId) {
// handle app installations
const oauthRes = (
await request.get<ExchangeCodeGithubResponse>(IntegrationUrls.GITHUB_TOKEN_URL, {
params: {
client_id: appCfg.CLIENT_ID_GITHUB_APP,
client_secret: appCfg.CLIENT_SECRET_GITHUB_APP,
code,
redirect_uri: `${appCfg.SITE_URL}/integrations/github/oauth2/callback`
},
headers: {
Accept: "application/json",
"Accept-Encoding": "application/json"
}
})
).data;
// use access token to validate installation ID
const installationsRes = (
await request.get<{
installations: {
id: number;
account: {
login: string;
};
}[];
}>(IntegrationUrls.GITHUB_USER_INSTALLATIONS, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${oauthRes.access_token}`,
"Accept-Encoding": "application/json"
}
})
).data;
const matchingInstallation = installationsRes.installations.find(
(installation) => installation.id === +installationId
);
if (!matchingInstallation) {
throw new ForbiddenRequestError({
message: "User has no access to the provided installation"
});
}
return {
accessToken: "", // for github app integrations, we only need the installationID from the metadata
refreshToken: null,
accessExpiresAt: null,
installationName: matchingInstallation.account.login
};
}
// handle oauth github integration
const res = (
await request.get<ExchangeCodeGithubResponse>(IntegrationUrls.GITHUB_TOKEN_URL, {
params: {
@@ -346,6 +407,7 @@ type TExchangeReturn = {
url?: string;
teamId?: string;
accountId?: string;
installationName?: string;
};
/**
@@ -355,11 +417,13 @@ type TExchangeReturn = {
export const exchangeCode = async ({
integration,
code,
url
url,
installationId
}: {
integration: string;
code: string;
url?: string;
installationId?: string;
}): Promise<TExchangeReturn> => {
switch (integration) {
case Integrations.GCP_SECRET_MANAGER:
@@ -384,7 +448,8 @@ export const exchangeCode = async ({
});
case Integrations.GITHUB:
return exchangeCodeGithub({
code
code,
installationId
});
case Integrations.GITLAB:
return exchangeCodeGitlab({

View File

@@ -76,7 +76,8 @@ export const integrationServiceFactory = ({
targetEnvironmentId
}: TCreateIntegrationDTO) => {
const integrationAuth = await integrationAuthDAL.findById(integrationAuthId);
if (!integrationAuth) throw new NotFoundError({ message: "Integration auth not found" });
if (!integrationAuth)
throw new NotFoundError({ message: `Integration auth with ID '${integrationAuthId}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -93,7 +94,11 @@ export const integrationServiceFactory = ({
);
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
if (!folder) throw new NotFoundError({ message: "Folder path not found" });
if (!folder) {
throw new NotFoundError({
message: `Folder with path '${secretPath}' not found in environment with slug'${sourceEnvironment}'`
});
}
const integration = await integrationDAL.create({
envId: folder.envId,
@@ -145,7 +150,7 @@ export const integrationServiceFactory = ({
metadata
}: TUpdateIntegrationDTO) => {
const integration = await integrationDAL.findById(id);
if (!integration) throw new NotFoundError({ message: "Integration auth not found" });
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -167,7 +172,11 @@ export const integrationServiceFactory = ({
}
const folder = await folderDAL.findBySecretPath(integration.projectId, newEnvironment, newSecretPath);
if (!folder) throw new NotFoundError({ message: "Folder path not found" });
if (!folder) {
throw new NotFoundError({
message: `Folder with path '${newSecretPath}' not found in environment with slug '${newEnvironment}'`
});
}
const updatedIntegration = await integrationDAL.updateById(id, {
envId: folder.envId,
@@ -200,7 +209,7 @@ export const integrationServiceFactory = ({
if (!integration) {
throw new NotFoundError({
message: "Integration not found"
message: `Integration with ID '${id}' not found`
});
}
@@ -215,7 +224,7 @@ export const integrationServiceFactory = ({
if (!integration) {
throw new NotFoundError({
message: "Integration not found"
message: `Integration with ID '${id}' not found`
});
}
@@ -231,7 +240,7 @@ export const integrationServiceFactory = ({
shouldDeleteIntegrationSecrets
}: TDeleteIntegrationDTO) => {
const integration = await integrationDAL.findById(id);
if (!integration) throw new NotFoundError({ message: "Integration auth not found" });
if (!integration) throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -258,27 +267,7 @@ export const integrationServiceFactory = ({
});
}
const deletedIntegration = await integrationDAL.transaction(async (tx) => {
// delete integration
const deletedIntegrationResult = await integrationDAL.deleteById(id, tx);
// check if there are other integrations that share the same integration auth
const integrations = await integrationDAL.find(
{
integrationAuthId: integration.integrationAuthId
},
tx
);
if (integrations.length === 0) {
// no other integration shares the same integration auth
// -> delete the integration auth
await integrationAuthDAL.deleteById(integration.integrationAuthId, tx);
}
return deletedIntegrationResult;
});
const deletedIntegration = await integrationDAL.deleteById(id);
return { ...integration, ...deletedIntegration };
};
@@ -305,7 +294,7 @@ export const integrationServiceFactory = ({
const syncIntegration = async ({ id, actorId, actor, actorOrgId, actorAuthMethod }: TSyncIntegrationDTO) => {
const integration = await integrationDAL.findById(id);
if (!integration) {
throw new NotFoundError({ message: "Integration not found" });
throw new NotFoundError({ message: `Integration with ID '${id}' not found` });
}
const { permission } = await permissionService.getProjectPermission(

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