1
0
mirror of https://github.com/Infisical/infisical.git synced 2025-03-21 04:39:28 +00:00

Compare commits

..

346 Commits

Author SHA1 Message Date
53983d13f3 Fix: Delete access approval requests 2024-05-06 10:08:04 +02:00
5d9e47aec6 Fix: Migration timestamps and cleanup 2024-05-04 08:01:45 +02:00
be968be813 Fix: Fixed migration timestamp to match main 2024-05-04 07:53:22 +02:00
dc60d59e2e Merge branch 'daniel/fix-db-ref' of https://github.com/Infisical/infisical into daniel/fix-db-ref 2024-05-04 07:41:49 +02:00
e3f48e72b0 Feat: Secret approval deletion and more final changes 2024-05-04 07:41:44 +02:00
3c6b7aee9a Fix: Seperate groups / project users additional privileges 2024-05-04 07:41:44 +02:00
a183e94ff4 Schemas 2024-05-04 07:41:44 +02:00
b54e780443 Fix: Remove group-specific fields 2024-05-04 07:41:44 +02:00
5376bb72b3 Feat: Refactor to support groups 2024-05-04 07:41:44 +02:00
56d0d59ddc Feat: Group user additional privileges 2024-05-04 07:41:44 +02:00
ef9d4a4eee Update permission-dal.ts 2024-05-04 07:41:44 +02:00
873c6eea18 Fix: Updating approvers, now based on user ID 2024-05-04 07:41:44 +02:00
8d8e0bb794 Update project-membership-service.ts 2024-05-04 07:41:44 +02:00
348cf1c50c Fix: Cleanup secret & access approvals on project membership deletion 2024-05-04 07:41:44 +02:00
05669efdd8 Update group-project-service.ts 2024-05-04 07:41:44 +02:00
c302630551 Fix: Add project ID 2024-05-04 07:41:44 +02:00
2a4c9100be Fix ambiguous field 2024-05-04 07:41:44 +02:00
9ced5717ac Fix: Cleanup secret & access approvals on user group unassignment 2024-05-04 07:41:44 +02:00
b2f2541d0b Fix: Cleanup secret & access approvals on group deletion 2024-05-04 07:41:44 +02:00
3a9ad8d306 Update access-approval-request-service.ts 2024-05-04 07:41:44 +02:00
10207d03dd Fix: Delete potential privileges associated with access request on deletion 2024-05-04 07:41:44 +02:00
832dd62158 Schema update 2024-05-04 07:41:44 +02:00
df29f3499f Update 20240429175301_fix-db-reference-for-groups-and-project-memberships.ts 2024-05-04 07:41:44 +02:00
0d4c05f537 Update 20240429172301_access_approval_requests.ts 2024-05-04 07:41:44 +02:00
fb0407fec8 Fix: Duplicate users when user has both group access & project access 2024-05-04 07:41:44 +02:00
7d899463b4 Feat: Cleanup on group user removal 2024-05-04 07:41:44 +02:00
cfaf076352 Fix: Cleanup on membership delete & group disconnection 2024-05-04 07:41:44 +02:00
a875489172 Update access-approval-policy-router.ts 2024-05-04 07:41:44 +02:00
8634f8348b Chore: Cleanup 2024-05-04 07:41:44 +02:00
ad5b16d448 Type error 2024-05-04 07:41:44 +02:00
62e6acb7dc Update audit-log-types.ts 2024-05-04 07:41:44 +02:00
bf50eed8b0 Fix: Type errors 2024-05-04 07:41:44 +02:00
dcd69b5d99 Chore: Generate accurate schemas 2024-05-04 07:41:44 +02:00
dda98a0036 Removed comments 2024-05-04 07:41:44 +02:00
24ab66f61f Fix: Audit Log support for secret approval requests 2024-05-04 07:41:44 +02:00
9b97afad1c Update index.tsx 2024-05-04 07:41:44 +02:00
dca3832fd4 Update project-membership-types.ts 2024-05-04 07:41:44 +02:00
c3458a9d34 Update audit logs 2024-05-04 07:41:44 +02:00
76d371f13c Fix: Remove project membership dependency 2024-05-04 07:41:44 +02:00
7e9bcc5ce1 Chore: Spelling 2024-05-04 07:41:44 +02:00
029f2fa3af Fix: Refactor to use user ID's 2024-05-04 07:41:44 +02:00
0e9b2a8045 Update secret-approval-policy-service.ts 2024-05-04 07:41:44 +02:00
514cf07ba5 Fix: Refactor to use user ID's 2024-05-04 07:41:44 +02:00
55efc58566 Fix: Spelling and refactor to use user ID's 2024-05-04 07:41:44 +02:00
9b03f4984a Feat: Delete access approval request 2024-05-04 07:41:44 +02:00
a04f938b6c Update queries.tsx 2024-05-04 07:41:44 +02:00
596a22e9eb Fix: Update types 2024-05-04 07:41:44 +02:00
2ea01537c0 Update types.ts 2024-05-04 07:41:44 +02:00
ab57c658a8 Feat: Include group members option 2024-05-04 07:41:44 +02:00
89030c92c7 Fix: Secret approval refactor to user ID 2024-05-04 07:41:44 +02:00
d842aef714 Fix: Type errors 2024-05-04 07:41:44 +02:00
53089e26b9 Fix: Use user ID's instead of project memberships 2024-05-04 07:41:44 +02:00
be890b4189 Fix: Use user ID's instead of project memberships 2024-05-04 07:41:44 +02:00
cd0f126cf2 Update group-project-dal.ts 2024-05-04 07:41:44 +02:00
a092b9a19f Feat: Get group members 2024-05-04 07:41:44 +02:00
2f043849c9 Feat: Group support for project specific operations 2024-05-04 07:41:44 +02:00
d15fa9f176 Update index.ts 2024-05-04 07:41:44 +02:00
61508ec90a Feat: Delete access request 2024-05-04 07:41:44 +02:00
9d84bfa69e Feat: Additional priv support for groups 2024-05-04 07:41:44 +02:00
6a1d465778 Fix: Audit log support for user ID 2024-05-04 07:41:44 +02:00
5eff705486 Feat: Fix Groups for project membership specific operations 2024-05-04 07:41:44 +02:00
cd532bc20d Fix: Refactor to use new user ID field 2024-05-04 07:41:44 +02:00
18cdaaf024 Fix: Update for user ID 2024-05-04 07:41:44 +02:00
74e1dbdf9b Feat: Fix Groups for project membership specific operations (Manually modified) 2024-05-04 07:41:44 +02:00
64ab75748c Feat: Fix Groups for project membership specific operations 2024-05-04 07:41:44 +02:00
f7b689158d Update SpecificPrivilegeSection.tsx 2024-05-04 07:40:37 +02:00
19b9a31f0b Draft 2024-05-04 07:40:37 +02:00
0568cdcec6 Update instance recognition of offline license 2024-05-04 07:40:37 +02:00
a4bc459576 Fix: Duplicate access request check 2024-05-04 07:40:37 +02:00
b0b73acc21 Update SecretApprovalPage.tsx 2024-05-04 07:40:36 +02:00
07d66cbb65 Fix: Moved from email to username 2024-05-04 07:40:36 +02:00
ee97782860 Cleanup 2024-05-04 07:40:36 +02:00
c856de534b Fix: Move standalone components to individual files 2024-05-04 07:40:36 +02:00
eefd71f4cc Chore: Remove unused files 2024-05-04 07:40:36 +02:00
77e9609d0c Fix: Use username instead of email 2024-05-04 07:40:36 +02:00
afbbe5b7ba Fix: Columns 2024-05-04 07:40:36 +02:00
54d5cdedab Fix: Use username instead of email 2024-05-04 07:40:36 +02:00
9e12935a9f Feat: Badge component 2024-05-04 07:40:36 +02:00
101fa56d83 Fix: Moved Divider to v2 2024-05-04 07:40:36 +02:00
9bceb99110 Update index.ts 2024-05-04 07:40:36 +02:00
ca7a0a73be Fix: Pick 2024-05-04 07:40:36 +02:00
3632361f3c Chore: Moved verifyApprovers 2024-05-04 07:40:36 +02:00
f5c0274844 Fix: Make verifyApprovers independent on memberships 2024-05-04 07:40:36 +02:00
36a11387dd Fix: Made API endpoints more REST compliant 2024-05-04 07:40:36 +02:00
a82c94472a Chore: Cleaned up models 2024-05-04 07:40:36 +02:00
508f9610ca Fix: Improved migrations 2024-05-04 07:40:36 +02:00
59065c0648 Delete access-approval-request-secret-dal.ts 2024-05-04 07:40:36 +02:00
6443c94283 Fix: Don't display requested by when user has no access to read workspace members 2024-05-04 07:40:36 +02:00
26611881bc Fix: Don't display requested by when user has no access to read workspace members 2024-05-04 07:40:36 +02:00
2852989ac1 Fix: Add tooltip for clarity and fix wording 2024-05-04 07:40:36 +02:00
124bb7c205 Fix: Requesting approvals on previously rejected resources 2024-05-04 07:40:36 +02:00
697445cb1f Fix: Sort by createdAt 2024-05-04 07:40:36 +02:00
04108907ba Migration improvements 2024-05-04 07:40:36 +02:00
411cac2a31 Fixed bugs 2024-05-04 07:40:36 +02:00
afb9920fca Update SecretApprovalPage.tsx 2024-05-04 07:40:36 +02:00
ccf99d2465 Fix: Rebase errors 2024-05-04 07:40:36 +02:00
bca84f74c5 Removed unnessecary types 2024-05-04 07:40:36 +02:00
6c93973db7 Update AccessApprovalRequest.tsx 2024-05-04 07:40:36 +02:00
8d3f8c94fb Update AccessApprovalRequest.tsx 2024-05-04 07:40:36 +02:00
2eeb7dbc41 Update AccessApprovalRequest.tsx 2024-05-04 07:40:36 +02:00
f18624d2e4 style changes 2024-05-04 07:40:36 +02:00
42a49da17b Update licence-fns.ts 2024-05-04 07:40:36 +02:00
5d87ce866c Update SpecificPrivilegeSection.tsx 2024-05-04 07:40:36 +02:00
02d7f90ec2 Update generate-schema-types.ts 2024-05-04 07:40:36 +02:00
03564fc59b Update SecretApprovalPage.tsx 2024-05-04 07:40:36 +02:00
8669f5c39a Fix: Added support for request access 2024-05-04 07:40:36 +02:00
c2bd2e6963 Fix: Remove redundant code 2024-05-04 07:40:36 +02:00
eb23d114a2 Fix: Validate approvers access 2024-05-04 07:40:36 +02:00
dec2cd465b Feat: Request access (new routes) 2024-05-04 07:40:36 +02:00
4cdec49751 Feat: Request Access (migrations) 2024-05-04 07:40:36 +02:00
43967ef848 Feat: Request access 2024-05-04 07:40:36 +02:00
55046d4144 Draft 2024-05-04 07:40:36 +02:00
124acfd279 Fix: Multiple approvers acceptance bug 2024-05-04 07:40:36 +02:00
62e12269b8 Fix: Rename change -> secret 2024-05-04 07:40:36 +02:00
f03d8b718e Style: Fix styling 2024-05-04 07:40:36 +02:00
acf13df0f3 Capitalization 2024-05-04 07:40:36 +02:00
cb8ec57177 Removed unnessecary types 2024-05-04 07:40:36 +02:00
b543f2ce50 Remove unnessecary types and projectMembershipid 2024-05-04 07:40:36 +02:00
f852e629ef Renaming 2024-05-04 07:40:36 +02:00
58b74d97bb Update smtp-service.ts 2024-05-04 07:40:36 +02:00
ba12aab65a Feat: Find users by project membership ID's 2024-05-04 07:40:36 +02:00
952c4a3931 Feat: access request emails 2024-05-04 07:40:36 +02:00
4a1bae07ca Update index.ts 2024-05-04 07:40:36 +02:00
c24f72435a Update access-approval-request-types.ts 2024-05-04 07:40:36 +02:00
4bf378c28d Feat: Send emails for access requests 2024-05-04 07:40:36 +02:00
407c8e17d3 Feat: Request access, extract permission details 2024-05-04 07:40:36 +02:00
67b7fb819a Fix: Security vulnurbility making it possible to spoof env & secret path requested. 2024-05-04 07:40:36 +02:00
edfccb2ae2 Update AccessApprovalRequest.tsx 2024-05-04 07:40:36 +02:00
df8dc43bcf Update AccessApprovalRequest.tsx 2024-05-04 07:40:36 +02:00
0d610f2644 Update AccessApprovalRequest.tsx 2024-05-04 07:40:36 +02:00
a422d211fe style changes 2024-05-04 07:40:36 +02:00
f66d5e3d28 Fix: Status filtering & query invalidation 2024-05-04 07:40:36 +02:00
2c4e951fe2 Fix: Access request query invalidation 2024-05-04 07:40:36 +02:00
e23d2dff64 fix privilegeId issue 2024-05-04 07:40:35 +02:00
e7de6ad5d9 Fix: Request access permissions 2024-05-04 07:40:35 +02:00
ca0d79d664 Update licence-fns.ts 2024-05-04 07:40:35 +02:00
adc0552df0 Add count 2024-05-04 07:40:35 +02:00
cff79e7c8c Fix: Don't allow users to request access to the same resource with same permissions multiple times 2024-05-04 07:40:35 +02:00
450e653005 Removed unused parameter 2024-05-04 07:40:35 +02:00
c866e55d1b Removed logs 2024-05-04 07:40:35 +02:00
d66c2a85f4 Removed logs 2024-05-04 07:40:35 +02:00
3b8c0a5cb1 Update SpecificPrivilegeSection.tsx 2024-05-04 07:40:35 +02:00
b77f0fed45 Update generate-schema-types.ts 2024-05-04 07:40:35 +02:00
e8bc47b573 Update SecretApprovalPage.tsx 2024-05-04 07:40:35 +02:00
c6785eff3a Fix: Minor fixes 2024-05-04 07:40:35 +02:00
bc1a9055ee Create index.tsx 2024-05-04 07:40:35 +02:00
dbe1f2bcff Feat: Request access 2024-05-04 07:40:35 +02:00
15107ebfaa Feat: Request access 2024-05-04 07:40:35 +02:00
435a395a15 Feat: Request access 2024-05-04 07:40:35 +02:00
fe829af054 Fix: Move to project slug 2024-05-04 07:40:35 +02:00
bd9dc44a69 Fix: Move to project slug 2024-05-04 07:40:35 +02:00
765dd84d19 Fix: Move to project slug 2024-05-04 07:40:35 +02:00
ac100e17f4 Fix: Added support for request access 2024-05-04 07:40:35 +02:00
e349f9aa3b Feat: Request access 2024-05-04 07:40:35 +02:00
29c3c41ebb Update index.tsx 2024-05-04 07:40:35 +02:00
e4af0759b8 Fix: Improve disabled Select 2024-05-04 07:40:35 +02:00
c681774709 Fix: Access Request setup 2024-05-04 07:40:35 +02:00
f63f2d9c69 Fix: Danger color not working on disabled buttons 2024-05-04 07:40:35 +02:00
044662901a Fix: Remove redundant code 2024-05-04 07:40:35 +02:00
8cdb2082d9 Feat: Request Access 2024-05-04 07:40:35 +02:00
52d0f5e1be Feat: Request access 2024-05-04 07:40:35 +02:00
be1e7be0d5 Feat: Request access 2024-05-04 07:40:35 +02:00
e1b0bc1b97 Fix: Types mismatch 2024-05-04 07:40:35 +02:00
f05d1b9d95 Fix: Validate approvers access 2024-05-04 07:40:35 +02:00
fa2bd6a75e Feat: Request access 2024-05-04 07:40:35 +02:00
2402ce2a12 Fix: Access Approval Policy DAL bugs 2024-05-04 07:40:35 +02:00
f770a18d41 Feat: Request access (new routes) 2024-05-04 07:40:35 +02:00
8ab7470f74 Fix: Move to project slug 2024-05-04 07:40:35 +02:00
eb56c23db1 Feat: Request access (models) 2024-05-04 07:40:35 +02:00
14812adade Feat: Request Access (migrations) 2024-05-04 07:40:35 +02:00
99b1efffc7 Feat: Request access 2024-05-04 07:40:35 +02:00
af6189c82b Feat: Request access 2024-05-04 07:40:35 +02:00
b6ca18af5d Fix: Remove logs 2024-05-04 07:40:26 +02:00
ee7bb6d60d Feat: Request Access 2024-05-04 07:40:26 +02:00
bfde867ba7 Draft 2024-05-04 07:40:12 +02:00
1a20f3148c Feat: Secret approval deletion and more final changes 2024-05-04 07:38:17 +02:00
ce5b14222f Fix: Seperate groups / project users additional privileges 2024-05-04 07:38:17 +02:00
74a43d55f7 Schemas 2024-05-04 07:38:17 +02:00
85cce4274e Fix: Remove group-specific fields 2024-05-04 07:38:17 +02:00
9eb88836e9 Feat: Refactor to support groups 2024-05-04 07:38:17 +02:00
d6c9658747 Feat: Group user additional privileges 2024-05-04 07:38:17 +02:00
f9967c0cc8 Update permission-dal.ts 2024-05-04 07:38:17 +02:00
bd8dfe4089 Fix: Updating approvers, now based on user ID 2024-05-04 07:38:17 +02:00
03fcaadab2 Update project-membership-service.ts 2024-05-04 07:38:17 +02:00
d3a0a84815 Fix: Cleanup secret & access approvals on project membership deletion 2024-05-04 07:38:17 +02:00
49ae146470 Update group-project-service.ts 2024-05-04 07:38:17 +02:00
f73b362c84 Fix: Add project ID 2024-05-04 07:38:17 +02:00
d9043fa9e0 Fix ambiguous field 2024-05-04 07:38:17 +02:00
98f6dc8df9 Fix: Cleanup secret & access approvals on user group unassignment 2024-05-04 07:38:17 +02:00
12c67d921d Fix: Cleanup secret & access approvals on group deletion 2024-05-04 07:38:17 +02:00
7dea2ba916 Update access-approval-request-service.ts 2024-05-04 07:38:17 +02:00
ace27a3605 Fix: Delete potential privileges associated with access request on deletion 2024-05-04 07:38:17 +02:00
e85ea1a458 Schema update 2024-05-04 07:38:17 +02:00
fb16464fda Update 20240429175301_fix-db-reference-for-groups-and-project-memberships.ts 2024-05-04 07:38:17 +02:00
c6b636bb42 Update 20240429172301_access_approval_requests.ts 2024-05-04 07:38:17 +02:00
034ac68b58 Fix: Duplicate users when user has both group access & project access 2024-05-04 07:38:17 +02:00
33e2c52f14 Feat: Cleanup on group user removal 2024-05-04 07:38:17 +02:00
b435a06a92 Fix: Cleanup on membership delete & group disconnection 2024-05-04 07:38:17 +02:00
48c23db3f9 Update access-approval-policy-router.ts 2024-05-04 07:38:17 +02:00
3159972ec3 Chore: Cleanup 2024-05-04 07:38:17 +02:00
8a5c293a6e Type error 2024-05-04 07:38:17 +02:00
1d9c18d155 Update audit-log-types.ts 2024-05-04 07:38:17 +02:00
13945bb31d Fix: Type errors 2024-05-04 07:38:17 +02:00
9df9197cac Chore: Generate accurate schemas 2024-05-04 07:38:17 +02:00
3809729e31 Removed comments 2024-05-04 07:38:17 +02:00
03d29a4afc Fix: Audit Log support for secret approval requests 2024-05-04 07:38:17 +02:00
a4264335fe Update index.tsx 2024-05-04 07:38:16 +02:00
7752bab0f0 Update project-membership-types.ts 2024-05-04 07:38:16 +02:00
56a20dc397 Update audit logs 2024-05-04 07:38:16 +02:00
6f79d8bb6c Fix: Remove project membership dependency 2024-05-04 07:38:16 +02:00
044ac01100 Chore: Spelling 2024-05-04 07:38:16 +02:00
641c0308f9 Fix: Refactor to use user ID's 2024-05-04 07:38:16 +02:00
ecfb833797 Update secret-approval-policy-service.ts 2024-05-04 07:38:16 +02:00
256f14cf6a Fix: Refactor to use user ID's 2024-05-04 07:38:16 +02:00
32c28227b2 Fix: Spelling and refactor to use user ID's 2024-05-04 07:38:16 +02:00
3be6402727 Feat: Delete access approval request 2024-05-04 07:38:16 +02:00
90c09c64cb Update queries.tsx 2024-05-04 07:38:16 +02:00
d0da69b999 Fix: Update types 2024-05-04 07:38:16 +02:00
7fb3730b22 Update types.ts 2024-05-04 07:38:16 +02:00
49e154ddd1 Feat: Include group members option 2024-05-04 07:38:16 +02:00
3742976bcb Fix: Secret approval refactor to user ID 2024-05-04 07:38:16 +02:00
5695137f24 Fix: Type errors 2024-05-04 07:38:16 +02:00
13d7cfd41b Fix: Use user ID's instead of project memberships 2024-05-04 07:38:16 +02:00
81fc5d3c18 Fix: Use user ID's instead of project memberships 2024-05-04 07:38:16 +02:00
8e8f44895d Update group-project-dal.ts 2024-05-04 07:38:16 +02:00
45570490a0 Feat: Get group members 2024-05-04 07:38:16 +02:00
1add5d6a24 Feat: Group support for project specific operations 2024-05-04 07:38:16 +02:00
7ac0536236 Update index.ts 2024-05-04 07:38:16 +02:00
89e9f46ae5 Feat: Delete access request 2024-05-04 07:38:16 +02:00
e3728b8a61 Feat: Additional priv support for groups 2024-05-04 07:38:16 +02:00
92bbabde3c Fix: Audit log support for user ID 2024-05-04 07:38:16 +02:00
11b4c5381a Feat: Fix Groups for project membership specific operations 2024-05-04 07:38:16 +02:00
97496c1b3c Fix: Refactor to use new user ID field 2024-05-04 07:38:16 +02:00
3cac1acf08 Fix: Update for user ID 2024-05-04 07:38:16 +02:00
c3756b8cc0 Feat: Fix Groups for project membership specific operations (Manually modified) 2024-05-04 07:38:16 +02:00
8678c79c02 Feat: Fix Groups for project membership specific operations 2024-05-04 07:38:16 +02:00
d2f010d17d Update SpecificPrivilegeSection.tsx 2024-05-04 07:38:16 +02:00
5c8d5e8430 Draft 2024-05-04 07:38:16 +02:00
7c8d99875a Update instance recognition of offline license 2024-05-04 07:38:16 +02:00
ab30b0803f Fix: Duplicate access request check 2024-05-04 07:38:16 +02:00
e2d68f07d1 Update SecretApprovalPage.tsx 2024-05-04 07:38:16 +02:00
07ced66538 Fix: Moved from email to username 2024-05-04 07:38:16 +02:00
9cb0ec231b Cleanup 2024-05-04 07:38:16 +02:00
8b169b2b9e Fix: Move standalone components to individual files 2024-05-04 07:38:16 +02:00
b9c02264c7 Chore: Remove unused files 2024-05-04 07:38:16 +02:00
9f96a9d188 Fix: Use username instead of email 2024-05-04 07:38:16 +02:00
55f232a642 Fix: Columns 2024-05-04 07:38:16 +02:00
34ff65d09c Fix: Use username instead of email 2024-05-04 07:38:16 +02:00
fe38c79f68 Feat: Badge component 2024-05-04 07:38:16 +02:00
a8aecc378b Fix: Moved Divider to v2 2024-05-04 07:38:16 +02:00
9ce7161aea Update index.ts 2024-05-04 07:38:16 +02:00
1951ca723c Fix: Pick 2024-05-04 07:38:16 +02:00
416f85f7e2 Chore: Moved verifyApprovers 2024-05-04 07:38:16 +02:00
75bef6fc8b Fix: Make verifyApprovers independent on memberships 2024-05-04 07:38:16 +02:00
5fa6e8bcf2 Fix: Made API endpoints more REST compliant 2024-05-04 07:38:16 +02:00
f4a5d9c391 Chore: Cleaned up models 2024-05-04 07:38:16 +02:00
3c6b976d8f Fix: Improved migrations 2024-05-04 07:38:16 +02:00
787d2287a0 Delete access-approval-request-secret-dal.ts 2024-05-04 07:38:16 +02:00
92f73d66f0 Fix: Don't display requested by when user has no access to read workspace members 2024-05-04 07:38:16 +02:00
3cd8670064 Fix: Don't display requested by when user has no access to read workspace members 2024-05-04 07:38:15 +02:00
4e3dd15d67 Fix: Add tooltip for clarity and fix wording 2024-05-04 07:38:15 +02:00
4c97ba1221 Fix: Requesting approvals on previously rejected resources 2024-05-04 07:38:15 +02:00
b89128fb32 Fix: Sort by createdAt 2024-05-04 07:38:15 +02:00
c788c0cb80 Migration improvements 2024-05-04 07:38:15 +02:00
e3fde17622 Fixed bugs 2024-05-04 07:38:15 +02:00
2eb9f30ef5 Update SecretApprovalPage.tsx 2024-05-04 07:38:15 +02:00
9432f3ce4a Fix: Rebase errors 2024-05-04 07:38:15 +02:00
5393afbd05 Removed unnessecary types 2024-05-04 07:38:15 +02:00
cb304d9a10 Update AccessApprovalRequest.tsx 2024-05-04 07:38:15 +02:00
a5f29db670 Update AccessApprovalRequest.tsx 2024-05-04 07:38:15 +02:00
009b49685c Update AccessApprovalRequest.tsx 2024-05-04 07:38:15 +02:00
90d3f4d643 style changes 2024-05-04 07:38:15 +02:00
cc08d31300 Update licence-fns.ts 2024-05-04 07:38:15 +02:00
71deb7c62a Update SpecificPrivilegeSection.tsx 2024-05-04 07:38:15 +02:00
efec1c0a96 Update generate-schema-types.ts 2024-05-04 07:38:15 +02:00
efa4b7a4b6 Update SecretApprovalPage.tsx 2024-05-04 07:38:15 +02:00
65e0077d6c Fix: Added support for request access 2024-05-04 07:38:15 +02:00
1be311ffd9 Fix: Remove redundant code 2024-05-04 07:38:15 +02:00
3994962d0b Fix: Validate approvers access 2024-05-04 07:38:15 +02:00
0b9334f34c Feat: Request access (new routes) 2024-05-04 07:38:15 +02:00
2b4396547d Feat: Request Access (migrations) 2024-05-04 07:38:15 +02:00
761ec8dcc0 Feat: Request access 2024-05-04 07:38:15 +02:00
56e69bc5e9 Draft 2024-05-04 07:38:15 +02:00
067faef6a2 Fix: Multiple approvers acceptance bug 2024-05-04 07:38:15 +02:00
026b934a87 Fix: Rename change -> secret 2024-05-04 07:38:15 +02:00
90eef0495e Style: Fix styling 2024-05-04 07:38:15 +02:00
119fe97b14 Capitalization 2024-05-04 07:38:15 +02:00
dab3daee86 Removed unnessecary types 2024-05-04 07:38:15 +02:00
f2e344c11d Remove unnessecary types and projectMembershipid 2024-05-04 07:38:15 +02:00
df1a879e73 Renaming 2024-05-04 07:38:15 +02:00
fb21d4e13d Update smtp-service.ts 2024-05-04 07:38:15 +02:00
ca6f50a257 Feat: Find users by project membership ID's 2024-05-04 07:38:15 +02:00
26f0adbf7e Feat: access request emails 2024-05-04 07:38:15 +02:00
456d9ca5ce Update index.ts 2024-05-04 07:38:15 +02:00
e652fd962c Update access-approval-request-types.ts 2024-05-04 07:38:15 +02:00
bc16484f3f Feat: Send emails for access requests 2024-05-04 07:38:15 +02:00
4e87cc7c28 Feat: Request access, extract permission details 2024-05-04 07:38:15 +02:00
d9e2b99338 Fix: Security vulnurbility making it possible to spoof env & secret path requested. 2024-05-04 07:38:15 +02:00
9bac996c7a Update AccessApprovalRequest.tsx 2024-05-04 07:38:15 +02:00
089d57ea59 Update AccessApprovalRequest.tsx 2024-05-04 07:38:15 +02:00
14a17d638d Update AccessApprovalRequest.tsx 2024-05-04 07:38:15 +02:00
5d9755b332 style changes 2024-05-04 07:38:15 +02:00
4f6b73518e Fix: Status filtering & query invalidation 2024-05-04 07:38:15 +02:00
2f4965659c Fix: Access request query invalidation 2024-05-04 07:38:15 +02:00
2dc12693b0 fix privilegeId issue 2024-05-04 07:38:15 +02:00
5305139a55 Fix: Request access permissions 2024-05-04 07:38:15 +02:00
db72c07e81 Update licence-fns.ts 2024-05-04 07:38:15 +02:00
2f3ae5429a Add count 2024-05-04 07:38:15 +02:00
56e216c37c Fix: Don't allow users to request access to the same resource with same permissions multiple times 2024-05-04 07:38:14 +02:00
8db3544885 Removed unused parameter 2024-05-04 07:38:14 +02:00
f196c6a0ce Removed logs 2024-05-04 07:38:14 +02:00
246eecc23c Removed logs 2024-05-04 07:38:14 +02:00
013b744706 Update SpecificPrivilegeSection.tsx 2024-05-04 07:38:14 +02:00
fe68328aeb Update generate-schema-types.ts 2024-05-04 07:38:14 +02:00
1dcfd14431 Update SecretApprovalPage.tsx 2024-05-04 07:38:14 +02:00
f232f00f77 Fix: Minor fixes 2024-05-04 07:38:14 +02:00
82517477cb Create index.tsx 2024-05-04 07:38:14 +02:00
4e149cce81 Feat: Request access 2024-05-04 07:38:14 +02:00
5894cb4049 Feat: Request access 2024-05-04 07:38:14 +02:00
4938dda303 Feat: Request access 2024-05-04 07:38:14 +02:00
62112447a6 Fix: Move to project slug 2024-05-04 07:38:14 +02:00
bead911e0f Fix: Move to project slug 2024-05-04 07:38:14 +02:00
49987ca1e5 Fix: Move to project slug 2024-05-04 07:38:14 +02:00
cec083aa9b Fix: Added support for request access 2024-05-04 07:38:14 +02:00
9146079317 Feat: Request access 2024-05-04 07:38:14 +02:00
243ffc9904 Update index.tsx 2024-05-04 07:38:14 +02:00
b07d29faa2 Fix: Improve disabled Select 2024-05-04 07:38:14 +02:00
60fcc42d8c Fix: Access Request setup 2024-05-04 07:38:14 +02:00
fabf7181fa Fix: Danger color not working on disabled buttons 2024-05-04 07:38:14 +02:00
c9a7b6abb6 Fix: Remove redundant code 2024-05-04 07:38:14 +02:00
3c53befb3e Feat: Request Access 2024-05-04 07:38:14 +02:00
1f0cf6cc9b Feat: Request access 2024-05-04 07:38:14 +02:00
84c19a7554 Feat: Request access 2024-05-04 07:38:14 +02:00
d3ea91c54b Fix: Types mismatch 2024-05-04 07:38:14 +02:00
ecb58b8680 Fix: Validate approvers access 2024-05-04 07:38:14 +02:00
1972a3c6ed Feat: Request access 2024-05-04 07:38:14 +02:00
a0b1fb23df Fix: Access Approval Policy DAL bugs 2024-05-04 07:38:14 +02:00
5910c11d88 Feat: Request access (new routes) 2024-05-04 07:38:14 +02:00
a768496c5e Fix: Move to project slug 2024-05-04 07:38:14 +02:00
e59cc138d9 Feat: Request access (models) 2024-05-04 07:38:14 +02:00
7a7d41ca83 Feat: Request Access (migrations) 2024-05-04 07:38:14 +02:00
bd8b56a224 Feat: Request access 2024-05-04 07:38:14 +02:00
aa5bd117e6 Feat: Request access 2024-05-04 07:38:14 +02:00
e66e6a7490 Fix: Remove logs 2024-05-04 07:38:00 +02:00
e54f499026 Feat: Request Access 2024-05-04 07:38:00 +02:00
7a5e0e9463 Draft 2024-05-04 07:37:42 +02:00
196 changed files with 2571 additions and 4770 deletions
.github/workflows
.infisicalignore
backend
package-lock.jsonpackage.json
src
@types
db
ee
lib/api-docs
server/routes
services
docs
frontend
package-lock.json
src
components/v2/SecretPathInput
const.ts
hooks/api
lib/fn
pages
integrations/aws-parameter-store
login
project/[id]/secrets/v2
signup
views
Login
Org/MembersPage/components
Project
AuditLogsPage/components
MembersPage/components
IdentityTab/components/IdentityRoleForm
MemberListTab/MemberRoleForm
SecretApprovalPage
SecretMainPage/components
ActionBar
SecretListView
SecretOverviewPage
SecretOverviewPage.tsx
components
SecretOverviewFolderRow
SecretOverviewTableRow
SelectionPanel
Signup
admin/DashboardPage
helm-charts/secrets-operator
k8-operator
pg-migrator/src/models/integration

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

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

@ -1207,58 +1207,6 @@
"node": ">=14.0.0"
}
},
"node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/core": "3.496.0",
"@aws-sdk/middleware-host-header": "3.502.0",
"@aws-sdk/middleware-logger": "3.502.0",
"@aws-sdk/middleware-recursion-detection": "3.502.0",
"@aws-sdk/middleware-user-agent": "3.502.0",
"@aws-sdk/region-config-resolver": "3.502.0",
"@aws-sdk/types": "3.502.0",
"@aws-sdk/util-endpoints": "3.502.0",
"@aws-sdk/util-user-agent-browser": "3.502.0",
"@aws-sdk/util-user-agent-node": "3.502.0",
"@smithy/config-resolver": "^2.1.1",
"@smithy/core": "^1.3.1",
"@smithy/fetch-http-handler": "^2.4.1",
"@smithy/hash-node": "^2.1.1",
"@smithy/invalid-dependency": "^2.1.1",
"@smithy/middleware-content-length": "^2.1.1",
"@smithy/middleware-endpoint": "^2.4.1",
"@smithy/middleware-retry": "^2.1.1",
"@smithy/middleware-serde": "^2.1.1",
"@smithy/middleware-stack": "^2.1.1",
"@smithy/node-config-provider": "^2.2.1",
"@smithy/node-http-handler": "^2.3.1",
"@smithy/protocol-http": "^3.1.1",
"@smithy/smithy-client": "^2.3.1",
"@smithy/types": "^2.9.1",
"@smithy/url-parser": "^2.1.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-body-length-browser": "^2.1.1",
"@smithy/util-body-length-node": "^2.2.1",
"@smithy/util-defaults-mode-browser": "^2.1.1",
"@smithy/util-defaults-mode-node": "^2.1.1",
"@smithy/util-endpoints": "^1.1.1",
"@smithy/util-middleware": "^2.1.1",
"@smithy/util-retry": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"fast-xml-parser": "4.2.5",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/client-secrets-manager/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@ -1366,7 +1314,7 @@
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/client-sts": {
"node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
@ -1488,58 +1436,6 @@
"node": ">=14.0.0"
}
},
"node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/core": "3.496.0",
"@aws-sdk/middleware-host-header": "3.502.0",
"@aws-sdk/middleware-logger": "3.502.0",
"@aws-sdk/middleware-recursion-detection": "3.502.0",
"@aws-sdk/middleware-user-agent": "3.502.0",
"@aws-sdk/region-config-resolver": "3.502.0",
"@aws-sdk/types": "3.502.0",
"@aws-sdk/util-endpoints": "3.502.0",
"@aws-sdk/util-user-agent-browser": "3.502.0",
"@aws-sdk/util-user-agent-node": "3.502.0",
"@smithy/config-resolver": "^2.1.1",
"@smithy/core": "^1.3.1",
"@smithy/fetch-http-handler": "^2.4.1",
"@smithy/hash-node": "^2.1.1",
"@smithy/invalid-dependency": "^2.1.1",
"@smithy/middleware-content-length": "^2.1.1",
"@smithy/middleware-endpoint": "^2.4.1",
"@smithy/middleware-retry": "^2.1.1",
"@smithy/middleware-serde": "^2.1.1",
"@smithy/middleware-stack": "^2.1.1",
"@smithy/node-config-provider": "^2.2.1",
"@smithy/node-http-handler": "^2.3.1",
"@smithy/protocol-http": "^3.1.1",
"@smithy/smithy-client": "^2.3.1",
"@smithy/types": "^2.9.1",
"@smithy/url-parser": "^2.1.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-body-length-browser": "^2.1.1",
"@smithy/util-body-length-node": "^2.2.1",
"@smithy/util-defaults-mode-browser": "^2.1.1",
"@smithy/util-defaults-mode-node": "^2.1.1",
"@smithy/util-endpoints": "^1.1.1",
"@smithy/util-middleware": "^2.1.1",
"@smithy/util-retry": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"fast-xml-parser": "4.2.5",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/credential-provider-node": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.504.0.tgz",
@ -1609,58 +1505,6 @@
"node": ">=14.0.0"
}
},
"node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/client-sts": {
"version": "3.504.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.504.0.tgz",
"integrity": "sha512-IESs8FkL7B/uY+ml4wgoRkrr6xYo4PizcNw6JX17eveq1gRBCPKeGMjE6HTDOcIYZZ8rqz/UeuH3JD4UhrMOnA==",
"dependencies": {
"@aws-crypto/sha256-browser": "3.0.0",
"@aws-crypto/sha256-js": "3.0.0",
"@aws-sdk/core": "3.496.0",
"@aws-sdk/middleware-host-header": "3.502.0",
"@aws-sdk/middleware-logger": "3.502.0",
"@aws-sdk/middleware-recursion-detection": "3.502.0",
"@aws-sdk/middleware-user-agent": "3.502.0",
"@aws-sdk/region-config-resolver": "3.502.0",
"@aws-sdk/types": "3.502.0",
"@aws-sdk/util-endpoints": "3.502.0",
"@aws-sdk/util-user-agent-browser": "3.502.0",
"@aws-sdk/util-user-agent-node": "3.502.0",
"@smithy/config-resolver": "^2.1.1",
"@smithy/core": "^1.3.1",
"@smithy/fetch-http-handler": "^2.4.1",
"@smithy/hash-node": "^2.1.1",
"@smithy/invalid-dependency": "^2.1.1",
"@smithy/middleware-content-length": "^2.1.1",
"@smithy/middleware-endpoint": "^2.4.1",
"@smithy/middleware-retry": "^2.1.1",
"@smithy/middleware-serde": "^2.1.1",
"@smithy/middleware-stack": "^2.1.1",
"@smithy/node-config-provider": "^2.2.1",
"@smithy/node-http-handler": "^2.3.1",
"@smithy/protocol-http": "^3.1.1",
"@smithy/smithy-client": "^2.3.1",
"@smithy/types": "^2.9.1",
"@smithy/url-parser": "^2.1.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-body-length-browser": "^2.1.1",
"@smithy/util-body-length-node": "^2.2.1",
"@smithy/util-defaults-mode-browser": "^2.1.1",
"@smithy/util-defaults-mode-node": "^2.1.1",
"@smithy/util-endpoints": "^1.1.1",
"@smithy/util-middleware": "^2.1.1",
"@smithy/util-retry": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"fast-xml-parser": "4.2.5",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"@aws-sdk/credential-provider-node": "^3.504.0"
}
},
"node_modules/@aws-sdk/middleware-host-header": {
"version": "3.502.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.502.0.tgz",
@ -3813,60 +3657,60 @@
}
},
"node_modules/@smithy/abort-controller": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.2.0.tgz",
"integrity": "sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.1.3.tgz",
"integrity": "sha512-c2aYH2Wu1RVE3rLlVgg2kQOBJGM0WbjReQi5DnPTm2Zb7F0gk7J2aeQeaX2u/lQZoHl6gv8Oac7mt9alU3+f4A==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/config-resolver": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.2.0.tgz",
"integrity": "sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.1.4.tgz",
"integrity": "sha512-AW2WUZmBAzgO3V3ovKtsUbI3aBNMeQKFDumoqkNxaVDWF/xfnxAWqBKDr/NuG7c06N2Rm4xeZLPiJH/d+na0HA==",
"dependencies": {
"@smithy/node-config-provider": "^2.3.0",
"@smithy/types": "^2.12.0",
"@smithy/util-config-provider": "^2.3.0",
"@smithy/util-middleware": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/node-config-provider": "^2.2.4",
"@smithy/types": "^2.10.1",
"@smithy/util-config-provider": "^2.2.1",
"@smithy/util-middleware": "^2.1.3",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/core": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.4.2.tgz",
"integrity": "sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA==",
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@smithy/core/-/core-1.3.5.tgz",
"integrity": "sha512-Rrc+e2Jj6Gu7Xbn0jvrzZlSiP2CZocIOfZ9aNUA82+1sa6GBnxqL9+iZ9EKHeD9aqD1nU8EK4+oN2EiFpSv7Yw==",
"dependencies": {
"@smithy/middleware-endpoint": "^2.5.1",
"@smithy/middleware-retry": "^2.3.1",
"@smithy/middleware-serde": "^2.3.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"@smithy/util-middleware": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/middleware-endpoint": "^2.4.4",
"@smithy/middleware-retry": "^2.1.4",
"@smithy/middleware-serde": "^2.1.3",
"@smithy/protocol-http": "^3.2.1",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"@smithy/util-middleware": "^2.1.3",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/credential-provider-imds": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.3.0.tgz",
"integrity": "sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==",
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.2.4.tgz",
"integrity": "sha512-DdatjmBZQnhGe1FhI8gO98f7NmvQFSDiZTwC3WMvLTCKQUY+Y1SVkhJqIuLu50Eb7pTheoXQmK+hKYUgpUWsNA==",
"dependencies": {
"@smithy/node-config-provider": "^2.3.0",
"@smithy/property-provider": "^2.2.0",
"@smithy/types": "^2.12.0",
"@smithy/url-parser": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/node-config-provider": "^2.2.4",
"@smithy/property-provider": "^2.1.3",
"@smithy/types": "^2.10.1",
"@smithy/url-parser": "^2.1.3",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
@ -3935,451 +3779,459 @@
}
},
"node_modules/@smithy/fetch-http-handler": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz",
"integrity": "sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==",
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.4.3.tgz",
"integrity": "sha512-Fn/KYJFo6L5I4YPG8WQb2hOmExgRmNpVH5IK2zU3JKrY5FKW7y9ar5e0BexiIC9DhSKqKX+HeWq/Y18fq7Dkpw==",
"dependencies": {
"@smithy/protocol-http": "^3.3.0",
"@smithy/querystring-builder": "^2.2.0",
"@smithy/types": "^2.12.0",
"@smithy/util-base64": "^2.3.0",
"tslib": "^2.6.2"
"@smithy/protocol-http": "^3.2.1",
"@smithy/querystring-builder": "^2.1.3",
"@smithy/types": "^2.10.1",
"@smithy/util-base64": "^2.1.1",
"tslib": "^2.5.0"
}
},
"node_modules/@smithy/hash-node": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.2.0.tgz",
"integrity": "sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.1.3.tgz",
"integrity": "sha512-FsAPCUj7VNJIdHbSxMd5uiZiF20G2zdSDgrgrDrHqIs/VMxK85Vqk5kMVNNDMCZmMezp6UKnac0B4nAyx7HJ9g==",
"dependencies": {
"@smithy/types": "^2.12.0",
"@smithy/util-buffer-from": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"@smithy/util-buffer-from": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/invalid-dependency": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.2.0.tgz",
"integrity": "sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.1.3.tgz",
"integrity": "sha512-wkra7d/G4CbngV4xsjYyAYOvdAhahQje/WymuQdVEnXFExJopEu7fbL5AEAlBPgWHXwu94VnCSG00gVzRfExyg==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
}
},
"node_modules/@smithy/is-array-buffer": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz",
"integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.1.1.tgz",
"integrity": "sha512-xozSQrcUinPpNPNPds4S7z/FakDTh1MZWtRP/2vQtYB/u3HYrX2UXuZs+VhaKBd6Vc7g2XPr2ZtwGBNDN6fNKQ==",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-content-length": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.2.0.tgz",
"integrity": "sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.1.3.tgz",
"integrity": "sha512-aJduhkC+dcXxdnv5ZpM3uMmtGmVFKx412R1gbeykS5HXDmRU6oSsyy2SoHENCkfOGKAQOjVE2WVqDJibC0d21g==",
"dependencies": {
"@smithy/protocol-http": "^3.3.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/protocol-http": "^3.2.1",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-endpoint": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz",
"integrity": "sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ==",
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.4.4.tgz",
"integrity": "sha512-4yjHyHK2Jul4JUDBo2sTsWY9UshYUnXeb/TAK/MTaPEb8XQvDmpwSFnfIRDU45RY1a6iC9LCnmJNg/yHyfxqkw==",
"dependencies": {
"@smithy/middleware-serde": "^2.3.0",
"@smithy/node-config-provider": "^2.3.0",
"@smithy/shared-ini-file-loader": "^2.4.0",
"@smithy/types": "^2.12.0",
"@smithy/url-parser": "^2.2.0",
"@smithy/util-middleware": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/middleware-serde": "^2.1.3",
"@smithy/node-config-provider": "^2.2.4",
"@smithy/shared-ini-file-loader": "^2.3.4",
"@smithy/types": "^2.10.1",
"@smithy/url-parser": "^2.1.3",
"@smithy/util-middleware": "^2.1.3",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-retry": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.3.1.tgz",
"integrity": "sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.1.4.tgz",
"integrity": "sha512-Cyolv9YckZTPli1EkkaS39UklonxMd08VskiuMhURDjC0HHa/AD6aK/YoD21CHv9s0QLg0WMLvk9YeLTKkXaFQ==",
"dependencies": {
"@smithy/node-config-provider": "^2.3.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/service-error-classification": "^2.1.5",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"@smithy/util-middleware": "^2.2.0",
"@smithy/util-retry": "^2.2.0",
"tslib": "^2.6.2",
"uuid": "^9.0.1"
"@smithy/node-config-provider": "^2.2.4",
"@smithy/protocol-http": "^3.2.1",
"@smithy/service-error-classification": "^2.1.3",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"@smithy/util-middleware": "^2.1.3",
"@smithy/util-retry": "^2.1.3",
"tslib": "^2.5.0",
"uuid": "^8.3.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-retry/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@smithy/middleware-serde": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz",
"integrity": "sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.1.3.tgz",
"integrity": "sha512-s76LId+TwASrHhUa9QS4k/zeXDUAuNuddKklQzRgumbzge5BftVXHXIqL4wQxKGLocPwfgAOXWx+HdWhQk9hTg==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/middleware-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz",
"integrity": "sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.1.3.tgz",
"integrity": "sha512-opMFufVQgvBSld/b7mD7OOEBxF6STyraVr1xel1j0abVILM8ALJvRoFbqSWHGmaDlRGIiV9Q5cGbWi0sdiEaLQ==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/node-config-provider": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz",
"integrity": "sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==",
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.2.4.tgz",
"integrity": "sha512-nqazHCp8r4KHSFhRQ+T0VEkeqvA0U+RhehBSr1gunUuNW3X7j0uDrWBxB2gE9eutzy6kE3Y7L+Dov/UXT871vg==",
"dependencies": {
"@smithy/property-provider": "^2.2.0",
"@smithy/shared-ini-file-loader": "^2.4.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/property-provider": "^2.1.3",
"@smithy/shared-ini-file-loader": "^2.3.4",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/node-http-handler": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz",
"integrity": "sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==",
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.4.1.tgz",
"integrity": "sha512-HCkb94soYhJMxPCa61wGKgmeKpJ3Gftx1XD6bcWEB2wMV1L9/SkQu/6/ysKBnbOzWRE01FGzwrTxucHypZ8rdg==",
"dependencies": {
"@smithy/abort-controller": "^2.2.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/querystring-builder": "^2.2.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/abort-controller": "^2.1.3",
"@smithy/protocol-http": "^3.2.1",
"@smithy/querystring-builder": "^2.1.3",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/property-provider": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.2.0.tgz",
"integrity": "sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.1.3.tgz",
"integrity": "sha512-bMz3se+ySKWNrgm7eIiQMa2HO/0fl2D0HvLAdg9pTMcpgp4SqOAh6bz7Ik6y7uQqSrk4rLjIKgbQ6yzYgGehCQ==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/protocol-http": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.3.0.tgz",
"integrity": "sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==",
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.2.1.tgz",
"integrity": "sha512-KLrQkEw4yJCeAmAH7hctE8g9KwA7+H2nSJwxgwIxchbp/L0B5exTdOQi9D5HinPLlothoervGmhpYKelZ6AxIA==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/querystring-builder": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz",
"integrity": "sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.1.3.tgz",
"integrity": "sha512-kFD3PnNqKELe6m9GRHQw/ftFFSZpnSeQD4qvgDB6BQN6hREHELSosVFUMPN4M3MDKN2jAwk35vXHLoDrNfKu0A==",
"dependencies": {
"@smithy/types": "^2.12.0",
"@smithy/util-uri-escape": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"@smithy/util-uri-escape": "^2.1.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/querystring-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz",
"integrity": "sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.1.3.tgz",
"integrity": "sha512-3+CWJoAqcBMR+yvz6D+Fc5VdoGFtfenW6wqSWATWajrRMGVwJGPT3Vy2eb2bnMktJc4HU4bpjeovFa566P3knQ==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/service-error-classification": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.5.tgz",
"integrity": "sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.1.3.tgz",
"integrity": "sha512-iUrpSsem97bbXHHT/v3s7vaq8IIeMo6P6cXdeYHrx0wOJpMeBGQF7CB0mbJSiTm3//iq3L55JiEm8rA7CTVI8A==",
"dependencies": {
"@smithy/types": "^2.12.0"
"@smithy/types": "^2.10.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/shared-ini-file-loader": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz",
"integrity": "sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==",
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.3.4.tgz",
"integrity": "sha512-CiZmPg9GeDKbKmJGEFvJBsJcFnh0AQRzOtQAzj1XEa8N/0/uSN/v1LYzgO7ry8hhO8+9KB7+DhSW0weqBra4Aw==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/signature-v4": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.3.0.tgz",
"integrity": "sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.1.3.tgz",
"integrity": "sha512-Jq4iPPdCmJojZTsPePn4r1ULShh6ONkokLuxp1Lnk4Sq7r7rJp4HlA1LbPBq4bD64TIzQezIpr1X+eh5NYkNxw==",
"dependencies": {
"@smithy/is-array-buffer": "^2.2.0",
"@smithy/types": "^2.12.0",
"@smithy/util-hex-encoding": "^2.2.0",
"@smithy/util-middleware": "^2.2.0",
"@smithy/util-uri-escape": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
"@smithy/eventstream-codec": "^2.1.3",
"@smithy/is-array-buffer": "^2.1.1",
"@smithy/types": "^2.10.1",
"@smithy/util-hex-encoding": "^2.1.1",
"@smithy/util-middleware": "^2.1.3",
"@smithy/util-uri-escape": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/smithy-client": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.5.1.tgz",
"integrity": "sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ==",
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.4.2.tgz",
"integrity": "sha512-ntAFYN51zu3N3mCd95YFcFi/8rmvm//uX+HnK24CRbI6k5Rjackn0JhgKz5zOx/tbNvOpgQIwhSX+1EvEsBLbA==",
"dependencies": {
"@smithy/middleware-endpoint": "^2.5.1",
"@smithy/middleware-stack": "^2.2.0",
"@smithy/protocol-http": "^3.3.0",
"@smithy/types": "^2.12.0",
"@smithy/util-stream": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/middleware-endpoint": "^2.4.4",
"@smithy/middleware-stack": "^2.1.3",
"@smithy/protocol-http": "^3.2.1",
"@smithy/types": "^2.10.1",
"@smithy/util-stream": "^2.1.3",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/types": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.12.0.tgz",
"integrity": "sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==",
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.10.1.tgz",
"integrity": "sha512-hjQO+4ru4cQ58FluQvKKiyMsFg0A6iRpGm2kqdH8fniyNd2WyanoOsYJfMX/IFLuLxEoW6gnRkNZy1y6fUUhtA==",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/url-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.2.0.tgz",
"integrity": "sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.1.3.tgz",
"integrity": "sha512-X1NRA4WzK/ihgyzTpeGvI9Wn45y8HmqF4AZ/FazwAv8V203Ex+4lXqcYI70naX9ETqbqKVzFk88W6WJJzCggTQ==",
"dependencies": {
"@smithy/querystring-parser": "^2.2.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/querystring-parser": "^2.1.3",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
}
},
"node_modules/@smithy/util-base64": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.3.0.tgz",
"integrity": "sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.1.1.tgz",
"integrity": "sha512-UfHVpY7qfF/MrgndI5PexSKVTxSZIdz9InghTFa49QOvuu9I52zLPLUHXvHpNuMb1iD2vmc6R+zbv/bdMipR/g==",
"dependencies": {
"@smithy/util-buffer-from": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
"@smithy/util-buffer-from": "^2.1.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-body-length-browser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.2.0.tgz",
"integrity": "sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.1.1.tgz",
"integrity": "sha512-ekOGBLvs1VS2d1zM2ER4JEeBWAvIOUKeaFch29UjjJsxmZ/f0L3K3x0dEETgh3Q9bkZNHgT+rkdl/J/VUqSRag==",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.5.0"
}
},
"node_modules/@smithy/util-body-length-node": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.3.0.tgz",
"integrity": "sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.2.1.tgz",
"integrity": "sha512-/ggJG+ta3IDtpNVq4ktmEUtOkH1LW64RHB5B0hcr5ZaWBmo96UX2cIOVbjCqqDickTXqBWZ4ZO0APuaPrD7Abg==",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-buffer-from": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz",
"integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.1.1.tgz",
"integrity": "sha512-clhNjbyfqIv9Md2Mg6FffGVrJxw7bgK7s3Iax36xnfVj6cg0fUG7I4RH0XgXJF8bxi+saY5HR21g2UPKSxVCXg==",
"dependencies": {
"@smithy/is-array-buffer": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/is-array-buffer": "^2.1.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-config-provider": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.3.0.tgz",
"integrity": "sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.2.1.tgz",
"integrity": "sha512-50VL/tx9oYYcjJn/qKqNy7sCtpD0+s8XEBamIFo4mFFTclKMNp+rsnymD796uybjiIquB7VCB/DeafduL0y2kw==",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-defaults-mode-browser": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.1.tgz",
"integrity": "sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw==",
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.1.4.tgz",
"integrity": "sha512-J6XAVY+/g7jf03QMnvqPyU+8jqGrrtXoKWFVOS+n1sz0Lg8HjHJ1ANqaDN+KTTKZRZlvG8nU5ZrJOUL6VdwgcQ==",
"dependencies": {
"@smithy/property-provider": "^2.2.0",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"@smithy/property-provider": "^2.1.3",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"bowser": "^2.11.0",
"tslib": "^2.6.2"
"tslib": "^2.5.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@smithy/util-defaults-mode-node": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.1.tgz",
"integrity": "sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA==",
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.2.3.tgz",
"integrity": "sha512-ttUISrv1uVOjTlDa3nznX33f0pthoUlP+4grhTvOzcLhzArx8qHB94/untGACOG3nlf8vU20nI2iWImfzoLkYA==",
"dependencies": {
"@smithy/config-resolver": "^2.2.0",
"@smithy/credential-provider-imds": "^2.3.0",
"@smithy/node-config-provider": "^2.3.0",
"@smithy/property-provider": "^2.2.0",
"@smithy/smithy-client": "^2.5.1",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/config-resolver": "^2.1.4",
"@smithy/credential-provider-imds": "^2.2.4",
"@smithy/node-config-provider": "^2.2.4",
"@smithy/property-provider": "^2.1.3",
"@smithy/smithy-client": "^2.4.2",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/@smithy/util-endpoints": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.2.0.tgz",
"integrity": "sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.1.4.tgz",
"integrity": "sha512-/qAeHmK5l4yQ4/bCIJ9p49wDe9rwWtOzhPHblu386fwPNT3pxmodgcs9jDCV52yK9b4rB8o9Sj31P/7Vzka1cg==",
"dependencies": {
"@smithy/node-config-provider": "^2.3.0",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/node-config-provider": "^2.2.4",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@smithy/util-hex-encoding": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz",
"integrity": "sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.1.1.tgz",
"integrity": "sha512-3UNdP2pkYUUBGEXzQI9ODTDK+Tcu1BlCyDBaRHwyxhA+8xLP8agEKQq4MGmpjqb4VQAjq9TwlCQX0kP6XDKYLg==",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-middleware": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.2.0.tgz",
"integrity": "sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.1.3.tgz",
"integrity": "sha512-/+2fm7AZ2ozl5h8wM++ZP0ovE9/tiUUAHIbCfGfb3Zd3+Dyk17WODPKXBeJ/TnK5U+x743QmA0xHzlSm8I/qhw==",
"dependencies": {
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-retry": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.2.0.tgz",
"integrity": "sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.1.3.tgz",
"integrity": "sha512-Kbvd+GEMuozbNUU3B89mb99tbufwREcyx2BOX0X2+qHjq6Gvsah8xSDDgxISDwcOHoDqUWO425F0Uc/QIRhYkg==",
"dependencies": {
"@smithy/service-error-classification": "^2.1.5",
"@smithy/types": "^2.12.0",
"tslib": "^2.6.2"
"@smithy/service-error-classification": "^2.1.3",
"@smithy/types": "^2.10.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/@smithy/util-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.2.0.tgz",
"integrity": "sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.1.3.tgz",
"integrity": "sha512-HvpEQbP8raTy9n86ZfXiAkf3ezp1c3qeeO//zGqwZdrfaoOpGKQgF2Sv1IqZp7wjhna7pvczWaGUHjcOPuQwKw==",
"dependencies": {
"@smithy/fetch-http-handler": "^2.5.0",
"@smithy/node-http-handler": "^2.5.0",
"@smithy/types": "^2.12.0",
"@smithy/util-base64": "^2.3.0",
"@smithy/util-buffer-from": "^2.2.0",
"@smithy/util-hex-encoding": "^2.2.0",
"@smithy/util-utf8": "^2.3.0",
"tslib": "^2.6.2"
"@smithy/fetch-http-handler": "^2.4.3",
"@smithy/node-http-handler": "^2.4.1",
"@smithy/types": "^2.10.1",
"@smithy/util-base64": "^2.1.1",
"@smithy/util-buffer-from": "^2.1.1",
"@smithy/util-hex-encoding": "^2.1.1",
"@smithy/util-utf8": "^2.1.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-uri-escape": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz",
"integrity": "sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.1.1.tgz",
"integrity": "sha512-saVzI1h6iRBUVSqtnlOnc9ssU09ypo7n+shdQ8hBTZno/9rZ3AuRYvoHInV57VF7Qn7B+pFJG7qTzFiHxWlWBw==",
"dependencies": {
"tslib": "^2.6.2"
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@smithy/util-utf8": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz",
"integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.1.1.tgz",
"integrity": "sha512-BqTpzYEcUMDwAKr7/mVRUtHDhs6ZoXDi9NypMvMfOr/+u1NW7JgqodPDECiiLboEm6bobcPcECxzjtQh865e9A==",
"dependencies": {
"@smithy/util-buffer-from": "^2.2.0",
"tslib": "^2.6.2"
"@smithy/util-buffer-from": "^2.1.1",
"tslib": "^2.5.0"
},
"engines": {
"node": ">=14.0.0"

@ -110,7 +110,7 @@
"libsodium-wrappers": "^0.7.13",
"lodash.isequal": "^4.5.0",
"ms": "^2.1.3",
"mysql2": "^3.9.7",
"mysql2": "^3.9.4",
"nanoid": "^5.0.4",
"nodemailer": "^6.9.9",
"ora": "^7.0.1",

@ -32,7 +32,6 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { TIdentityAwsIamAuthServiceFactory } from "@app/services/identity-aws-iam-auth/identity-aws-iam-auth-service";
import { TIdentityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
import { TIdentityUaServiceFactory } from "@app/services/identity-ua/identity-ua-service";
import { TIntegrationServiceFactory } from "@app/services/integration/integration-service";
@ -116,7 +115,6 @@ declare module "fastify" {
identityAccessToken: TIdentityAccessTokenServiceFactory;
identityProject: TIdentityProjectServiceFactory;
identityUa: TIdentityUaServiceFactory;
identityAwsIamAuth: TIdentityAwsIamAuthServiceFactory;
accessApprovalPolicy: TAccessApprovalPolicyServiceFactory;
accessApprovalRequest: TAccessApprovalRequestServiceFactory;
secretApprovalPolicy: TSecretApprovalPolicyServiceFactory;

@ -50,6 +50,9 @@ import {
TGroupProjectMemberships,
TGroupProjectMembershipsInsert,
TGroupProjectMembershipsUpdate,
TGroupProjectUserAdditionalPrivilege,
TGroupProjectUserAdditionalPrivilegeInsert,
TGroupProjectUserAdditionalPrivilegeUpdate,
TGroups,
TGroupsInsert,
TGroupsUpdate,
@ -59,9 +62,6 @@ import {
TIdentityAccessTokens,
TIdentityAccessTokensInsert,
TIdentityAccessTokensUpdate,
TIdentityAwsIamAuths,
TIdentityAwsIamAuthsInsert,
TIdentityAwsIamAuthsUpdate,
TIdentityOrgMemberships,
TIdentityOrgMembershipsInsert,
TIdentityOrgMembershipsUpdate,
@ -293,6 +293,11 @@ declare module "knex/types/tables" {
TProjectUserMembershipRolesInsert,
TProjectUserMembershipRolesUpdate
>;
[TableName.GroupProjectUserAdditionalPrivilege]: Knex.CompositeTableType<
TGroupProjectUserAdditionalPrivilege,
TGroupProjectUserAdditionalPrivilegeInsert,
TGroupProjectUserAdditionalPrivilegeUpdate
>;
[TableName.ProjectRoles]: Knex.CompositeTableType<TProjectRoles, TProjectRolesInsert, TProjectRolesUpdate>;
[TableName.ProjectUserAdditionalPrivilege]: Knex.CompositeTableType<
TProjectUserAdditionalPrivilege,
@ -329,11 +334,6 @@ declare module "knex/types/tables" {
TIdentityUniversalAuthsInsert,
TIdentityUniversalAuthsUpdate
>;
[TableName.IdentityAwsIamAuth]: Knex.CompositeTableType<
TIdentityAwsIamAuths,
TIdentityAwsIamAuthsInsert,
TIdentityAwsIamAuthsUpdate
>;
[TableName.IdentityUaClientSecret]: Knex.CompositeTableType<
TIdentityUaClientSecrets,
TIdentityUaClientSecretsInsert,

@ -9,9 +9,8 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("name").notNullable();
t.integer("approvals").defaultTo(1).notNullable();
t.string("secretPath");
t.uuid("envId").notNullable();
t.string("secretPath");
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
t.timestamps(true, true, true);
});
@ -21,8 +20,9 @@ export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover))) {
await knex.schema.createTable(TableName.AccessApprovalPolicyApprover, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("approverId").notNullable();
t.foreign("approverId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.uuid("approverUserId").nullable();
t.foreign("approverUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");

@ -0,0 +1,37 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.GroupProjectUserAdditionalPrivilege))) {
await knex.schema.createTable(TableName.GroupProjectUserAdditionalPrivilege, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("slug", 60).notNullable();
t.uuid("groupProjectMembershipId").notNullable();
t.foreign("groupProjectMembershipId")
.references("id")
.inTable(TableName.GroupProjectMembership)
.onDelete("CASCADE");
t.uuid("requestedByUserId").notNullable();
t.foreign("requestedByUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.boolean("isTemporary").notNullable().defaultTo(false);
t.string("temporaryMode");
t.string("temporaryRange"); // could be cron or relative time like 1H or 1minute etc
t.datetime("temporaryAccessStartTime");
t.datetime("temporaryAccessEndTime");
t.jsonb("permissions").notNullable();
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.GroupProjectUserAdditionalPrivilege);
}
export async function down(knex: Knex): Promise<void> {
await dropOnUpdateTrigger(knex, TableName.GroupProjectUserAdditionalPrivilege);
await knex.schema.dropTableIfExists(TableName.GroupProjectUserAdditionalPrivilege);
}

@ -11,11 +11,26 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("policyId").notNullable();
t.foreign("policyId").references("id").inTable(TableName.AccessApprovalPolicy).onDelete("CASCADE");
t.uuid("privilegeId").nullable();
t.foreign("privilegeId").references("id").inTable(TableName.ProjectUserAdditionalPrivilege).onDelete("CASCADE");
t.uuid("projectUserPrivilegeId").nullable();
t.foreign("projectUserPrivilegeId")
.references("id")
.inTable(TableName.ProjectUserAdditionalPrivilege)
.onDelete("CASCADE");
t.uuid("requestedBy").notNullable();
t.foreign("requestedBy").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.uuid("groupProjectUserPrivilegeId").nullable();
t.foreign("groupProjectUserPrivilegeId")
.references("id")
.inTable(TableName.GroupProjectUserAdditionalPrivilege)
.onDelete("CASCADE");
t.uuid("requestedByUserId").notNullable();
t.foreign("requestedByUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.uuid("projectMembershipId").nullable();
t.foreign("projectMembershipId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.uuid("groupMembershipId").nullable();
t.foreign("groupMembershipId").references("id").inTable(TableName.GroupProjectMembership).onDelete("CASCADE");
// We use these values to create the actual privilege at a later point in time.
t.boolean("isTemporary").notNullable();
@ -31,14 +46,17 @@ export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.AccessApprovalRequestReviewer))) {
await knex.schema.createTable(TableName.AccessApprovalRequestReviewer, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.uuid("member").notNullable();
t.foreign("member").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
t.uuid("memberUserId").notNullable();
t.foreign("memberUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
t.string("status").notNullable();
t.uuid("requestId").notNullable();
t.foreign("requestId").references("id").inTable(TableName.AccessApprovalRequest).onDelete("CASCADE");
t.timestamps(true, true, true);
});
}
await createOnUpdateTrigger(knex, TableName.AccessApprovalRequestReviewer);
}

@ -0,0 +1,71 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
export async function up(knex: Knex): Promise<void> {
// SecretApprovalPolicyApprover, approverUserId
if (!(await knex.schema.hasColumn(TableName.SecretApprovalPolicyApprover, "approverUserId"))) {
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (t) => {
t.uuid("approverId").nullable().alter();
t.uuid("approverUserId").nullable();
t.foreign("approverUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
// SecretApprovalRequest, statusChangeByUserId
if (!(await knex.schema.hasColumn(TableName.SecretApprovalRequest, "statusChangeByUserId"))) {
await knex.schema.alterTable(TableName.SecretApprovalRequest, (t) => {
t.uuid("statusChangeBy").nullable().alter();
t.uuid("statusChangeByUserId").nullable();
t.foreign("statusChangeByUserId").references("id").inTable(TableName.Users).onDelete("SET NULL");
});
}
// SecretApprovalRequest, committerUserId
if (!(await knex.schema.hasColumn(TableName.SecretApprovalRequest, "committerUserId"))) {
await knex.schema.alterTable(TableName.SecretApprovalRequest, (t) => {
t.uuid("committerId").nullable().alter();
t.uuid("committerUserId").nullable();
t.foreign("committerUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
// SecretApprovalRequestReviewer, memberUserId
if (!(await knex.schema.hasColumn(TableName.SecretApprovalRequestReviewer, "memberUserId"))) {
await knex.schema.alterTable(TableName.SecretApprovalRequestReviewer, (t) => {
t.uuid("member").nullable().alter();
t.uuid("memberUserId").nullable();
t.foreign("memberUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
});
}
}
export async function down(knex: Knex): Promise<void> {
if (await knex.schema.hasColumn(TableName.SecretApprovalPolicyApprover, "approverUserId")) {
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (t) => {
t.dropColumn("approverUserId");
});
}
if (await knex.schema.hasColumn(TableName.SecretApprovalRequest, "statusChangeByUserId")) {
await knex.schema.alterTable(TableName.SecretApprovalRequest, (t) => {
t.dropColumn("statusChangeByUserId");
});
}
if (await knex.schema.hasColumn(TableName.SecretApprovalRequest, "committerUserId")) {
await knex.schema.alterTable(TableName.SecretApprovalRequest, (t) => {
t.dropColumn("committerUserId");
});
}
if (await knex.schema.hasColumn(TableName.SecretApprovalRequestReviewer, "memberUserId")) {
await knex.schema.alterTable(TableName.SecretApprovalRequestReviewer, (t) => {
t.dropColumn("memberUserId");
});
}
}

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

@ -1,29 +0,0 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
if (!(await knex.schema.hasTable(TableName.IdentityAwsIamAuth))) {
await knex.schema.createTable(TableName.IdentityAwsIamAuth, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
t.jsonb("accessTokenTrustedIps").notNullable();
t.timestamps(true, true, true);
t.uuid("identityId").notNullable().unique();
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
t.string("stsEndpoint").notNullable();
t.string("allowedPrincipalArns").notNullable();
t.string("allowedAccountIds").notNullable();
});
}
await createOnUpdateTrigger(knex, TableName.IdentityAwsIamAuth);
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists(TableName.IdentityAwsIamAuth);
await dropOnUpdateTrigger(knex, TableName.IdentityAwsIamAuth);
}

@ -9,7 +9,7 @@ import { TImmutableDBKeys } from "./models";
export const AccessApprovalPoliciesApproversSchema = z.object({
id: z.string().uuid(),
approverId: z.string().uuid(),
approverUserId: z.string().uuid().nullable().optional(),
policyId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()

@ -9,7 +9,7 @@ import { TImmutableDBKeys } from "./models";
export const AccessApprovalRequestsReviewersSchema = z.object({
id: z.string().uuid(),
member: z.string().uuid(),
memberUserId: z.string().uuid(),
status: z.string(),
requestId: z.string().uuid(),
createdAt: z.date(),

@ -10,8 +10,11 @@ import { TImmutableDBKeys } from "./models";
export const AccessApprovalRequestsSchema = z.object({
id: z.string().uuid(),
policyId: z.string().uuid(),
privilegeId: z.string().uuid().nullable().optional(),
requestedBy: z.string().uuid(),
projectUserPrivilegeId: z.string().uuid().nullable().optional(),
groupProjectUserPrivilegeId: z.string().uuid().nullable().optional(),
requestedByUserId: z.string().uuid(),
projectMembershipId: z.string().uuid().nullable().optional(),
groupMembershipId: z.string().uuid().nullable().optional(),
isTemporary: z.boolean(),
temporaryRange: z.string().nullable().optional(),
permissions: z.unknown(),

@ -0,0 +1,32 @@
// 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 GroupProjectUserAdditionalPrivilegeSchema = z.object({
id: z.string().uuid(),
slug: z.string(),
groupProjectMembershipId: z.string().uuid(),
requestedByUserId: z.string().uuid(),
isTemporary: z.boolean().default(false),
temporaryMode: z.string().nullable().optional(),
temporaryRange: z.string().nullable().optional(),
temporaryAccessStartTime: z.date().nullable().optional(),
temporaryAccessEndTime: z.date().nullable().optional(),
permissions: z.unknown(),
createdAt: z.date(),
updatedAt: z.date()
});
export type TGroupProjectUserAdditionalPrivilege = z.infer<typeof GroupProjectUserAdditionalPrivilegeSchema>;
export type TGroupProjectUserAdditionalPrivilegeInsert = Omit<
z.input<typeof GroupProjectUserAdditionalPrivilegeSchema>,
TImmutableDBKeys
>;
export type TGroupProjectUserAdditionalPrivilegeUpdate = Partial<
Omit<z.input<typeof GroupProjectUserAdditionalPrivilegeSchema>, TImmutableDBKeys>
>;

@ -1,26 +0,0 @@
// 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 IdentityAwsIamAuthsSchema = z.object({
id: z.string().uuid(),
accessTokenTTL: z.coerce.number().default(7200),
accessTokenMaxTTL: z.coerce.number().default(7200),
accessTokenNumUsesLimit: z.coerce.number().default(0),
accessTokenTrustedIps: z.unknown(),
createdAt: z.date(),
updatedAt: z.date(),
identityId: z.string().uuid(),
stsEndpoint: z.string(),
allowedPrincipalArns: z.string(),
allowedAccountIds: z.string()
});
export type TIdentityAwsIamAuths = z.infer<typeof IdentityAwsIamAuthsSchema>;
export type TIdentityAwsIamAuthsInsert = Omit<z.input<typeof IdentityAwsIamAuthsSchema>, TImmutableDBKeys>;
export type TIdentityAwsIamAuthsUpdate = Partial<Omit<z.input<typeof IdentityAwsIamAuthsSchema>, TImmutableDBKeys>>;

@ -14,10 +14,10 @@ export * from "./git-app-install-sessions";
export * from "./git-app-org";
export * from "./group-project-membership-roles";
export * from "./group-project-memberships";
export * from "./group-project-user-additional-privilege";
export * from "./groups";
export * from "./identities";
export * from "./identity-access-tokens";
export * from "./identity-aws-iam-auths";
export * from "./identity-org-memberships";
export * from "./identity-project-additional-privilege";
export * from "./identity-project-membership-role";

@ -25,6 +25,7 @@ export enum TableName {
ProjectMembership = "project_memberships",
ProjectRoles = "project_roles",
ProjectUserAdditionalPrivilege = "project_user_additional_privilege",
GroupProjectUserAdditionalPrivilege = "group_project_user_additional_privilege",
ProjectUserMembershipRole = "project_user_membership_roles",
ProjectKeys = "project_keys",
Secret = "secrets",
@ -45,7 +46,6 @@ export enum TableName {
IdentityAccessToken = "identity_access_tokens",
IdentityUniversalAuth = "identity_universal_auths",
IdentityUaClientSecret = "identity_ua_client_secrets",
IdentityAwsIamAuth = "identity_aws_iam_auths",
IdentityOrgMembership = "identity_org_memberships",
IdentityProjectMembership = "identity_project_memberships",
IdentityProjectMembershipRole = "identity_project_membership_role",
@ -143,6 +143,5 @@ export enum ProjectUpgradeStatus {
}
export enum IdentityAuthMethod {
Univeral = "universal-auth",
AWS_IAM_AUTH = "aws-iam-auth"
Univeral = "universal-auth"
}

@ -9,10 +9,11 @@ import { TImmutableDBKeys } from "./models";
export const SecretApprovalPoliciesApproversSchema = z.object({
id: z.string().uuid(),
approverId: z.string().uuid(),
approverId: z.string().uuid().nullable().optional(),
policyId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
approverUserId: z.string().uuid().nullable().optional()
});
export type TSecretApprovalPoliciesApprovers = z.infer<typeof SecretApprovalPoliciesApproversSchema>;

@ -9,11 +9,12 @@ import { TImmutableDBKeys } from "./models";
export const SecretApprovalRequestsReviewersSchema = z.object({
id: z.string().uuid(),
member: z.string().uuid(),
member: z.string().uuid().nullable().optional(),
status: z.string(),
requestId: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
memberUserId: z.string().uuid().nullable().optional()
});
export type TSecretApprovalRequestsReviewers = z.infer<typeof SecretApprovalRequestsReviewersSchema>;

@ -16,9 +16,11 @@ export const SecretApprovalRequestsSchema = z.object({
slug: z.string(),
folderId: z.string().uuid(),
statusChangeBy: z.string().uuid().nullable().optional(),
committerId: z.string().uuid(),
committerId: z.string().uuid().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date()
updatedAt: z.date(),
statusChangeByUserId: z.string().uuid().nullable().optional(),
committerUserId: z.string().uuid().nullable().optional()
});
export type TSecretApprovalRequests = z.infer<typeof SecretApprovalRequestsSchema>;

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

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

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

@ -53,7 +53,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
}),
response: {
200: z.object({
approvals: sapPubSchema.extend({ approvers: z.string().array(), secretPath: z.string().optional() }).array()
approvals: sapPubSchema
.extend({ approvers: z.string().nullish().array(), secretPath: z.string().optional() })
.array()
})
}
},

@ -74,7 +74,7 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
schema: {
querystring: z.object({
projectSlug: z.string().trim(),
authorProjectMembershipId: z.string().trim().optional(),
authorUserId: z.string().trim().optional(),
envSlug: z.string().trim().optional()
}),
response: {
@ -84,7 +84,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
isApproved: z.boolean(),
privilege: z
.object({
membershipId: z.string(),
projectMembershipId: z.string().nullish(),
groupMembershipId: z.string().nullish(),
isTemporary: z.boolean(),
temporaryMode: z.string().nullish(),
temporaryRange: z.string().nullish(),
@ -115,8 +116,8 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
handler: async (req) => {
const { requests } = await server.services.accessApprovalRequest.listApprovalRequests({
projectSlug: req.query.projectSlug,
authorProjectMembershipId: req.query.authorProjectMembershipId,
envSlug: req.query.envSlug,
authorUserId: req.query.authorUserId,
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
@ -127,6 +128,37 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
}
});
server.route({
url: "/:requestId",
method: "DELETE",
schema: {
params: z.object({
requestId: z.string().trim()
}),
querystring: z.object({
projectSlug: z.string().trim()
}),
response: {
200: z.object({
request: AccessApprovalRequestsSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const { request } = await server.services.accessApprovalRequest.deleteAccessApprovalRequest({
actor: req.permission.type,
actorId: req.permission.id,
actorOrgId: req.permission.orgId,
actorAuthMethod: req.permission.authMethod,
requestId: req.params.requestId,
projectSlug: req.query.projectSlug
});
return { request };
}
});
server.route({
url: "/:requestId/review",
method: "POST",

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

@ -1,6 +1,6 @@
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
import { registerGroupRouter } from "./group-router";

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

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

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

@ -130,7 +130,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
}),
response: {
200: z.object({
approvals: sapPubSchema.merge(z.object({ approvers: z.string().array() })).array()
approvals: sapPubSchema.merge(z.object({ approvers: z.string().nullish().array() })).array()
})
}
},
@ -161,7 +161,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
}),
response: {
200: z.object({
policy: sapPubSchema.merge(z.object({ approvers: z.string().array() })).optional()
policy: sapPubSchema.merge(z.object({ approvers: z.string().nullish().array() })).optional()
})
}
},

@ -197,7 +197,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
type: isClosing ? EventType.SECRET_APPROVAL_CLOSED : EventType.SECRET_APPROVAL_REOPENED,
// eslint-disable-next-line
metadata: {
[isClosing ? ("closedBy" as const) : ("reopenedBy" as const)]: approval.statusChangeBy as string,
[isClosing ? ("closedBy" as const) : ("reopenedBy" as const)]: approval.statusChangeByUserId as string,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
// eslint-disable-next-line

@ -20,7 +20,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.select(tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
@ -38,12 +38,12 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
const formatedDoc = mergeOneToManyRelation(
doc,
"id",
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
({ approverUserId, envId, envName: name, envSlug: slug, ...el }) => ({
...el,
envId,
environment: { id: envId, name, slug }
}),
({ approverId }) => approverId,
({ approverUserId }) => approverUserId,
"approvers"
);
return formatedDoc?.[0];
@ -58,12 +58,12 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
const formatedDoc = mergeOneToManyRelation(
docs,
"id",
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
({ approverUserId, envId, envName: name, envSlug: slug, ...el }) => ({
...el,
envId,
environment: { id: envId, name, slug }
}),
({ approverId }) => approverId,
({ approverUserId }) => approverUserId,
"approvers"
);
return formatedDoc.map((policy) => ({ ...policy, secretPath: policy.secretPath || undefined }));

@ -5,7 +5,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
import { BadRequestError } from "@app/lib/errors";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
@ -24,7 +24,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
accessApprovalPolicyDAL: TAccessApprovalPolicyDALFactory;
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findOne">;
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
userDAL: Pick<TUserDALFactory, "findUsersByProjectId">;
};
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
@ -34,8 +34,8 @@ export const accessApprovalPolicyServiceFactory = ({
accessApprovalPolicyApproverDAL,
permissionService,
projectEnvDAL,
projectDAL,
projectMembershipDAL
userDAL,
projectDAL
}: TSecretApprovalPolicyServiceFactoryDep) => {
const createAccessApprovalPolicy = async ({
name,
@ -69,12 +69,13 @@ export const accessApprovalPolicyServiceFactory = ({
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
if (!env) throw new BadRequestError({ message: "Environment not found" });
const secretApprovers = await projectMembershipDAL.find({
projectId: project.id,
$in: { id: approvers }
});
// We need to get the users by project ID to ensure they are part of the project.
const accessApproverUsers = await userDAL.findUsersByProjectId(
project.id,
approvers.map((approverUserId) => approverUserId)
);
if (secretApprovers.length !== approvers.length) {
if (accessApproverUsers.length !== approvers.length) {
throw new BadRequestError({ message: "Approver not found in project" });
}
@ -85,7 +86,7 @@ export const accessApprovalPolicyServiceFactory = ({
secretPath,
actorAuthMethod,
permissionService,
userIds: secretApprovers.map((approver) => approver.userId)
userIds: accessApproverUsers.map((user) => user.id)
});
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
@ -99,8 +100,8 @@ export const accessApprovalPolicyServiceFactory = ({
tx
);
await accessApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({
approverId: id,
accessApproverUsers.map((user) => ({
approverUserId: user.id,
policyId: doc.id
})),
tx
@ -169,12 +170,9 @@ export const accessApprovalPolicyServiceFactory = ({
);
if (approvers) {
// Find the workspace project memberships of the users passed in the approvers array
const secretApprovers = await projectMembershipDAL.find(
{
projectId: accessApprovalPolicy.projectId,
$in: { id: approvers }
},
{ tx }
const secretApproverUsers = await userDAL.findUsersByProjectId(
accessApprovalPolicy.projectId,
approvers.map((approverUserId) => approverUserId)
);
await verifyApprovers({
@ -184,15 +182,15 @@ export const accessApprovalPolicyServiceFactory = ({
secretPath: doc.secretPath!,
actorAuthMethod,
permissionService,
userIds: secretApprovers.map((approver) => approver.userId)
userIds: secretApproverUsers.map((user) => user.id)
});
if (secretApprovers.length !== approvers.length)
if (secretApproverUsers.length !== approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
await accessApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({
approverId: id,
secretApproverUsers.map((user) => ({
approverUserId: user.id,
policyId: doc.id
})),
tx

@ -11,6 +11,42 @@ export type TAccessApprovalRequestDALFactory = ReturnType<typeof accessApprovalR
export const accessApprovalRequestDALFactory = (db: TDbClient) => {
const accessApprovalRequestOrm = ormify(db, TableName.AccessApprovalRequest);
const projectUserAdditionalPrivilegeOrm = ormify(db, TableName.ProjectUserAdditionalPrivilege);
const groupProjectUserAdditionalPrivilegeOrm = ormify(db, TableName.GroupProjectUserAdditionalPrivilege);
const deleteMany = async (filter: TFindFilter<TAccessApprovalRequests>, tx?: Knex) => {
const transaction = tx || (await db.transaction());
try {
const accessApprovalRequests = await accessApprovalRequestOrm.find(filter, { tx: transaction });
await projectUserAdditionalPrivilegeOrm.delete(
{
$in: {
id: accessApprovalRequests
.filter((req) => Boolean(req.projectUserPrivilegeId))
.map((req) => req.projectUserPrivilegeId!)
}
},
transaction
);
await groupProjectUserAdditionalPrivilegeOrm.delete(
{
$in: {
id: accessApprovalRequests
.filter((req) => Boolean(req.groupProjectUserPrivilegeId))
.map((req) => req.groupProjectUserPrivilegeId!)
}
},
transaction
);
return await accessApprovalRequestOrm.delete(filter, transaction);
} catch (error) {
throw new DatabaseError({ error, name: "DeleteManyAccessApprovalRequest" });
}
};
const findRequestsWithPrivilegeByPolicyIds = async (policyIds: string[]) => {
try {
@ -19,9 +55,14 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
`${TableName.AccessApprovalRequest}.privilegeId`,
`${TableName.AccessApprovalRequest}.projectUserPrivilegeId`,
`${TableName.ProjectUserAdditionalPrivilege}.id`
)
.leftJoin(
TableName.GroupProjectUserAdditionalPrivilege,
`${TableName.AccessApprovalRequest}.groupProjectUserPrivilegeId`,
`${TableName.GroupProjectUserAdditionalPrivilege}.id`
)
.leftJoin(
TableName.AccessApprovalPolicy,
`${TableName.AccessApprovalRequest}.policyId`,
@ -50,7 +91,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
)
.select(db.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(
db.ref("projectId").withSchema(TableName.Environment),
@ -59,32 +100,85 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
)
.select(
db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"),
db.ref("memberUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
)
// Project user additional privilege
.select(
db
.ref("projectMembershipId")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeMembershipId"),
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
.as("projectPrivilegeProjectMembershipId"),
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("projectPrivilegeIsTemporary"),
db
.ref("temporaryMode")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("projectPrivilegeTemporaryMode"),
db
.ref("temporaryRange")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("projectPrivilegeTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeTemporaryAccessStartTime"),
.as("projectPrivilegeTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeTemporaryAccessEndTime"),
.as("projectPrivilegeTemporaryAccessEndTime"),
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegePermissions")
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("projectPrivilegePermissions")
)
// Group project user additional privilege
.select(
db
.ref("groupProjectMembershipId")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegeGroupProjectMembershipId"),
db
.ref("requestedByUserId")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegeRequestedByUserId"),
db
.ref("isTemporary")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegeIsTemporary"),
db
.ref("temporaryMode")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegeTemporaryMode"),
db
.ref("temporaryRange")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegeTemporaryRange"),
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegeTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegeTemporaryAccessEndTime"),
db
.ref("permissions")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("groupPrivilegePermissions")
)
.orderBy(`${TableName.AccessApprovalRequest}.createdAt`, "desc");
const formattedDocs = sqlNestRelationships({
const projectUserFormattedDocs = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (doc) => ({
@ -99,33 +193,49 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
secretPath: doc.policySecretPath,
envId: doc.policyEnvId
},
privilege: doc.privilegeId
// eslint-disable-next-line no-nested-ternary
privilege: doc.projectUserPrivilegeId
? {
membershipId: doc.privilegeMembershipId,
isTemporary: doc.privilegeIsTemporary,
temporaryMode: doc.privilegeTemporaryMode,
temporaryRange: doc.privilegeTemporaryRange,
temporaryAccessStartTime: doc.privilegeTemporaryAccessStartTime,
temporaryAccessEndTime: doc.privilegeTemporaryAccessEndTime,
permissions: doc.privilegePermissions
projectMembershipId: doc.projectMembershipId,
groupMembershipId: null,
requestedByUserId: null,
isTemporary: doc.projectPrivilegeIsTemporary,
temporaryMode: doc.projectPrivilegeTemporaryMode,
temporaryRange: doc.projectPrivilegeTemporaryRange,
temporaryAccessStartTime: doc.projectPrivilegeTemporaryAccessStartTime,
temporaryAccessEndTime: doc.projectPrivilegeTemporaryAccessEndTime,
permissions: doc.projectPrivilegePermissions
}
: null,
: doc.groupProjectUserPrivilegeId
? {
groupMembershipId: doc.groupPrivilegeGroupProjectMembershipId,
requestedByUserId: doc.groupPrivilegeRequestedByUserId,
projectMembershipId: null,
isTemporary: doc.groupPrivilegeIsTemporary,
temporaryMode: doc.groupPrivilegeTemporaryMode,
temporaryRange: doc.groupPrivilegeTemporaryRange,
temporaryAccessStartTime: doc.groupPrivilegeTemporaryAccessStartTime,
temporaryAccessEndTime: doc.groupPrivilegeTemporaryAccessEndTime,
permissions: doc.groupPrivilegePermissions
}
: null,
isApproved: !!doc.privilegeId
isApproved: Boolean(doc.projectUserPrivilegeId || doc.groupProjectUserPrivilegeId)
}),
childrenMapper: [
{
key: "reviewerMemberId",
key: "reviewerUserId",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
mapper: ({ reviewerUserId, reviewerStatus: status }) =>
reviewerUserId ? { member: reviewerUserId, status } : undefined
},
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
{ key: "approverUserId", label: "approvers" as const, mapper: ({ approverUserId }) => approverUserId }
]
});
if (!formattedDocs) return [];
if (!projectUserFormattedDocs) return [];
return formattedDocs.map((doc) => ({
return projectUserFormattedDocs.map((doc) => ({
...doc,
policy: { ...doc.policy, approvers: doc.approvers }
}));
@ -157,7 +267,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(
tx.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"),
tx.ref("memberUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"),
tx.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
tx.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
@ -165,7 +275,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover)
);
const findById = async (id: string, tx?: Knex) => {
@ -188,11 +298,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}),
childrenMapper: [
{
key: "reviewerMemberId",
key: "reviewerUserId",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
mapper: ({ reviewerUserId, reviewerStatus: status }) =>
reviewerUserId ? { member: reviewerUserId, status } : undefined
},
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
{ key: "approverUserId", label: "approvers" as const, mapper: ({ approverUserId }) => approverUserId }
]
});
if (!formatedDoc?.[0]) return;
@ -214,12 +325,6 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicy}.id`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.leftJoin(
TableName.ProjectUserAdditionalPrivilege,
`${TableName.AccessApprovalRequest}.privilegeId`,
`${TableName.ProjectUserAdditionalPrivilege}.id`
)
.leftJoin(
TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`,
@ -229,7 +334,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
.where(`${TableName.Environment}.projectId`, projectId)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
.select(db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"));
.select(db.ref("memberUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("memberUserId"));
const formattedRequests = sqlNestRelationships({
data: accessRequests,
@ -239,21 +344,28 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}),
childrenMapper: [
{
key: "reviewerMemberId",
key: "memberUserId",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
mapper: ({ memberUserId, reviewerStatus: status }) =>
memberUserId ? { member: memberUserId, status } : undefined
}
]
});
// an approval is pending if there is no reviewer rejections and no privilege ID is set
const pendingApprovals = formattedRequests.filter(
(req) => !req.privilegeId && !req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
(req) =>
!req.projectUserPrivilegeId &&
!req.groupProjectUserPrivilegeId &&
!req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
);
// an approval is finalized if there are any rejections or a privilege ID is set
const finalizedApprovals = formattedRequests.filter(
(req) => req.privilegeId || req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
(req) =>
req.projectUserPrivilegeId ||
req.groupProjectUserPrivilegeId ||
req.reviewers.some((r) => r.status === ApprovalStatus.REJECTED)
);
return { pendingCount: pendingApprovals.length, finalizedCount: finalizedApprovals.length };
@ -262,5 +374,5 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}
};
return { ...accessApprovalRequestOrm, findById, findRequestsWithPrivilegeByPolicyIds, getCount };
return { ...accessApprovalRequestOrm, findById, findRequestsWithPrivilegeByPolicyIds, getCount, delete: deleteMany };
};

@ -1,7 +1,8 @@
import { ForbiddenError } from "@casl/ability";
import slugify from "@sindresorhus/slugify";
import ms from "ms";
import { ProjectMembershipRole } from "@app/db/schemas";
import { ProjectMembershipRole, TProjectUserAdditionalPrivilege } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
@ -14,7 +15,9 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal";
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
import { verifyApprovers } from "../access-approval-policy/access-approval-policy-fns";
import { TGroupProjectUserAdditionalPrivilegeDALFactory } from "../group-project-user-additional-privilege/group-project-user-additional-privilege-dal";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
@ -23,13 +26,15 @@ import { TAccessApprovalRequestReviewerDALFactory } from "./access-approval-requ
import {
ApprovalStatus,
TCreateAccessApprovalRequestDTO,
TDeleteApprovalRequestDTO,
TGetAccessRequestCountDTO,
TListApprovalRequestsDTO,
TReviewAccessRequestDTO
} from "./access-approval-request-types";
type TSecretApprovalRequestServiceFactoryDep = {
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "create" | "findById">;
type TAccessApprovalRequestServiceFactoryDep = {
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "create" | "findById" | "deleteById">;
groupAdditionalPrivilegeDAL: TGroupProjectUserAdditionalPrivilegeDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
accessApprovalPolicyApproverDAL: Pick<TAccessApprovalPolicyApproverDALFactory, "find">;
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
@ -44,6 +49,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
| "updateById"
| "findOne"
| "getCount"
| "deleteById"
>;
accessApprovalPolicyDAL: Pick<TAccessApprovalPolicyDALFactory, "findOne" | "find">;
accessApprovalRequestReviewerDAL: Pick<
@ -52,7 +58,10 @@ type TSecretApprovalRequestServiceFactoryDep = {
>;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "findUserByProjectMembershipId" | "findUsersByProjectMembershipIds">;
userDAL: Pick<
TUserDALFactory,
"findUserByProjectMembershipId" | "findUsersByProjectMembershipIds" | "findUsersByProjectId" | "findUserByProjectId"
>;
};
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
@ -62,6 +71,7 @@ export const accessApprovalRequestServiceFactory = ({
projectEnvDAL,
permissionService,
accessApprovalRequestDAL,
groupAdditionalPrivilegeDAL,
accessApprovalRequestReviewerDAL,
projectMembershipDAL,
accessApprovalPolicyDAL,
@ -69,7 +79,7 @@ export const accessApprovalRequestServiceFactory = ({
additionalPrivilegeDAL,
smtpService,
userDAL
}: TSecretApprovalRequestServiceFactoryDep) => {
}: TAccessApprovalRequestServiceFactoryDep) => {
const createAccessApprovalRequest = async ({
isTemporary,
temporaryRange,
@ -94,9 +104,6 @@ export const accessApprovalRequestServiceFactory = ({
);
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
const requestedByUser = await userDAL.findUserByProjectMembershipId(membership.id);
if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" });
await projectDAL.checkProjectUpgradeStatus(project.id);
const { envSlug, secretPath, accessTypes } = verifyRequestedPermissions({ permissions: requestedPermissions });
@ -114,25 +121,43 @@ export const accessApprovalRequestServiceFactory = ({
policyId: policy.id
});
const approverUsers = await userDAL.findUsersByProjectMembershipIds(
approvers.map((approver) => approver.approverId)
if (approvers.some((approver) => !approver.approverUserId)) {
throw new BadRequestError({ message: "Policy approvers must be assigned to users" });
}
const approverUsers = await userDAL.findUsersByProjectId(
project.id,
approvers.map((approver) => approver.approverUserId!)
);
const requestedByUser = await userDAL.findUserByProjectId(project.id, actorId);
if (!requestedByUser) throw new BadRequestError({ message: "User not found in project" });
const duplicateRequests = await accessApprovalRequestDAL.find({
policyId: policy.id,
requestedBy: membership.id,
requestedByUserId: actorId,
permissions: JSON.stringify(requestedPermissions),
isTemporary
});
if (duplicateRequests?.length > 0) {
for await (const duplicateRequest of duplicateRequests) {
if (duplicateRequest.privilegeId) {
const privilege = await additionalPrivilegeDAL.findById(duplicateRequest.privilegeId);
let foundPrivilege: Pick<
TProjectUserAdditionalPrivilege,
"temporaryAccessEndTime" | "isTemporary" | "id"
> | null = null;
const isExpired = new Date() > new Date(privilege.temporaryAccessEndTime || ("" as string));
if (duplicateRequest.projectUserPrivilegeId) {
foundPrivilege = await additionalPrivilegeDAL.findById(duplicateRequest.projectUserPrivilegeId);
} else if (duplicateRequest.groupProjectUserPrivilegeId) {
foundPrivilege = await groupAdditionalPrivilegeDAL.findById(duplicateRequest.groupProjectUserPrivilegeId);
}
if (!isExpired || !privilege.isTemporary) {
if (foundPrivilege) {
const isExpired = new Date() > new Date(foundPrivilege.temporaryAccessEndTime || ("" as string));
if (!isExpired || !foundPrivilege.isTemporary) {
throw new BadRequestError({ message: "You already have an active privilege with the same criteria" });
}
} else {
@ -150,10 +175,18 @@ export const accessApprovalRequestServiceFactory = ({
}
const approval = await accessApprovalRequestDAL.transaction(async (tx) => {
const requesterUser = await userDAL.findUserByProjectId(project.id, actorId);
if (!requesterUser?.projectMembershipId && !requesterUser?.groupProjectMembershipId) {
throw new BadRequestError({ message: "You don't have a membership for this project" });
}
const approvalRequest = await accessApprovalRequestDAL.create(
{
projectMembershipId: requesterUser.projectMembershipId || null,
groupMembershipId: requesterUser.groupProjectMembershipId || null,
policyId: policy.id,
requestedBy: membership.id,
requestedByUserId: actorId, // This is the user ID of the person who made the request
temporaryRange: temporaryRange || null,
permissions: JSON.stringify(requestedPermissions),
isTemporary
@ -187,9 +220,62 @@ export const accessApprovalRequestServiceFactory = ({
return { request: approval };
};
const deleteAccessApprovalRequest = async ({
projectSlug,
actor,
requestId,
actorOrgId,
actorId,
actorAuthMethod
}: TDeleteApprovalRequestDTO) => {
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new UnauthorizedError({ message: "Project not found" });
const { membership, permission } = await permissionService.getProjectPermission(
actor,
actorId,
project.id,
actorAuthMethod,
actorOrgId
);
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Delete,
ProjectPermissionSub.SecretApproval
);
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
if (!accessApprovalRequest?.projectUserPrivilegeId && !accessApprovalRequest?.groupProjectUserPrivilegeId) {
throw new BadRequestError({ message: "Access request must be approved to be deleted" });
}
if (accessApprovalRequest?.projectId !== project.id) {
throw new UnauthorizedError({ message: "Request not found in project" });
}
const approvers = await accessApprovalPolicyApproverDAL.find({
policyId: accessApprovalRequest.policyId
});
// make sure the actor (actorId) is an approver
if (!approvers.some((approver) => approver.approverUserId === actorId)) {
throw new UnauthorizedError({ message: "Only policy approvers can delete access requests" });
}
if (accessApprovalRequest.projectUserPrivilegeId) {
await additionalPrivilegeDAL.deleteById(accessApprovalRequest.projectUserPrivilegeId);
} else if (accessApprovalRequest.groupProjectUserPrivilegeId) {
await groupAdditionalPrivilegeDAL.deleteById(accessApprovalRequest.groupProjectUserPrivilegeId);
}
return { request: accessApprovalRequest };
};
const listApprovalRequests = async ({
projectSlug,
authorProjectMembershipId,
authorUserId,
envSlug,
actor,
actorOrgId,
@ -211,13 +297,8 @@ export const accessApprovalRequestServiceFactory = ({
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
if (authorProjectMembershipId) {
requests = requests.filter((request) => request.requestedBy === authorProjectMembershipId);
}
if (envSlug) {
requests = requests.filter((request) => request.environment === envSlug);
}
if (authorUserId) requests = requests.filter((request) => request.requestedByUserId === authorUserId);
if (envSlug) requests = requests.filter((request) => request.environment === envSlug);
return { requests };
};
@ -246,8 +327,8 @@ export const accessApprovalRequestServiceFactory = ({
if (
!hasRole(ProjectMembershipRole.Admin) &&
accessApprovalRequest.requestedBy !== membership.id && // The request wasn't made by the current user
!policy.approvers.find((approverId) => approverId === membership.id) // The request isn't performed by an assigned approver
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
!policy.approvers.find((approverUserId) => approverUserId === membership.id) // The request isn't performed by an assigned approver
) {
throw new UnauthorizedError({ message: "You are not authorized to approve this request" });
}
@ -273,7 +354,7 @@ export const accessApprovalRequestServiceFactory = ({
const review = await accessApprovalRequestReviewerDAL.findOne(
{
requestId: accessApprovalRequest.id,
member: membership.id
memberUserId: actorId
},
tx
);
@ -282,7 +363,7 @@ export const accessApprovalRequestServiceFactory = ({
{
status,
requestId: accessApprovalRequest.id,
member: membership.id
memberUserId: actorId
},
tx
);
@ -297,41 +378,92 @@ export const accessApprovalRequestServiceFactory = ({
throw new BadRequestError({ message: "Temporary range is required for temporary access" });
}
let privilegeId: string | null = null;
let projectUserPrivilegeId: string | null = null;
let groupProjectMembershipId: string | null = null;
if (!accessApprovalRequest.groupMembershipId && !accessApprovalRequest.projectMembershipId) {
throw new BadRequestError({ message: "Project membership or group membership is required" });
}
// Permanent access
if (!accessApprovalRequest.isTemporary && !accessApprovalRequest.temporaryRange) {
// Permanent access
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.requestedBy,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions)
},
tx
);
privilegeId = privilege.id;
if (accessApprovalRequest.groupMembershipId) {
// Group user privilege
const groupProjectUserAdditionalPrivilege = await groupAdditionalPrivilegeDAL.create(
{
groupProjectMembershipId: accessApprovalRequest.groupMembershipId,
requestedByUserId: accessApprovalRequest.requestedByUserId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions)
},
tx
);
groupProjectMembershipId = groupProjectUserAdditionalPrivilege.id;
} else {
// Project user privilege
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.projectMembershipId!,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions)
},
tx
);
projectUserPrivilegeId = privilege.id;
}
} else {
// Temporary access
const relativeTempAllocatedTimeInMs = ms(accessApprovalRequest.temporaryRange!);
const startTime = new Date();
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.requestedBy,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions),
isTemporary: true,
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: accessApprovalRequest.temporaryRange!,
temporaryAccessStartTime: startTime,
temporaryAccessEndTime: new Date(new Date(startTime).getTime() + relativeTempAllocatedTimeInMs)
},
tx
);
privilegeId = privilege.id;
if (accessApprovalRequest.groupMembershipId) {
// Group user privilege
const groupProjectUserAdditionalPrivilege = await groupAdditionalPrivilegeDAL.create(
{
groupProjectMembershipId: accessApprovalRequest.groupMembershipId,
requestedByUserId: accessApprovalRequest.requestedByUserId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions),
isTemporary: true,
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: accessApprovalRequest.temporaryRange!,
temporaryAccessStartTime: startTime,
temporaryAccessEndTime: new Date(new Date(startTime).getTime() + relativeTempAllocatedTimeInMs)
},
tx
);
groupProjectMembershipId = groupProjectUserAdditionalPrivilege.id;
} else {
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.projectMembershipId!,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions),
isTemporary: true,
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
temporaryRange: accessApprovalRequest.temporaryRange!,
temporaryAccessStartTime: startTime,
temporaryAccessEndTime: new Date(new Date(startTime).getTime() + relativeTempAllocatedTimeInMs)
},
tx
);
projectUserPrivilegeId = privilege.id;
}
}
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { privilegeId }, tx);
if (projectUserPrivilegeId) {
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { projectUserPrivilegeId }, tx);
} else if (groupProjectMembershipId) {
await accessApprovalRequestDAL.updateById(
accessApprovalRequest.id,
{ groupProjectUserPrivilegeId: groupProjectMembershipId },
tx
);
} else {
throw new BadRequestError({ message: "No privilege was created" });
}
}
return newReview;
@ -364,6 +496,7 @@ export const accessApprovalRequestServiceFactory = ({
createAccessApprovalRequest,
listApprovalRequests,
reviewAccessRequest,
deleteAccessApprovalRequest,
getCount
};
};

@ -28,6 +28,11 @@ export type TCreateAccessApprovalRequestDTO = {
export type TListApprovalRequestsDTO = {
projectSlug: string;
authorProjectMembershipId?: string;
authorUserId?: string;
envSlug?: string;
} & Omit<TProjectPermission, "projectId">;
export type TDeleteApprovalRequestDTO = {
requestId: string;
projectSlug: string;
} & Omit<TProjectPermission, "projectId">;

@ -66,10 +66,6 @@ export enum EventType {
CREATE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "create-identity-universal-auth-client-secret",
REVOKE_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRET = "revoke-identity-universal-auth-client-secret",
GET_IDENTITY_UNIVERSAL_AUTH_CLIENT_SECRETS = "get-identity-universal-auth-client-secret",
LOGIN_IDENTITY_AWS_IAM_AUTH = "login-identity-aws-iam-auth",
ADD_IDENTITY_AWS_IAM_AUTH = "add-identity-aws-iam-auth",
UPDATE_IDENTITY_AWS_IAM_AUTH = "update-identity-aws-iam-auth",
GET_IDENTITY_AWS_IAM_AUTH = "get-identity-aws-iam-auth",
CREATE_ENVIRONMENT = "create-environment",
UPDATE_ENVIRONMENT = "update-environment",
DELETE_ENVIRONMENT = "delete-environment",
@ -410,50 +406,6 @@ interface RevokeIdentityUniversalAuthClientSecretEvent {
};
}
interface LoginIdentityAwsIamAuthEvent {
type: EventType.LOGIN_IDENTITY_AWS_IAM_AUTH;
metadata: {
identityId: string;
identityAwsIamAuthId: string;
identityAccessTokenId: string;
};
}
interface AddIdentityAwsIamAuthEvent {
type: EventType.ADD_IDENTITY_AWS_IAM_AUTH;
metadata: {
identityId: string;
stsEndpoint: string;
allowedPrincipalArns: string;
allowedAccountIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
};
}
interface UpdateIdentityAwsIamAuthEvent {
type: EventType.UPDATE_IDENTITY_AWS_IAM_AUTH;
metadata: {
identityId: string;
stsEndpoint?: string;
allowedPrincipalArns?: string;
allowedAccountIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
};
}
interface GetIdentityAwsIamAuthEvent {
type: EventType.GET_IDENTITY_AWS_IAM_AUTH;
metadata: {
identityId: string;
};
}
interface CreateEnvironmentEvent {
type: EventType.CREATE_ENVIRONMENT;
metadata: {
@ -673,9 +625,9 @@ interface SecretApprovalReopened {
interface SecretApprovalRequest {
type: EventType.SECRET_APPROVAL_REQUEST;
metadata: {
committedBy: string;
secretApprovalRequestSlug: string;
secretApprovalRequestId: string;
committedByUser?: string | null; // Needs to be nullable for backward compatibility
};
}
@ -708,10 +660,6 @@ export type Event =
| CreateIdentityUniversalAuthClientSecretEvent
| GetIdentityUniversalAuthClientSecretsEvent
| RevokeIdentityUniversalAuthClientSecretEvent
| LoginIdentityAwsIamAuthEvent
| AddIdentityAwsIamAuthEvent
| UpdateIdentityAwsIamAuthEvent
| GetIdentityAwsIamAuthEvent
| CreateEnvironmentEvent
| UpdateEnvironmentEvent
| DeleteEnvironmentEvent

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

@ -5,10 +5,78 @@ import { TableName, TGroups } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, ormify, selectAllTableCols, TFindFilter, TFindOpt } from "@app/lib/knex";
import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
export type TGroupDALFactory = ReturnType<typeof groupDALFactory>;
export const groupDALFactory = (db: TDbClient) => {
export const groupDALFactory = (db: TDbClient, userGroupMembershipDAL: TUserGroupMembershipDALFactory) => {
const groupOrm = ormify(db, TableName.Groups);
const groupMembershipOrm = ormify(db, TableName.GroupProjectMembership);
const accessApprovalRequestOrm = ormify(db, TableName.AccessApprovalRequest);
const secretApprovalRequestOrm = ormify(db, TableName.SecretApprovalRequest);
const deleteMany = async (filterQuery: TFindFilter<TGroups>, tx?: Knex) => {
const transaction = tx || (await db.transaction());
// Find all memberships
const groups = await groupOrm.find(filterQuery, { tx: transaction });
for await (const group of groups) {
// Find all the group memberships of the groups (a group membership is which projects the group is a part of)
const groupProjectMemberships = await groupMembershipOrm.find(
{ groupId: group.id },
{
tx: transaction
}
);
// For each of those group memberships, we need to find all the members of the group that don't have a regular membership in the project
for await (const groupMembership of groupProjectMemberships) {
const members = await userGroupMembershipDAL.findGroupMembersNotInProject(
group.id,
groupMembership.projectId,
transaction
);
// We then delete all the access approval requests and secret approval requests associated with these members
await accessApprovalRequestOrm.delete(
{
groupMembershipId: groupMembership.id,
$in: {
requestedByUserId: members.map(({ user }) => user.id)
}
},
transaction
);
const policies = await (tx || db)(TableName.SecretApprovalPolicy)
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.where(`${TableName.Environment}.projectId`, groupMembership.projectId)
.select(selectAllTableCols(TableName.SecretApprovalPolicy));
await secretApprovalRequestOrm.delete(
{
$in: {
policyId: policies.map(({ id }) => id),
committerUserId: members.map(({ user }) => user.id)
}
},
transaction
);
}
}
await groupOrm.delete(
{
$in: {
id: groups.map((group) => group.id)
}
},
transaction
);
return groups;
};
const findGroups = async (filter: TFindFilter<TGroups>, { offset, limit, sort, tx }: TFindOpt<TGroups> = {}) => {
try {
@ -122,9 +190,10 @@ export const groupDALFactory = (db: TDbClient) => {
};
return {
...groupOrm,
findGroups,
findByOrgId,
findAllGroupMembers,
...groupOrm
delete: deleteMany
};
};

@ -1,6 +1,6 @@
import { Knex } from "knex";
import { SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
import { SecretKeyEncoding, TUsers } from "@app/db/schemas";
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError, ScimRequestError } from "@app/lib/errors";
@ -188,9 +188,9 @@ export const addUsersToGroupByUserIds = async ({
// check if all user(s) are part of the organization
const existingUserOrgMemberships = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.orgId` as "orgId"]: group.orgId,
orgId: group.orgId,
$in: {
[`${TableName.OrgMembership}.userId` as "userId"]: userIds
userId: userIds
}
},
{ tx }
@ -266,6 +266,9 @@ export const removeUsersFromGroupByUserIds = async ({
userIds,
userDAL,
userGroupMembershipDAL,
accessApprovalRequestDAL,
secretApprovalRequestDAL,
secretApprovalPolicyDAL,
groupProjectDAL,
projectKeyDAL,
tx: outerTx
@ -322,20 +325,16 @@ export const removeUsersFromGroupByUserIds = async ({
});
if (membersToRemoveFromGroupNonPending.length) {
// check which projects the group is part of
const projectIds = Array.from(
new Set(
(
await groupProjectDAL.find(
{
groupId: group.id
},
{ tx }
)
).map((gp) => gp.projectId)
)
const groupProjectMemberships = await groupProjectDAL.find(
{
groupId: group.id
},
{ tx }
);
// check which projects the group is part of
const projectIds = Array.from(new Set(groupProjectMemberships.map((gp) => gp.projectId)));
// TODO: this part can be optimized
for await (const userId of userIds) {
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(userId, group.id, projectIds, tx);
@ -353,10 +352,35 @@ export const removeUsersFromGroupByUserIds = async ({
);
}
await accessApprovalRequestDAL.delete(
{
$in: {
groupMembershipId: groupProjectMemberships
.filter((gp) => projectsToDeleteKeyFor.includes(gp.projectId))
.map((gp) => gp.id)
},
requestedByUserId: userId
},
tx
);
const projectSecretApprovalPolicies = await secretApprovalPolicyDAL.findByProjectIds(projectIds);
await secretApprovalRequestDAL.delete(
{
committerUserId: userId,
$in: {
policyId: projectSecretApprovalPolicies.map((p) => p.id)
}
},
tx
);
await userGroupMembershipDAL.delete(
{
groupId: group.id,
userId
$in: {
userId: membersToRemoveFromGroupNonPending.map((member) => member.id)
}
},
tx
);
@ -364,12 +388,15 @@ export const removeUsersFromGroupByUserIds = async ({
}
if (membersToRemoveFromGroupPending.length) {
await userGroupMembershipDAL.delete({
groupId: group.id,
$in: {
userId: membersToRemoveFromGroupPending.map((member) => member.id)
}
});
await userGroupMembershipDAL.delete(
{
groupId: group.id,
$in: {
userId: membersToRemoveFromGroupPending.map((member) => member.id)
}
},
tx
);
}
return membersToRemoveFromGroupNonPending.concat(membersToRemoveFromGroupPending);

@ -12,9 +12,12 @@ import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
import { TSecretApprovalRequestDALFactory } from "../secret-approval-request/secret-approval-request-dal";
import { TGroupDALFactory } from "./group-dal";
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "./group-fns";
import {
@ -41,6 +44,9 @@ type TGroupServiceFactoryDep = {
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "delete">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "delete">;
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findByProjectIds">;
};
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
@ -50,6 +56,9 @@ export const groupServiceFactory = ({
groupDAL,
groupProjectDAL,
orgDAL,
secretApprovalRequestDAL,
secretApprovalPolicyDAL,
accessApprovalRequestDAL,
userGroupMembershipDAL,
projectDAL,
projectBotDAL,
@ -328,6 +337,9 @@ export const groupServiceFactory = ({
group,
userIds: [user.id],
userDAL,
accessApprovalRequestDAL,
secretApprovalPolicyDAL,
secretApprovalRequestDAL,
userGroupMembershipDAL,
groupProjectDAL,
projectKeyDAL

@ -10,6 +10,10 @@ import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
import { TSecretApprovalRequestDALFactory } from "../secret-approval-request/secret-approval-request-dal";
export type TCreateGroupDTO = {
name: string;
slug?: string;
@ -77,6 +81,9 @@ export type TRemoveUsersFromGroupByUserIds = {
group: TGroups;
userIds: string[];
userDAL: Pick<TUserDALFactory, "find" | "transaction">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "delete">;
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "delete">;
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findByProjectIds">;
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "filterProjectsByUserMembership" | "delete">;
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "delete">;

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

@ -1,14 +1,7 @@
import { ForbiddenError } from "@casl/ability";
import jwt from "jsonwebtoken";
import {
OrgMembershipRole,
OrgMembershipStatus,
SecretKeyEncoding,
TableName,
TLdapConfigsUpdate,
TUsers
} from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TLdapConfigsUpdate } 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";
@ -26,19 +19,19 @@ import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { UserAliasType } from "@app/services/user-alias/user-alias-types";
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
import { TLicenseServiceFactory } from "../license/license-service";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
import { TSecretApprovalRequestDALFactory } from "../secret-approval-request/secret-approval-request-dal";
import { TLdapConfigDALFactory } from "./ldap-config-dal";
import {
TCreateLdapCfgDTO,
@ -56,7 +49,6 @@ import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal";
type TLdapConfigServiceFactoryDep = {
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne">;
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
orgDAL: Pick<
TOrgDALFactory,
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
@ -78,6 +70,9 @@ type TLdapConfigServiceFactoryDep = {
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "delete">;
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "delete">;
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findByProjectIds">;
};
export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>;
@ -86,10 +81,12 @@ export const ldapConfigServiceFactory = ({
ldapConfigDAL,
ldapGroupMapDAL,
orgDAL,
orgMembershipDAL,
orgBotDAL,
groupDAL,
groupProjectDAL,
accessApprovalRequestDAL,
secretApprovalPolicyDAL,
secretApprovalRequestDAL,
projectKeyDAL,
projectDAL,
projectBotDAL,
@ -391,17 +388,16 @@ export const ldapConfigServiceFactory = ({
username,
firstName,
lastName,
email,
emails,
groups,
orgId,
relayState
}: TLdapLoginDTO) => {
const appCfg = getConfig();
const serverCfg = await getServerCfg();
let userAlias = await userAliasDAL.findOne({
externalId,
orgId,
aliasType: UserAliasType.LDAP
aliasType: AuthMethod.LDAP
});
const organization = await orgDAL.findOrgById(orgId);
@ -409,13 +405,7 @@ export const ldapConfigServiceFactory = ({
if (userAlias) {
await userDAL.transaction(async (tx) => {
const [orgMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: userAlias.userId,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
},
{ tx }
);
const [orgMembership] = await orgDAL.findMembership({ userId: userAlias.userId }, { tx });
if (!orgMembership) {
await orgDAL.createMembership(
{
@ -438,75 +428,40 @@ export const ldapConfigServiceFactory = ({
});
} else {
userAlias = await userDAL.transaction(async (tx) => {
let newUser: TUsers | undefined;
if (serverCfg.trustSamlEmails) {
newUser = await userDAL.findOne(
{
email,
isEmailVerified: true
},
tx
);
}
if (!newUser) {
const uniqueUsername = await normalizeUsername(username, userDAL);
newUser = await userDAL.create(
{
username: serverCfg.trustLdapEmails ? email : uniqueUsername,
email,
isEmailVerified: serverCfg.trustLdapEmails,
firstName,
lastName,
authMethods: [],
isGhost: false
},
tx
);
}
const uniqueUsername = await normalizeUsername(username, userDAL);
const newUser = await userDAL.create(
{
username: uniqueUsername,
email: emails[0],
firstName,
lastName,
authMethods: [AuthMethod.LDAP],
isGhost: false
},
tx
);
const newUserAlias = await userAliasDAL.create(
{
userId: newUser.id,
username,
aliasType: UserAliasType.LDAP,
aliasType: AuthMethod.LDAP,
externalId,
emails: [email],
emails,
orgId
},
tx
);
const [orgMembership] = await orgDAL.findMembership(
await orgDAL.createMembership(
{
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
userId: newUser.id,
orgId,
role: OrgMembershipRole.Member,
status: OrgMembershipStatus.Invited
},
{ tx }
tx
);
if (!orgMembership) {
await orgMembershipDAL.create(
{
userId: userAlias.userId,
inviteEmail: email,
orgId,
role: OrgMembershipRole.Member,
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
},
tx
);
// Only update the membership to Accepted if the user account is already completed.
} else if (orgMembership.status === OrgMembershipStatus.Invited && newUser.isAccepted) {
await orgDAL.updateMembershipById(
orgMembership.id,
{
status: OrgMembershipStatus.Accepted
},
tx
);
}
return newUserAlias;
});
}
@ -578,7 +533,10 @@ export const ldapConfigServiceFactory = ({
group,
userIds: [newUser.id],
userDAL,
secretApprovalRequestDAL,
accessApprovalRequestDAL,
userGroupMembershipDAL,
secretApprovalPolicyDAL,
groupProjectDAL,
projectKeyDAL,
tx
@ -597,14 +555,11 @@ export const ldapConfigServiceFactory = ({
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id,
username: user.username,
...(user.email && { email: user.email, isEmailVerified: user.isEmailVerified }),
firstName,
lastName,
organizationName: organization.name,
organizationId: organization.id,
organizationSlug: organization.slug,
authMethod: AuthMethod.LDAP,
authType: UserAliasType.LDAP,
isUserCompleted,
...(relayState
? {

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

@ -62,6 +62,11 @@ export const permissionDALFactory = (db: TDbClient) => {
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin(
TableName.GroupProjectUserAdditionalPrivilege,
`${TableName.GroupProjectUserAdditionalPrivilege}.groupProjectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.GroupProjectMembershipRole}.customRoleId`,
@ -77,11 +82,34 @@ export const permissionDALFactory = (db: TDbClient) => {
db.ref("projectId").withSchema(TableName.GroupProjectMembership),
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
db.ref("orgId").withSchema(TableName.Project),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("permissions").withSchema(TableName.ProjectRoles)
)
.select("permissions");
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.select(
db.ref("projectId").withSchema(TableName.GroupProjectMembership).as("groupMembershipProjectId"),
db.ref("id").withSchema(TableName.GroupProjectUserAdditionalPrivilege).as("userApId"),
db.ref("permissions").withSchema(TableName.GroupProjectUserAdditionalPrivilege).as("userApPermissions"),
db.ref("temporaryMode").withSchema(TableName.GroupProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
db.ref("isTemporary").withSchema(TableName.GroupProjectUserAdditionalPrivilege).as("userApIsTemporary"),
db.ref("temporaryRange").withSchema(TableName.GroupProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
db.ref("groupProjectMembershipId").withSchema(TableName.GroupProjectUserAdditionalPrivilege),
db
.ref("requestedByUserId")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("userApRequestedByUserId"),
const docs = await db(TableName.ProjectMembership)
db
.ref("temporaryAccessStartTime")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("userApTemporaryAccessStartTime"),
db
.ref("temporaryAccessEndTime")
.withSchema(TableName.GroupProjectUserAdditionalPrivilege)
.as("userApTemporaryAccessEndTime")
);
const projectMemberDocs = await db(TableName.ProjectMembership)
.join(
TableName.ProjectUserMembershipRole,
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
@ -127,7 +155,7 @@ export const permissionDALFactory = (db: TDbClient) => {
);
const permission = sqlNestRelationships({
data: docs,
data: projectMemberDocs,
key: "projectId",
parentMapper: ({ orgId, orgAuthEnforced, membershipId, membershipCreatedAt, membershipUpdatedAt }) => ({
orgId,
@ -194,6 +222,33 @@ export const permissionDALFactory = (db: TDbClient) => {
permissions: z.unknown(),
customRoleSlug: z.string().optional().nullable()
}).parse(data)
},
{
key: "userApId",
label: "additionalPrivileges" as const,
mapper: ({
groupMembershipProjectId,
groupProjectMembershipId,
userApId,
userApPermissions,
userApRequestedByUserId,
userApIsTemporary,
userApTemporaryMode,
userApTemporaryRange,
userApTemporaryAccessEndTime,
userApTemporaryAccessStartTime
}) => ({
groupProjectMembershipId,
groupMembershipProjectId,
id: userApId,
userId: userApRequestedByUserId,
permissions: userApPermissions,
temporaryRange: userApTemporaryRange,
temporaryMode: userApTemporaryMode,
temporaryAccessEndTime: userApTemporaryAccessEndTime,
temporaryAccessStartTime: userApTemporaryAccessStartTime,
isTemporary: userApIsTemporary
})
}
]
})
@ -214,15 +269,24 @@ export const permissionDALFactory = (db: TDbClient) => {
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
);
const activeAdditionalPrivileges =
permission?.[0]?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime }) =>
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
) ?? [];
const activeGroupAdditionalPrivileges =
groupPermission?.[0]?.additionalPrivileges?.filter(
({ isTemporary, temporaryAccessEndTime, groupProjectMembershipId, groupMembershipProjectId, userId: user }) =>
groupMembershipProjectId === projectId &&
!!groupProjectMembershipId &&
user === userId &&
(!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime))
) ?? [];
return {
...(permission[0] || groupPermission[0]),
roles: [...activeRoles, ...activeGroupRoles],
additionalPrivileges: activeAdditionalPrivileges
additionalPrivileges: [...activeAdditionalPrivileges, ...activeGroupAdditionalPrivileges]
};
} catch (error) {
throw new DatabaseError({ error, name: "GetProjectPermission" });

@ -90,6 +90,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
// This is fine. This service is only used for direct user privileges, not group-based privileges
if (!userPrivilege.projectMembershipId)
throw new BadRequestError({ message: "Operation not supported for groups" });
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
@ -138,6 +142,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
// This is fine. This service is only used for direct user privileges, not group-based privileges
if (!userPrivilege.projectMembershipId)
throw new BadRequestError({ message: "Operation not supported for groups" });
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
@ -164,6 +172,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
// This is fine. This service is only used for direct user privileges, not group-based privileges
if (!userPrivilege.projectMembershipId)
throw new BadRequestError({ message: "Operation not supported for groups" });
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });

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

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

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

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

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

@ -20,7 +20,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalPolicy}.id`,
`${TableName.SecretApprovalPolicyApprover}.policyId`
)
.select(tx.ref("approverId").withSchema(TableName.SecretApprovalPolicyApprover))
.select(tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
@ -33,18 +33,18 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
const doc = await sapFindQuery(tx || db, {
[`${TableName.SecretApprovalPolicy}.id` as "id"]: id
});
const formatedDoc = mergeOneToManyRelation(
const formattedDoc = mergeOneToManyRelation(
doc,
"id",
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
({ approverUserId, envId, envName: name, envSlug: slug, ...el }) => ({
...el,
envId,
environment: { id: envId, name, slug }
}),
({ approverId }) => approverId,
({ approverUserId }) => approverUserId,
"approvers"
);
return formatedDoc?.[0];
return formattedDoc?.[0];
} catch (error) {
throw new DatabaseError({ error, name: "FindById" });
}
@ -53,22 +53,31 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
const find = async (filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, tx?: Knex) => {
try {
const docs = await sapFindQuery(tx || db, filter);
const formatedDoc = mergeOneToManyRelation(
const formattedDoc = mergeOneToManyRelation(
docs,
"id",
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
({ approverUserId, envId, envName: name, envSlug: slug, ...el }) => ({
...el,
envId,
environment: { id: envId, name, slug }
}),
({ approverId }) => approverId,
({ approverUserId }) => approverUserId,
"approvers"
);
return formatedDoc;
return formattedDoc;
} catch (error) {
throw new DatabaseError({ error, name: "Find" });
}
};
return { ...secretApprovalPolicyOrm, findById, find };
const findByProjectIds = async (projectIds: string[], tx?: Knex) => {
const policies = await (tx || db)(TableName.SecretApprovalPolicy)
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.whereIn(`${TableName.Environment}.projectId`, projectIds)
.select(selectAllTableCols(TableName.SecretApprovalPolicy));
return policies;
};
return { ...secretApprovalPolicyOrm, findById, find, findByProjectIds };
};

@ -7,6 +7,7 @@ import { BadRequestError } from "@app/lib/errors";
import { containsGlobPatterns } from "@app/lib/picomatch";
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TUserDALFactory } from "@app/services/user/user-dal";
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
@ -29,6 +30,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
userDAL: Pick<TUserDALFactory, "findUsersByProjectId" | "findUserByProjectId">;
};
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
@ -38,7 +40,7 @@ export const secretApprovalPolicyServiceFactory = ({
permissionService,
secretApprovalPolicyApproverDAL,
projectEnvDAL,
projectMembershipDAL
userDAL
}: TSecretApprovalPolicyServiceFactoryDep) => {
const createSecretApprovalPolicy = async ({
name,
@ -69,11 +71,12 @@ export const secretApprovalPolicyServiceFactory = ({
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
if (!env) throw new BadRequestError({ message: "Environment not found" });
const secretApprovers = await projectMembershipDAL.find({
const secretApproverUsers = await userDAL.findUsersByProjectId(
projectId,
$in: { id: approvers }
});
if (secretApprovers.length !== approvers.length)
approvers.map((approverUserId) => approverUserId)
);
if (secretApproverUsers.length !== approvers.length)
throw new BadRequestError({ message: "Approver not found in project" });
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
@ -87,8 +90,8 @@ export const secretApprovalPolicyServiceFactory = ({
tx
);
await secretApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({
approverId: id,
secretApproverUsers.map(({ id }) => ({
approverUserId: id,
policyId: doc.id
})),
tx
@ -132,21 +135,19 @@ export const secretApprovalPolicyServiceFactory = ({
tx
);
if (approvers) {
const secretApprovers = await projectMembershipDAL.find(
{
projectId: secretApprovalPolicy.projectId,
$in: { id: approvers }
},
{ tx }
const secretApproverUsers = await userDAL.findUsersByProjectId(
secretApprovalPolicy.projectId,
approvers.map((approverUserId) => approverUserId)
);
if (secretApprovers.length !== approvers.length)
if (secretApproverUsers.length !== approvers.length)
throw new BadRequestError({ message: "Approver not found in project" });
if (doc.approvals > secretApprovers.length)
if (doc.approvals > secretApproverUsers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
await secretApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({
approverId: id,
secretApproverUsers.map((user) => ({
approverUserId: user.id,
policyId: doc.id
})),
tx

@ -16,7 +16,7 @@ export type TSecretApprovalRequestDALFactory = ReturnType<typeof secretApprovalR
type TFindQueryFilter = {
projectId: string;
membershipId: string;
actorId: string;
status?: RequestState;
environment?: string;
committer?: string;
@ -49,7 +49,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
)
.select(selectAllTableCols(TableName.SecretApprovalRequest))
.select(
tx.ref("member").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerMemberId"),
tx.ref("memberUserId").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerMemberId"),
tx.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
tx.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
tx.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
@ -57,14 +57,14 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
tx.ref("approverId").withSchema(TableName.SecretApprovalPolicyApprover)
tx.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover)
);
const findById = async (id: string, tx?: Knex) => {
try {
const sql = findQuery({ [`${TableName.SecretApprovalRequest}.id` as "id"]: id }, tx || db);
const docs = await sql;
const formatedDoc = sqlNestRelationships({
const formattedDoc = sqlNestRelationships({
data: docs,
key: "id",
parentMapper: (el) => ({
@ -84,20 +84,20 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
},
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
{ key: "approverUserId", label: "approvers" as const, mapper: ({ approverUserId }) => approverUserId }
]
});
if (!formatedDoc?.[0]) return;
if (!formattedDoc?.[0]) return;
return {
...formatedDoc[0],
policy: { ...formatedDoc[0].policy, approvers: formatedDoc[0].approvers }
...formattedDoc[0],
policy: { ...formattedDoc[0].policy, approvers: formattedDoc[0].approvers }
};
} catch (error) {
throw new DatabaseError({ error, name: "FindByIdSAR" });
}
};
const findProjectRequestCount = async (projectId: string, membershipId: string, tx?: Knex) => {
const findProjectRequestCount = async (projectId: string, approverUserId: string, tx?: Knex) => {
try {
const docs = await (tx || db)
.with(
@ -110,12 +110,12 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.SecretApprovalRequest}.policyId`,
`${TableName.SecretApprovalPolicyApprover}.policyId`
)
.where({ projectId })
.where({ [`${TableName.Environment}.projectId` as "projectId"]: projectId })
.andWhere(
(bd) =>
void bd
.where(`${TableName.SecretApprovalPolicyApprover}.approverId`, membershipId)
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, approverUserId)
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, approverUserId)
)
.select("status", `${TableName.SecretApprovalRequest}.id`)
.groupBy(`${TableName.SecretApprovalRequest}.id`, "status")
@ -142,7 +142,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
};
const findByProjectId = async (
{ status, limit = 20, offset = 0, projectId, committer, environment, membershipId }: TFindQueryFilter,
{ status, limit = 20, offset = 0, projectId, committer, environment, actorId }: TFindQueryFilter,
tx?: Knex
) => {
try {
@ -173,21 +173,21 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
)
.where(
stripUndefinedInWhere({
projectId,
[`${TableName.Environment}.projectId`]: projectId,
[`${TableName.Environment}.slug` as "slug"]: environment,
[`${TableName.SecretApprovalRequest}.status`]: status,
committerId: committer
committerUserId: committer
})
)
.andWhere(
(bd) =>
void bd
.where(`${TableName.SecretApprovalPolicyApprover}.approverId`, membershipId)
.orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId)
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, actorId)
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, actorId)
)
.select(selectAllTableCols(TableName.SecretApprovalRequest))
.select(
db.ref("projectId").withSchema(TableName.Environment),
db.ref("projectId").withSchema(TableName.Environment).as("envProjectId"),
db.ref("slug").withSchema(TableName.Environment).as("environment"),
db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerMemberId"),
db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
@ -201,7 +201,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
),
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
db.ref("approverId").withSchema(TableName.SecretApprovalPolicyApprover)
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover)
)
.orderBy("createdAt", "desc");
@ -217,7 +217,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
parentMapper: (el) => ({
...SecretApprovalRequestsSchema.parse(el),
environment: el.environment,
projectId: el.projectId,
projectId: el.envProjectId,
policy: {
id: el.policyId,
name: el.policyName,
@ -232,9 +232,9 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
mapper: ({ reviewerMemberId: member, reviewerStatus: s }) => (member ? { member, status: s } : undefined)
},
{
key: "approverId",
key: "approverUserId",
label: "approvers" as const,
mapper: ({ approverId }) => approverId
mapper: ({ approverUserId }) => approverUserId
},
{
key: "commitId",

@ -113,7 +113,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
db.ref("secretCommentTag").withSchema(TableName.SecretVersion).as("secVerCommentTag"),
db.ref("secretCommentCiphertext").withSchema(TableName.SecretVersion).as("secVerCommentCiphertext")
);
const formatedDoc = sqlNestRelationships({
const formattedDoc = sqlNestRelationships({
data: doc,
key: "id",
parentMapper: (data) => SecretApprovalRequestsSecretsSchema.omit({ secretVersion: true }).parse(data),
@ -212,7 +212,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
}
]
});
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
return formattedDoc?.map(({ secret, secretVersion, ...el }) => ({
...el,
secret: secret?.[0],
secretVersion: secretVersion?.[0]

@ -85,7 +85,7 @@ export const secretApprovalRequestServiceFactory = ({
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { membership } = await permissionService.getProjectPermission(
await permissionService.getProjectPermission(
actor as ActorType.USER,
actorId,
projectId,
@ -93,7 +93,7 @@ export const secretApprovalRequestServiceFactory = ({
actorOrgId
);
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, membership.id);
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
return count;
};
@ -111,19 +111,14 @@ export const secretApprovalRequestServiceFactory = ({
}: TListApprovalsDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { membership } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
const approvals = await secretApprovalRequestDAL.findByProjectId({
projectId,
committer,
environment,
status,
membershipId: membership.id,
actorId,
limit,
offset
});
@ -143,7 +138,7 @@ export const secretApprovalRequestServiceFactory = ({
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
const { policy } = secretApprovalRequest;
const { membership, hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission(
actor,
actorId,
secretApprovalRequest.projectId,
@ -152,8 +147,8 @@ export const secretApprovalRequestServiceFactory = ({
);
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
secretApprovalRequest.committerUserId !== actorId &&
!policy.approvers.find((approverUserId) => approverUserId === actorId)
) {
throw new UnauthorizedError({ message: "User has no access" });
}
@ -178,7 +173,7 @@ export const secretApprovalRequestServiceFactory = ({
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy } = secretApprovalRequest;
const { membership, hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
actorId,
secretApprovalRequest.projectId,
@ -187,8 +182,8 @@ export const secretApprovalRequestServiceFactory = ({
);
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
secretApprovalRequest.committerUserId !== actorId &&
!policy.approvers.find((approverUserId) => approverUserId === actorId)
) {
throw new UnauthorizedError({ message: "User has no access" });
}
@ -196,7 +191,7 @@ export const secretApprovalRequestServiceFactory = ({
const review = await secretApprovalRequestReviewerDAL.findOne(
{
requestId: secretApprovalRequest.id,
member: membership.id
memberUserId: actorId
},
tx
);
@ -205,7 +200,7 @@ export const secretApprovalRequestServiceFactory = ({
{
status,
requestId: secretApprovalRequest.id,
member: membership.id
memberUserId: actorId
},
tx
);
@ -228,7 +223,7 @@ export const secretApprovalRequestServiceFactory = ({
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy } = secretApprovalRequest;
const { membership, hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
actorId,
secretApprovalRequest.projectId,
@ -237,8 +232,8 @@ export const secretApprovalRequestServiceFactory = ({
);
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
secretApprovalRequest.committerUserId !== actorId &&
!policy.approvers.find((approverUserId) => approverUserId === actorId)
) {
throw new UnauthorizedError({ message: "User has no access" });
}
@ -251,8 +246,9 @@ export const secretApprovalRequestServiceFactory = ({
const updatedRequest = await secretApprovalRequestDAL.updateById(secretApprovalRequest.id, {
status,
statusChangeBy: membership.id
statusChangeByUserId: actorId
});
return { ...secretApprovalRequest, ...updatedRequest };
};
@ -268,7 +264,7 @@ export const secretApprovalRequestServiceFactory = ({
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
const { policy, folderId, projectId } = secretApprovalRequest;
const { membership, hasRole } = await permissionService.getProjectPermission(
const { hasRole } = await permissionService.getProjectPermission(
ActorType.USER,
actorId,
projectId,
@ -278,8 +274,8 @@ export const secretApprovalRequestServiceFactory = ({
if (
!hasRole(ProjectMembershipRole.Admin) &&
secretApprovalRequest.committerId !== membership.id &&
!policy.approvers.find((approverId) => approverId === membership.id)
secretApprovalRequest.committerUserId !== actorId &&
!policy.approvers.find((approverUserId) => approverUserId === actorId)
) {
throw new UnauthorizedError({ message: "User has no access" });
}
@ -290,7 +286,7 @@ export const secretApprovalRequestServiceFactory = ({
const hasMinApproval =
secretApprovalRequest.policy.approvals <=
secretApprovalRequest.policy.approvers.filter(
(approverId) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
(approverUserId) => reviewers[approverUserId.toString()] === ApprovalStatus.APPROVED
).length;
if (!hasMinApproval) throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
@ -445,7 +441,7 @@ export const secretApprovalRequestServiceFactory = ({
conflicts: JSON.stringify(conflicts),
hasMerged: true,
status: RequestState.Closed,
statusChangeBy: membership.id
statusChangeByUserId: actorId
},
tx
);
@ -480,7 +476,7 @@ export const secretApprovalRequestServiceFactory = ({
}: TGenerateSecretApprovalRequestDTO) => {
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
const { permission, membership } = await permissionService.getProjectPermission(
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
@ -634,7 +630,7 @@ export const secretApprovalRequestServiceFactory = ({
policyId: policy.id,
status: "open",
hasMerged: false,
committerId: membership.id
committerUserId: actorId
},
tx
);

@ -92,18 +92,6 @@ export const UNIVERSAL_AUTH = {
}
} as const;
export const AWS_IAM_AUTH = {
LOGIN: {
identityId: "The ID of the identity to login.",
iamHttpRequestMethod: "The HTTP request method used in the signed request.",
iamRequestUrl:
"The base64-encoded HTTP URL used in the signed request. Most likely, the base64-encoding of https://sts.amazonaws.com/",
iamRequestBody:
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
}
} as const;
export const ORGANIZATIONS = {
LIST_USER_MEMBERSHIPS: {
organizationId: "The ID of the organization to get memberships from."
@ -284,7 +272,6 @@ export const SECRETS = {
export const RAW_SECRETS = {
LIST: {
expand: "Whether or not to expand secret references",
recursive:
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
workspaceId: "The ID of the project to list secrets from.",
@ -477,21 +464,12 @@ export const SECRET_TAGS = {
export const IDENTITY_ADDITIONAL_PRIVILEGE = {
CREATE: {
projectSlug: "The slug of the project of the identity in.",
identityId: "The ID of the identity to create.",
identityId: "The ID of the identity to delete.",
slug: "The slug of the privilege to create.",
permissions: `The permission object for the privilege.
- Read secrets
\`\`\`
{ "permissions": [{"action": "read", "subject": "secrets"]}
\`\`\`
- Read and Write secrets
\`\`\`
{ "permissions": [{"action": "read", "subject": "secrets"], {"action": "write", "subject": "secrets"]}
\`\`\`
- Read secrets scoped to an environment and secret path
\`\`\`
- { "permissions": [{"action": "read", "subject": "secrets", "conditions": { "environment": "dev", "secretPath": { "$glob": "/" } }}] }
\`\`\`
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]]
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]]
2. [["read", "secrets", {environment: "dev"}]]
`,
isPackPermission: "Whether the server should pack(compact) the permission object.",
isTemporary: "Whether the privilege is temporary.",
@ -505,19 +483,11 @@ export const IDENTITY_ADDITIONAL_PRIVILEGE = {
slug: "The slug of the privilege to update.",
newSlug: "The new slug of the privilege to update.",
permissions: `The permission object for the privilege.
- Read secrets
\`\`\`
{ "permissions": [{"action": "read", "subject": "secrets"]}
\`\`\`
- Read and Write secrets
\`\`\`
{ "permissions": [{"action": "read", "subject": "secrets"], {"action": "write", "subject": "secrets"]}
\`\`\`
- Read secrets scoped to an environment and secret path
\`\`\`
- { "permissions": [{"action": "read", "subject": "secrets", "conditions": { "environment": "dev", "secretPath": { "$glob": "/" } }}] }
\`\`\`
1. [["read", "secrets", {environment: "dev", secretPath: {$glob: "/"}}]]
2. [["read", "secrets", {environment: "dev"}], ["create", "secrets", {environment: "dev"}]]
2. [["read", "secrets", {environment: "dev"}]]
`,
isPackPermission: "Whether the server should pack(compact) the permission object.",
isTemporary: "Whether the privilege is temporary.",
temporaryMode: "Type of temporary access given. Types: relative",
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
@ -625,8 +595,7 @@ export const INTEGRATION = {
shouldAutoRedeploy: "Used by Render to trigger auto deploy.",
secretGCPLabel: "The label for GCP secrets.",
secretAWSTag: "The tags for AWS secrets.",
kmsKeyId: "The ID of the encryption key from AWS KMS.",
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store."
kmsKeyId: "The ID of the encryption key from AWS KMS."
}
},
UPDATE: {

@ -22,6 +22,7 @@ import { dynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secre
import { groupDALFactory } from "@app/ee/services/group/group-dal";
import { groupServiceFactory } from "@app/ee/services/group/group-service";
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { groupProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/group-project-user-additional-privilege/group-project-user-additional-privilege-dal";
import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal";
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
@ -78,8 +79,6 @@ import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
import { identityServiceFactory } from "@app/services/identity/identity-service";
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
import { identityAwsIamAuthDALFactory } from "@app/services/identity-aws-iam-auth/identity-aws-iam-auth-dal";
import { identityAwsIamAuthServiceFactory } from "@app/services/identity-aws-iam-auth/identity-aws-iam-auth-service";
import { identityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
import { identityProjectMembershipRoleDALFactory } from "@app/services/identity-project/identity-project-membership-role-dal";
import { identityProjectServiceFactory } from "@app/services/identity-project/identity-project-service";
@ -96,7 +95,6 @@ import { orgDALFactory } from "@app/services/org/org-dal";
import { orgRoleDALFactory } from "@app/services/org/org-role-dal";
import { orgRoleServiceFactory } from "@app/services/org/org-role-service";
import { orgServiceFactory } from "@app/services/org/org-service";
import { orgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { projectDALFactory } from "@app/services/project/project-dal";
import { projectQueueFactory } from "@app/services/project/project-queue";
import { projectServiceFactory } from "@app/services/project/project-service";
@ -164,7 +162,6 @@ export const registerRoutes = async (
const authDAL = authDALFactory(db);
const authTokenDAL = tokenDALFactory(db);
const orgDAL = orgDALFactory(db);
const orgMembershipDAL = orgMembershipDALFactory(db);
const orgBotDAL = orgBotDALFactory(db);
const incidentContactDAL = incidentContactDALFactory(db);
const orgRoleDAL = orgRoleDALFactory(db);
@ -203,7 +200,6 @@ export const registerRoutes = async (
const identityUaDAL = identityUaDALFactory(db);
const identityUaClientSecretDAL = identityUaClientSecretDALFactory(db);
const identityAwsIamAuthDAL = identityAwsIamAuthDALFactory(db);
const auditLogDAL = auditLogDALFactory(db);
const auditLogStreamDAL = auditLogStreamDALFactory(db);
@ -222,6 +218,8 @@ export const registerRoutes = async (
const accessApprovalPolicyApproverDAL = accessApprovalPolicyApproverDALFactory(db);
const accessApprovalRequestReviewerDAL = accessApprovalRequestReviewerDALFactory(db);
const groupProjectUserAdditionalPrivilegeDAL = groupProjectUserAdditionalPrivilegeDALFactory(db);
const sapApproverDAL = secretApprovalPolicyApproverDALFactory(db);
const secretApprovalPolicyDAL = secretApprovalPolicyDALFactory(db);
const secretApprovalRequestDAL = secretApprovalRequestDALFactory(db);
@ -235,10 +233,10 @@ export const registerRoutes = async (
const gitAppInstallSessionDAL = gitAppInstallSessionDALFactory(db);
const gitAppOrgDAL = gitAppDALFactory(db);
const groupDAL = groupDALFactory(db);
const userGroupMembershipDAL = userGroupMembershipDALFactory(db);
const groupDAL = groupDALFactory(db, userGroupMembershipDAL);
const groupProjectDAL = groupProjectDALFactory(db);
const groupProjectMembershipRoleDAL = groupProjectMembershipRoleDALFactory(db);
const userGroupMembershipDAL = userGroupMembershipDALFactory(db);
const secretScanningDAL = secretScanningDALFactory(db);
const licenseDAL = licenseDALFactory(db);
const dynamicSecretDAL = dynamicSecretDALFactory(db);
@ -276,32 +274,31 @@ export const registerRoutes = async (
projectMembershipDAL,
projectEnvDAL,
secretApprovalPolicyApproverDAL: sapApproverDAL,
userDAL,
permissionService,
secretApprovalPolicyDAL
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const samlService = samlConfigServiceFactory({
permissionService,
orgBotDAL,
orgDAL,
orgMembershipDAL,
userDAL,
userAliasDAL,
samlConfigDAL,
licenseService,
tokenService,
smtpService
licenseService
});
const groupService = groupServiceFactory({
userDAL,
groupDAL,
groupProjectDAL,
orgDAL,
secretApprovalPolicyDAL,
userGroupMembershipDAL,
projectDAL,
projectBotDAL,
projectKeyDAL,
secretApprovalRequestDAL,
accessApprovalRequestDAL,
permissionService,
licenseService
});
@ -309,7 +306,10 @@ export const registerRoutes = async (
groupDAL,
groupProjectDAL,
groupProjectMembershipRoleDAL,
secretApprovalPolicyDAL,
secretApprovalRequestDAL,
userGroupMembershipDAL,
accessApprovalRequestDAL,
projectDAL,
projectKeyDAL,
projectBotDAL,
@ -320,13 +320,14 @@ export const registerRoutes = async (
licenseService,
scimDAL,
userDAL,
userAliasDAL,
orgDAL,
orgMembershipDAL,
projectDAL,
projectMembershipDAL,
groupDAL,
secretApprovalPolicyDAL,
groupProjectDAL,
secretApprovalRequestDAL,
accessApprovalRequestDAL,
userGroupMembershipDAL,
projectKeyDAL,
projectBotDAL,
@ -338,9 +339,11 @@ export const registerRoutes = async (
ldapConfigDAL,
ldapGroupMapDAL,
orgDAL,
orgMembershipDAL,
orgBotDAL,
secretApprovalPolicyDAL,
groupDAL,
secretApprovalRequestDAL,
accessApprovalRequestDAL,
groupProjectDAL,
projectKeyDAL,
projectDAL,
@ -362,13 +365,8 @@ export const registerRoutes = async (
queueService
});
const userService = userServiceFactory({
userDAL,
userAliasDAL,
orgMembershipDAL,
tokenService,
smtpService
});
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL });
const userService = userServiceFactory({ userDAL });
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL });
const passwordService = authPaswordServiceFactory({
tokenService,
@ -377,7 +375,6 @@ export const registerRoutes = async (
userDAL
});
const orgService = orgServiceFactory({
userAliasDAL,
licenseService,
samlConfigDAL,
orgRoleDAL,
@ -438,6 +435,7 @@ export const registerRoutes = async (
projectUserMembershipRoleDAL,
projectDAL,
permissionService,
groupProjectDAL,
projectBotDAL,
orgDAL,
userDAL,
@ -618,7 +616,7 @@ export const registerRoutes = async (
accessApprovalPolicyApproverDAL,
permissionService,
projectEnvDAL,
projectMembershipDAL,
userDAL,
projectDAL
});
@ -627,6 +625,7 @@ export const registerRoutes = async (
permissionService,
accessApprovalRequestReviewerDAL,
additionalPrivilegeDAL: projectUserAdditionalPrivilegeDAL,
groupAdditionalPrivilegeDAL: groupProjectUserAdditionalPrivilegeDAL,
projectMembershipDAL,
accessApprovalPolicyDAL,
accessApprovalRequestDAL,
@ -702,14 +701,6 @@ export const registerRoutes = async (
identityUaDAL,
licenseService
});
const identityAWSIAMAuthService = identityAwsIamAuthServiceFactory({
identityAccessTokenDAL,
identityAwsIamAuthDAL,
identityOrgMembershipDAL,
identityDAL,
licenseService,
permissionService
});
const dynamicSecretProviders = buildDynamicSecretProviders();
const dynamicSecretQueueService = dynamicSecretLeaseQueueServiceFactory({
@ -779,7 +770,6 @@ export const registerRoutes = async (
identityAccessToken: identityAccessTokenService,
identityProject: identityProjectService,
identityUa: identityUaService,
identityAwsIamAuth: identityAWSIAMAuthService,
secretApprovalPolicy: sapService,
accessApprovalPolicy: accessApprovalPolicyService,
accessApprovalRequest: accessApprovalRequestService,

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

@ -42,9 +42,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
schema: {
body: z.object({
allowSignUp: z.boolean().optional(),
allowedSignUpDomain: z.string().optional().nullable(),
trustSamlEmails: z.boolean().optional(),
trustLdapEmails: z.boolean().optional()
allowedSignUpDomain: z.string().optional().nullable()
}),
response: {
200: z.object({

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

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

@ -66,8 +66,7 @@ export const registerIntegrationRouter = async (server: FastifyZodProvider) => {
)
.optional()
.describe(INTEGRATION.CREATE.metadata.secretAWSTag),
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId),
shouldDisableDelete: z.boolean().optional().describe(INTEGRATION.CREATE.metadata.shouldDisableDelete)
kmsKeyId: z.string().optional().describe(INTEGRATION.CREATE.metadata.kmsKeyId)
})
.default({})
}),

@ -68,9 +68,16 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
params: z.object({
workspaceId: z.string().trim()
}),
querystring: z.object({
includeGroupMembers: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
}),
response: {
200: z.object({
users: ProjectMembershipsSchema.extend({
isGroupMember: z.boolean(),
user: UsersSchema.pick({
email: true,
username: true,
@ -104,6 +111,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
actorId: req.permission.id,
actor: req.permission.type,
actorAuthMethod: req.permission.authMethod,
includeGroupMembers: req.query.includeGroupMembers,
projectId: req.params.workspaceId,
actorOrgId: req.permission.orgId
});

@ -2,52 +2,11 @@ import { z } from "zod";
import { AuthTokenSessionsSchema, OrganizationsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { ApiKeysSchema } from "@app/db/schemas/api-keys";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMethod, AuthMode } from "@app/services/auth/auth-type";
export const registerUserRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/me/emails/code",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
username: z.string().trim()
}),
response: {
200: z.object({})
}
},
handler: async (req) => {
await server.services.user.sendEmailVerificationCode(req.body.username);
return {};
}
});
server.route({
method: "POST",
url: "/me/emails/verify",
config: {
rateLimit: authRateLimit
},
schema: {
body: z.object({
username: z.string().trim(),
code: z.string().trim()
}),
response: {
200: z.object({})
}
},
handler: async (req) => {
await server.services.user.verifyEmailVerificationCode(req.body.username, req.body.code);
return {};
}
});
server.route({
method: "PATCH",
url: "/me/mfa",

@ -166,11 +166,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
expandSecretReferences: z
.enum(["true", "false"])
.default("false")
.transform((value) => value === "true")
.describe(RAW_SECRETS.LIST.expand),
recursive: z
.enum(["true", "false"])
.default("false")
@ -238,7 +233,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
actor: req.permission.type,
actorOrgId: req.permission.orgId,
environment,
expandSecretReferences: req.query.expandSecretReferences,
actorAuthMethod: req.permission.authMethod,
projectId: workspaceId,
path: secretPath,
@ -921,7 +915,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
committedByUser: approval.committerUserId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
@ -1105,7 +1099,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
committedByUser: approval.committerUserId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
@ -1236,14 +1230,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
]
}
});
await server.services.auditLog.createAuditLog({
projectId: req.body.workspaceId,
...req.auditLogInfo,
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
committedByUser: approval.committerUserId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
@ -1369,7 +1362,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
committedByUser: approval.committerUserId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
@ -1496,7 +1489,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
committedByUser: approval.committerUserId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}
@ -1610,7 +1603,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.SECRET_APPROVAL_REQUEST,
metadata: {
committedBy: approval.committerId,
committedByUser: approval.committerUserId,
secretApprovalRequestId: approval.id,
secretApprovalRequestSlug: approval.slug
}

@ -27,17 +27,10 @@ export const getTokenConfig = (tokenType: TokenType) => {
const expiresAt = new Date(new Date().getTime() + 86400000);
return { token, expiresAt };
}
case TokenType.TOKEN_EMAIL_VERIFICATION: {
// generate random 6-digit code
const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1));
const triesLeft = 3;
const expiresAt = new Date(new Date().getTime() + 86400000);
return { token, triesLeft, expiresAt };
}
case TokenType.TOKEN_EMAIL_MFA: {
// generate random 6-digit code
const token = String(crypto.randomInt(10 ** 5, 10 ** 6 - 1));
const triesLeft = 3;
const triesLeft = 5;
const expiresAt = new Date(new Date().getTime() + 300000);
return { token, triesLeft, expiresAt };
}

@ -1,6 +1,5 @@
export enum TokenType {
TOKEN_EMAIL_CONFIRMATION = "emailConfirmation",
TOKEN_EMAIL_VERIFICATION = "emailVerification", // unverified -> verified
TOKEN_EMAIL_MFA = "emailMfa",
TOKEN_EMAIL_ORG_INVITATION = "organizationInvitation",
TOKEN_EMAIL_PASSWORD_RESET = "passwordReset"

@ -361,7 +361,6 @@ export const authLoginServiceFactory = ({
user = await userDAL.create({
username: email,
email,
isEmailVerified: true,
firstName,
lastName,
authMethods: [authMethod],
@ -375,8 +374,6 @@ export const authLoginServiceFactory = ({
authTokenType: AuthTokenType.PROVIDER_TOKEN,
userId: user.id,
username: user.username,
email: user.email,
isEmailVerified: user.isEmailVerified,
firstName: user.firstName,
lastName: user.lastName,
authMethod,

@ -1,6 +1,6 @@
import jwt from "jsonwebtoken";
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
import { OrgMembershipStatus } from "@app/db/schemas";
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@ -80,7 +80,7 @@ export const authSignupServiceFactory = ({
});
await smtpService.sendMail({
template: SmtpTemplates.SignupEmailVerification,
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email as string],
substitutions: {
@ -102,8 +102,6 @@ export const authSignupServiceFactory = ({
code
});
await userDAL.updateById(user.id, { isEmailVerified: true });
// generate jwt token this is a temporary token
const jwtToken = jwt.sign(
{
@ -171,11 +169,12 @@ export const authSignupServiceFactory = ({
tx
);
// If it's SAML Auth and the organization ID is present, we should check if the user has a pending invite for this org, and accept it
if ((isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && organizationId) {
if (isAuthMethodSaml(authMethod) && organizationId) {
const [pendingOrgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
inviteEmail: email,
userId: user.id,
status: OrgMembershipStatus.Invited,
[`${TableName.OrgMembership}.orgId` as "orgId"]: organizationId
orgId: organizationId
});
if (pendingOrgMembership) {

@ -1,7 +1,7 @@
import { Knex } from "knex";
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, sqlNestRelationships } from "@app/lib/knex";
@ -10,6 +10,100 @@ export type TGroupProjectDALFactory = ReturnType<typeof groupProjectDALFactory>;
export const groupProjectDALFactory = (db: TDbClient) => {
const groupProjectOrm = ormify(db, TableName.GroupProjectMembership);
// The GroupProjectMembership table has a reference to the project (projectId) AND the group (groupId).
// We need to join the GroupProjectMembership table with the Groups table to get the group name and slug.
// We also need to join the GroupProjectMembershipRole table to get the role of the group in the project.
const findAllProjectGroupMembers = async (projectId: string) => {
const docs = await db(TableName.UserGroupMembership)
// Join the GroupProjectMembership table with the Groups table to get the group name and slug.
.join(
TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.GroupProjectMembership}.groupId` // this gives us access to the project id in the group membership
)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.join<TUserEncryptionKeys>(
TableName.UserEncryptionKey,
`${TableName.UserEncryptionKey}.userId`,
`${TableName.Users}.id`
)
.join(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.select(
db.ref("id").withSchema(TableName.GroupProjectMembership),
db.ref("isGhost").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("email").withSchema(TableName.Users),
db.ref("publicKey").withSchema(TableName.UserEncryptionKey),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
db.ref("id").withSchema(TableName.Users).as("userId"),
db.ref("role").withSchema(TableName.GroupProjectMembershipRole),
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("membershipRoleId"),
db.ref("customRoleId").withSchema(TableName.GroupProjectMembershipRole),
db.ref("name").withSchema(TableName.ProjectRoles).as("customRoleName"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("temporaryMode").withSchema(TableName.GroupProjectMembershipRole),
db.ref("isTemporary").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryRange").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryAccessStartTime").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryAccessEndTime").withSchema(TableName.GroupProjectMembershipRole)
)
.where({ isGhost: false });
const members = sqlNestRelationships({
data: docs,
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId }) => ({
isGroupMember: true,
id,
userId,
projectId,
user: { email, username, firstName, lastName, id: userId, publicKey, isGhost }
}),
key: "id",
childrenMapper: [
{
label: "roles" as const,
key: "membershipRoleId",
mapper: ({
role,
customRoleId,
customRoleName,
customRoleSlug,
membershipRoleId,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
}) => ({
id: membershipRoleId,
role,
customRoleId,
customRoleName,
customRoleSlug,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
})
}
]
});
return members;
};
const findByProjectId = async (projectId: string, tx?: Knex) => {
try {
const docs = await (tx || db)(TableName.GroupProjectMembership)
@ -95,5 +189,5 @@ export const groupProjectDALFactory = (db: TDbClient) => {
}
};
return { ...groupProjectOrm, findByProjectId };
return { ...groupProjectOrm, findByProjectId, findAllProjectGroupMembers };
};

@ -2,8 +2,11 @@ import { ForbiddenError } from "@casl/ability";
import ms from "ms";
import { ProjectMembershipRole, SecretKeyEncoding } from "@app/db/schemas";
import { TAccessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretApprovalPolicyDALFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-dal";
import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-dal";
import { isAtLeastAsPrivileged } from "@app/lib/casl";
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
@ -39,6 +42,9 @@ type TGroupProjectServiceFactoryDep = {
projectBotDAL: TProjectBotDALFactory;
groupDAL: Pick<TGroupDALFactory, "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getProjectPermissionByRole">;
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "delete">;
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findByProjectIds">;
secretApprovalRequestDAL: Pick<TSecretApprovalRequestDALFactory, "delete">;
};
export type TGroupProjectServiceFactory = ReturnType<typeof groupProjectServiceFactory>;
@ -48,6 +54,9 @@ export const groupProjectServiceFactory = ({
groupProjectDAL,
groupProjectMembershipRoleDAL,
userGroupMembershipDAL,
secretApprovalRequestDAL,
secretApprovalPolicyDAL,
accessApprovalRequestDAL,
projectDAL,
projectKeyDAL,
projectBotDAL,
@ -277,7 +286,8 @@ export const groupProjectServiceFactory = ({
if (!group) throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
const groupProjectMembership = await groupProjectDAL.findOne({ groupId: group.id, projectId: project.id });
if (!groupProjectMembership) throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
if (!groupProjectMembership.id)
throw new BadRequestError({ message: `Failed to find group with slug ${groupSlug}` });
const { permission } = await permissionService.getProjectPermission(
actor,
@ -289,8 +299,34 @@ export const groupProjectServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
const deletedProjectGroup = await groupProjectDAL.transaction(async (tx) => {
// This is group members that do not have individual access to the project (A.K.A members that don't have a normal project membership)
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group.id, project.id, tx);
// Delete all access approvals by the group members
await accessApprovalRequestDAL.delete(
{
groupMembershipId: groupProjectMembership.id,
$in: {
requestedByUserId: groupMembers.map((member) => member.user.id)
}
},
tx
);
const secretApprovalPolicies = await secretApprovalPolicyDAL.findByProjectIds([project.id], tx);
// Delete any secret approvals by the group members
await secretApprovalRequestDAL.delete(
{
$in: {
policyId: secretApprovalPolicies.map((policy) => policy.id),
committerUserId: groupMembers.map((member) => member.user.id)
}
},
tx
);
if (groupMembers.length) {
await projectKeyDAL.delete(
{

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

@ -1,67 +0,0 @@
/**
* Extracts the identity ARN from the GetCallerIdentity response to one of the following formats:
* - arn:aws:iam::123456789012:user/MyUserName
* - arn:aws:iam::123456789012:role/MyRoleName
*/
export const extractPrincipalArn = (arn: string) => {
// split the ARN into parts using ":" as the delimiter
const fullParts = arn.split(":");
if (fullParts.length !== 6) {
throw new Error(`Unrecognized ARN: contains ${fullParts.length} colon-separated parts, expected 6`);
}
const [prefix, partition, service, , accountNumber, resource] = fullParts;
if (prefix !== "arn") {
throw new Error('Unrecognized ARN: does not begin with "arn:"');
}
// structure to hold the parsed data
const entity = {
Partition: partition,
Service: service,
AccountNumber: accountNumber,
Type: "",
Path: "",
FriendlyName: "",
SessionInfo: ""
};
// validate the service is either 'iam' or 'sts'
if (entity.Service !== "iam" && entity.Service !== "sts") {
throw new Error(`Unrecognized service: ${entity.Service}, not one of iam or sts`);
}
// parse the last part of the ARN which describes the resource
const parts = resource.split("/");
if (parts.length < 2) {
throw new Error(`Unrecognized ARN: "${resource}" contains fewer than 2 slash-separated parts`);
}
const [type, ...rest] = parts;
entity.Type = type;
entity.FriendlyName = parts[parts.length - 1];
// handle different types of resources
switch (entity.Type) {
case "assumed-role": {
if (rest.length < 2) {
throw new Error(`Unrecognized ARN: "${resource}" contains fewer than 3 slash-separated parts`);
}
// assumed roles use a special format where the friendly name is the role name
const [roleName, sessionId] = rest;
entity.Type = "role"; // treat assumed role case as role
entity.FriendlyName = roleName;
entity.SessionInfo = sessionId;
break;
}
case "user":
case "role":
case "instance-profile":
// standard cases: just join back the path if there's any
entity.Path = rest.slice(0, -1).join("/");
break;
default:
throw new Error(`Unrecognized principal type: "${entity.Type}"`);
}
return `arn:aws:iam::${entity.AccountNumber}:${entity.Type}/${entity.FriendlyName}`;
};

@ -1,315 +0,0 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { ForbiddenError } from "@casl/ability";
import axios from "axios";
import jwt from "jsonwebtoken";
import { IdentityAuthMethod } from "@app/db/schemas";
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 { getConfig } from "@app/lib/config/env";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
import { AuthTokenType } from "../auth/auth-type";
import { TIdentityDALFactory } from "../identity/identity-dal";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identity-access-token-dal";
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
import { TIdentityAwsIamAuthDALFactory } from "./identity-aws-iam-auth-dal";
import { extractPrincipalArn } from "./identity-aws-iam-auth-fns";
import {
TAttachAWSIAMAuthDTO,
TAWSGetCallerIdentityHeaders,
TGetAWSIAMAuthDTO,
TGetCallerIdentityResponse,
TLoginAWSIAMAuthDTO,
TUpdateAWSIAMAuthDTO
} from "./identity-aws-iam-auth-types";
type TIdentityAwsIamAuthServiceFactoryDep = {
identityAccessTokenDAL: Pick<TIdentityAccessTokenDALFactory, "create">;
identityAwsIamAuthDAL: Pick<TIdentityAwsIamAuthDALFactory, "findOne" | "transaction" | "create" | "updateById">;
identityOrgMembershipDAL: Pick<TIdentityOrgDALFactory, "findOne">;
identityDAL: Pick<TIdentityDALFactory, "updateById">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
};
export type TIdentityAwsIamAuthServiceFactory = ReturnType<typeof identityAwsIamAuthServiceFactory>;
export const identityAwsIamAuthServiceFactory = ({
identityAccessTokenDAL,
identityAwsIamAuthDAL,
identityOrgMembershipDAL,
identityDAL,
licenseService,
permissionService
}: TIdentityAwsIamAuthServiceFactoryDep) => {
const login = async ({
identityId,
iamHttpRequestMethod,
iamRequestBody,
iamRequestHeaders
}: TLoginAWSIAMAuthDTO) => {
const identityAwsIamAuth = await identityAwsIamAuthDAL.findOne({ identityId });
if (!identityAwsIamAuth) throw new UnauthorizedError();
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId: identityAwsIamAuth.identityId });
const headers: TAWSGetCallerIdentityHeaders = JSON.parse(Buffer.from(iamRequestHeaders, "base64").toString());
const body: string = Buffer.from(iamRequestBody, "base64").toString();
const {
data: {
GetCallerIdentityResponse: {
GetCallerIdentityResult: { Account, Arn }
}
}
}: { data: TGetCallerIdentityResponse } = await axios({
method: iamHttpRequestMethod,
url: identityAwsIamAuth.stsEndpoint,
headers,
data: body
});
if (identityAwsIamAuth.allowedAccountIds) {
// validate if Account is in the list of allowed Account IDs
const isAccountAllowed = identityAwsIamAuth.allowedAccountIds
.split(",")
.map((accountId) => accountId.trim())
.some((accountId) => accountId === Account);
if (!isAccountAllowed) throw new UnauthorizedError();
}
if (identityAwsIamAuth.allowedPrincipalArns) {
// validate if Arn is in the list of allowed Principal ARNs
const isArnAllowed = identityAwsIamAuth.allowedPrincipalArns
.split(",")
.map((principalArn) => principalArn.trim())
.some((principalArn) => {
// convert wildcard ARN to a regular expression: "arn:aws:iam::123456789012:*" -> "^arn:aws:iam::123456789012:.*$"
// considers exact matches + wildcard matches
const regex = new RegExp(`^${principalArn.replace(/\*/g, ".*")}$`);
return regex.test(extractPrincipalArn(Arn));
});
if (!isArnAllowed) throw new UnauthorizedError();
}
const identityAccessToken = await identityAwsIamAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create(
{
identityId: identityAwsIamAuth.identityId,
isAccessTokenRevoked: false,
accessTokenTTL: identityAwsIamAuth.accessTokenTTL,
accessTokenMaxTTL: identityAwsIamAuth.accessTokenMaxTTL,
accessTokenNumUses: 0,
accessTokenNumUsesLimit: identityAwsIamAuth.accessTokenNumUsesLimit
},
tx
);
return newToken;
});
const appCfg = getConfig();
const accessToken = jwt.sign(
{
identityId: identityAwsIamAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
{
expiresIn:
Number(identityAccessToken.accessTokenMaxTTL) === 0
? undefined
: Number(identityAccessToken.accessTokenMaxTTL)
}
);
return { accessToken, identityAwsIamAuth, identityAccessToken, identityMembershipOrg };
};
const attachAwsIamAuth = async ({
identityId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TAttachAWSIAMAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity.authMethod)
throw new BadRequestError({
message: "Failed to add AWS IAM Auth to already configured identity"
});
if (accessTokenMaxTTL > 0 && accessTokenTTL > accessTokenMaxTTL) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
accessTokenTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(accessTokenTrustedIp.ipAddress);
});
const identityAwsIamAuth = await identityAwsIamAuthDAL.transaction(async (tx) => {
const doc = await identityAwsIamAuthDAL.create(
{
identityId: identityMembershipOrg.identityId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps)
},
tx
);
await identityDAL.updateById(
identityMembershipOrg.identityId,
{
authMethod: IdentityAuthMethod.AWS_IAM_AUTH
},
tx
);
return doc;
});
return { ...identityAwsIamAuth, orgId: identityMembershipOrg.orgId };
};
const updateAwsIamAuth = async ({
identityId,
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TUpdateAWSIAMAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_IAM_AUTH)
throw new BadRequestError({
message: "Failed to update AWS IAM Auth"
});
const identityAwsIamAuth = await identityAwsIamAuthDAL.findOne({ identityId });
if (
(accessTokenMaxTTL || identityAwsIamAuth.accessTokenMaxTTL) > 0 &&
(accessTokenTTL || identityAwsIamAuth.accessTokenMaxTTL) >
(accessTokenMaxTTL || identityAwsIamAuth.accessTokenMaxTTL)
) {
throw new BadRequestError({ message: "Access token TTL cannot be greater than max TTL" });
}
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
const reformattedAccessTokenTrustedIps = accessTokenTrustedIps?.map((accessTokenTrustedIp) => {
if (
!plan.ipAllowlisting &&
accessTokenTrustedIp.ipAddress !== "0.0.0.0/0" &&
accessTokenTrustedIp.ipAddress !== "::/0"
)
throw new BadRequestError({
message:
"Failed to add IP access range to access token due to plan restriction. Upgrade plan to add IP access range."
});
if (!isValidIpOrCidr(accessTokenTrustedIp.ipAddress))
throw new BadRequestError({
message: "The IP is not a valid IPv4, IPv6, or CIDR block"
});
return extractIPDetails(accessTokenTrustedIp.ipAddress);
});
const updatedAwsIamAuth = await identityAwsIamAuthDAL.updateById(identityAwsIamAuth.id, {
stsEndpoint,
allowedPrincipalArns,
allowedAccountIds,
accessTokenMaxTTL,
accessTokenTTL,
accessTokenNumUsesLimit,
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
? JSON.stringify(reformattedAccessTokenTrustedIps)
: undefined
});
return { ...updatedAwsIamAuth, orgId: identityMembershipOrg.orgId };
};
const getAwsIamAuth = async ({ identityId, actorId, actor, actorAuthMethod, actorOrgId }: TGetAWSIAMAuthDTO) => {
const identityMembershipOrg = await identityOrgMembershipDAL.findOne({ identityId });
if (!identityMembershipOrg) throw new BadRequestError({ message: "Failed to find identity" });
if (identityMembershipOrg.identity?.authMethod !== IdentityAuthMethod.AWS_IAM_AUTH)
throw new BadRequestError({
message: "The identity does not have AWS IAM Auth attached"
});
const awsIamIdentityAuth = await identityAwsIamAuthDAL.findOne({ identityId });
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
identityMembershipOrg.orgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
return { ...awsIamIdentityAuth, orgId: identityMembershipOrg.orgId };
};
return {
login,
attachAwsIamAuth,
updateAwsIamAuth,
getAwsIamAuth
};
};

@ -1,54 +0,0 @@
import { TProjectPermission } from "@app/lib/types";
export type TLoginAWSIAMAuthDTO = {
identityId: string;
iamHttpRequestMethod: string;
iamRequestBody: string;
iamRequestHeaders: string;
};
export type TAttachAWSIAMAuthDTO = {
identityId: string;
stsEndpoint: string;
allowedPrincipalArns: string;
allowedAccountIds: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
accessTokenNumUsesLimit: number;
accessTokenTrustedIps: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">;
export type TUpdateAWSIAMAuthDTO = {
identityId: string;
stsEndpoint?: string;
allowedPrincipalArns?: string;
allowedAccountIds?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
accessTokenNumUsesLimit?: number;
accessTokenTrustedIps?: { ipAddress: string }[];
} & Omit<TProjectPermission, "projectId">;
export type TGetAWSIAMAuthDTO = {
identityId: string;
} & Omit<TProjectPermission, "projectId">;
export type TAWSGetCallerIdentityHeaders = {
"Content-Type": string;
Host: string;
"X-Amz-Date": string;
"Content-Length": number;
"x-amz-security-token": string;
Authorization: string;
};
export type TGetCallerIdentityResponse = {
GetCallerIdentityResponse: {
GetCallerIdentityResult: {
Account: string;
Arn: string;
UserId: string;
};
ResponseMetadata: { RequestId: string };
};
};

@ -1,58 +0,0 @@
import { z } from "zod";
const twelveDigitRegex = /^\d{12}$/;
const arnRegex = /^arn:aws:iam::\d{12}:(user\/[\w-]+|role\/[\w-]+|\*)$/;
export const validateAccountIds = z
.string()
.trim()
.default("")
// Custom validation to ensure each part is a 12-digit number
.refine(
(data) => {
if (data === "") return true;
// Split the string by commas to check each supposed number
const accountIds = data.split(",").map((id) => id.trim());
// Return true only if every item matches the 12-digit requirement
return accountIds.every((id) => twelveDigitRegex.test(id));
},
{
message: "Each account ID must be a 12-digit number."
}
)
// Transform the string to normalize space after commas
.transform((data) => {
if (data === "") return "";
// Trim each ID and join with ', ' to ensure formatting
return data
.split(",")
.map((id) => id.trim())
.join(", ");
});
export const validatePrincipalArns = z
.string()
.trim()
.default("")
// Custom validation for ARN format
.refine(
(data) => {
// Skip validation if the string is empty
if (data === "") return true;
// Split the string by commas to check each supposed ARN
const arns = data.split(",");
// Return true only if every item matches one of the allowed ARN formats
return arns.every((arn) => arnRegex.test(arn.trim()));
},
{
message:
"Each ARN must be in the format of 'arn:aws:iam::123456789012:user/UserName', 'arn:aws:iam::123456789012:role/RoleName', or 'arn:aws:iam::123456789012:*'."
}
)
// Transform to normalize the spaces around commas
.transform((data) =>
data
.split(",")
.map((arn) => arn.trim())
.join(", ")
);

@ -517,22 +517,20 @@ const syncSecretsAWSParameterStore = async ({
})
);
if (!metadata.shouldDisableDelete) {
// Identify secrets to delete
await Promise.all(
Object.keys(awsParameterStoreSecretsObj).map(async (key) => {
if (!(key in secrets)) {
// case:
// -> delete secret
await ssm
.deleteParameter({
Name: awsParameterStoreSecretsObj[key].Name as string
})
.promise();
}
})
);
}
// Identify secrets to delete
await Promise.all(
Object.keys(awsParameterStoreSecretsObj).map(async (key) => {
if (!(key in secrets)) {
// case:
// -> delete secret
await ssm
.deleteParameter({
Name: awsParameterStoreSecretsObj[key].Name as string
})
.promise();
}
})
);
};
/**

@ -27,7 +27,6 @@ export type TCreateIntegrationDTO = {
value: string;
}[];
kmsKeyId?: string;
shouldDisableDelete?: boolean;
};
} & Omit<TProjectPermission, "projectId">;

@ -1,13 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TOrgMembershipDALFactory = ReturnType<typeof orgMembershipDALFactory>;
export const orgMembershipDALFactory = (db: TDbClient) => {
const orgMembershipOrm = ormify(db, TableName.OrgMembership);
return {
...orgMembershipOrm
};
};

@ -262,19 +262,13 @@ export const orgDALFactory = (db: TDbClient) => {
.where(buildFindFilter(filter))
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.OrgMembership}.orgId`)
.leftJoin(TableName.UserAliases, function joinUserAlias() {
this.on(`${TableName.UserAliases}.userId`, "=", `${TableName.OrgMembership}.userId`)
.andOn(`${TableName.UserAliases}.orgId`, "=", `${TableName.OrgMembership}.orgId`)
.andOn(`${TableName.UserAliases}.aliasType`, "=", (tx || db).raw("?", ["saml"]));
})
.select(
selectAllTableCols(TableName.OrgMembership),
db.ref("email").withSchema(TableName.Users),
db.ref("username").withSchema(TableName.Users),
db.ref("firstName").withSchema(TableName.Users),
db.ref("lastName").withSchema(TableName.Users),
db.ref("scimEnabled").withSchema(TableName.Organization),
db.ref("externalId").withSchema(TableName.UserAliases)
db.ref("scimEnabled").withSchema(TableName.Organization)
)
.where({ isGhost: false });

@ -1,78 +1,41 @@
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TOrgDALFactory } from "@app/services/org/org-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
type TDeleteOrgMembership = {
orgMembershipId: string;
orgId: string;
orgDAL: Pick<TOrgDALFactory, "findMembership" | "deleteMembershipById" | "transaction">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "delete" | "findProjectMembershipsByUserId">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete">;
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
licenseService: Pick<TLicenseServiceFactory, "updateSubscriptionOrgMemberCount">;
projectDAL: Pick<TProjectDALFactory, "find">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete">;
};
export const deleteOrgMembershipFn = async ({
export const deleteOrgMembership = async ({
orgMembershipId,
orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
projectDAL,
projectMembershipDAL
}: TDeleteOrgMembership) => {
const deletedMembership = await orgDAL.transaction(async (tx) => {
const membership = await orgDAL.transaction(async (tx) => {
// delete org membership
const orgMembership = await orgDAL.deleteMembershipById(orgMembershipId, orgId, tx);
if (!orgMembership.userId) {
await licenseService.updateSubscriptionOrgMemberCount(orgId);
return orgMembership;
}
const projects = await projectDAL.find({ orgId }, { tx });
await userAliasDAL.delete(
{
userId: orgMembership.userId,
orgId
},
tx
);
// Get all the project memberships of the user in the organization
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, orgMembership.userId);
// Delete all the project memberships of the user in the organization
// delete associated project memberships
await projectMembershipDAL.delete(
{
$in: {
id: projectMemberships.map((membership) => membership.id)
}
projectId: projects.map((project) => project.id)
},
userId: orgMembership.userId as string
},
tx
);
// Get all the project keys of the user in the organization
const projectKeys = await projectKeyDAL.find({
$in: {
projectId: projectMemberships.map((membership) => membership.projectId)
},
receiverId: orgMembership.userId
});
// Delete all the project keys of the user in the organization
await projectKeyDAL.delete(
{
$in: {
id: projectKeys.map((key) => key.id)
}
},
tx
);
await licenseService.updateSubscriptionOrgMemberCount(orgId);
return orgMembership;
});
return deletedMembership;
return membership;
};

@ -4,7 +4,7 @@ import crypto from "crypto";
import jwt from "jsonwebtoken";
import { Knex } from "knex";
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
import { TProjects } from "@app/db/schemas/projects";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
@ -18,7 +18,6 @@ import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
import { alphaNumericNanoId } from "@app/lib/nanoid";
import { isDisposableEmail } from "@app/lib/validator";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
import { ActorAuthMethod, ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
@ -31,7 +30,6 @@ import { TUserDALFactory } from "../user/user-dal";
import { TIncidentContactsDALFactory } from "./incident-contacts-dal";
import { TOrgBotDALFactory } from "./org-bot-dal";
import { TOrgDALFactory } from "./org-dal";
import { deleteOrgMembershipFn } from "./org-fns";
import { TOrgRoleDALFactory } from "./org-role-dal";
import {
TDeleteOrgMembershipDTO,
@ -45,7 +43,6 @@ import {
} from "./org-types";
type TOrgServiceFactoryDep = {
userAliasDAL: Pick<TUserAliasDALFactory, "delete">;
orgDAL: TOrgDALFactory;
orgBotDAL: TOrgBotDALFactory;
orgRoleDAL: TOrgRoleDALFactory;
@ -68,7 +65,6 @@ type TOrgServiceFactoryDep = {
export type TOrgServiceFactory = ReturnType<typeof orgServiceFactory>;
export const orgServiceFactory = ({
userAliasDAL,
orgDAL,
userDAL,
groupDAL,
@ -431,13 +427,7 @@ export const orgServiceFactory = ({
if (inviteeUser) {
// if user already exist means its already part of infisical
// Thus the signup flow is not needed anymore
const [inviteeMembership] = await orgDAL.findMembership(
{
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
[`${TableName.OrgMembership}.userId` as "userId"]: inviteeUser.id
},
{ tx }
);
const [inviteeMembership] = await orgDAL.findMembership({ orgId, userId: inviteeUser.id }, { tx });
if (inviteeMembership && inviteeMembership.status === OrgMembershipStatus.Accepted) {
throw new BadRequestError({
message: "Failed to invite an existing member of org",
@ -529,9 +519,9 @@ export const orgServiceFactory = ({
throw new BadRequestError({ message: "Invalid request", name: "Verify user to org" });
}
const [orgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
userId: user.id,
status: OrgMembershipStatus.Invited,
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
orgId
});
if (!orgMembership)
throw new BadRequestError({
@ -582,14 +572,47 @@ export const orgServiceFactory = ({
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Member);
const deletedMembership = await deleteOrgMembershipFn({
orgMembershipId: membershipId,
orgId,
orgDAL,
projectMembershipDAL,
projectKeyDAL,
userAliasDAL,
licenseService
const deletedMembership = await orgDAL.transaction(async (tx) => {
const orgMembership = await orgDAL.deleteMembershipById(membershipId, orgId, tx);
if (!orgMembership.userId) {
await licenseService.updateSubscriptionOrgMemberCount(orgId);
return orgMembership;
}
// Get all the project memberships of the user in the organization
const projectMemberships = await projectMembershipDAL.findProjectMembershipsByUserId(orgId, orgMembership.userId);
// Delete all the project memberships of the user in the organization
await projectMembershipDAL.delete(
{
$in: {
id: projectMemberships.map((membership) => membership.id)
}
},
tx
);
// Get all the project keys of the user in the organization
const projectKeys = await projectKeyDAL.find({
$in: {
projectId: projectMemberships.map((membership) => membership.projectId)
},
receiverId: orgMembership.userId
});
// Delete all the project keys of the user in the organization
await projectKeyDAL.delete(
{
$in: {
id: projectKeys.map((key) => key.id)
}
},
tx
);
await licenseService.updateSubscriptionOrgMemberCount(orgId);
return orgMembership;
});
return deletedMembership;

@ -1,12 +1,74 @@
import { Knex } from "knex";
import { Tables } from "knex/types/tables";
import { TDbClient } from "@app/db";
import { TableName, TUserEncryptionKeys } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
export type TProjectMembershipDALFactory = ReturnType<typeof projectMembershipDALFactory>;
export const projectMembershipDALFactory = (db: TDbClient) => {
const projectMemberOrm = ormify(db, TableName.ProjectMembership);
const projectMembershipOrm = ormify(db, TableName.ProjectMembership);
const accessApprovalRequestOrm = ormify(db, TableName.AccessApprovalRequest);
const secretApprovalRequestOrm = ormify(db, TableName.SecretApprovalRequest);
const deleteMany = async (filter: TFindFilter<Tables[TableName.ProjectMembership]["base"]>, tx?: Knex) => {
const handleDeletion = async (processedTx: Knex) => {
// Find all memberships
const memberships = await projectMembershipOrm.find(filter, {
tx: processedTx
});
// Delete all access approvals in this project from the users attached to these memberships
await accessApprovalRequestOrm.delete(
{
$in: {
projectMembershipId: memberships.map((membership) => membership.id)
}
},
processedTx
);
for await (const membership of memberships) {
const allPoliciesInProject = await (tx || db)(TableName.SecretApprovalRequest)
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
.join(
TableName.SecretApprovalPolicy,
`${TableName.SecretApprovalRequest}.policyId`,
`${TableName.SecretApprovalPolicy}.id`
)
.where({ [`${TableName.Environment}.projectId` as "projectId"]: membership.projectId })
.where({ [`${TableName.SecretApprovalRequest}.committerUserId` as "committerUserId"]: membership.userId })
.select(db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"));
await secretApprovalRequestOrm.delete(
{
$in: {
policyId: allPoliciesInProject.map((policy) => policy.policyId)
},
committerUserId: membership.userId
},
processedTx
);
// Delete the actual project memberships
await projectMembershipOrm.delete(
{
id: membership.id
},
processedTx
);
}
return memberships;
};
if (tx) {
return handleDeletion(tx);
}
return db.transaction(handleDeletion);
};
// special query
const findAllProjectMembers = async (projectId: string) => {
@ -54,6 +116,7 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
const members = sqlNestRelationships({
data: docs,
parentMapper: ({ email, firstName, username, lastName, publicKey, isGhost, id, userId }) => ({
isGroupMember: false,
id,
userId,
projectId,
@ -152,8 +215,9 @@ export const projectMembershipDALFactory = (db: TDbClient) => {
};
return {
...projectMemberOrm,
...projectMembershipOrm,
findAllProjectMembers,
delete: deleteMany,
findProjectGhostUser,
findMembershipsByUsername,
findProjectMembershipsByUserId

@ -19,6 +19,7 @@ import { groupBy } from "@app/lib/fn";
import { TUserGroupMembershipDALFactory } from "../../ee/services/group/user-group-membership-dal";
import { ActorType } from "../auth/auth-type";
import { TGroupProjectDALFactory } from "../group-project/group-project-dal";
import { TOrgDALFactory } from "../org/org-dal";
import { TProjectDALFactory } from "../project/project-dal";
import { assignWorkspaceKeysToMembers } from "../project/project-fns";
@ -52,6 +53,7 @@ type TProjectMembershipServiceFactoryDep = {
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser" | "transaction">;
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
groupProjectDAL: TGroupProjectDALFactory;
};
export type TProjectMembershipServiceFactory = ReturnType<typeof projectMembershipServiceFactory>;
@ -61,6 +63,7 @@ export const projectMembershipServiceFactory = ({
projectMembershipDAL,
projectUserMembershipRoleDAL,
smtpService,
groupProjectDAL,
projectRoleDAL,
projectBotDAL,
orgDAL,
@ -74,6 +77,7 @@ export const projectMembershipServiceFactory = ({
actorId,
actor,
actorOrgId,
includeGroupMembers,
actorAuthMethod,
projectId
}: TGetProjectMembershipDTO) => {
@ -86,7 +90,20 @@ export const projectMembershipServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
return projectMembershipDAL.findAllProjectMembers(projectId);
const projectMembers = await projectMembershipDAL.findAllProjectMembers(projectId);
if (includeGroupMembers) {
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
const allMembers = [...projectMembers, ...groupMembers];
// Ensure the userId is unique
const membersIds = new Set(allMembers.map((entity) => entity.user.id));
const uniqueMembers = allMembers.filter((entity) => membersIds.has(entity.user.id));
return uniqueMembers;
}
return projectMembers;
};
const addUsersToProject = async ({
@ -110,7 +127,7 @@ export const projectMembershipServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
const orgMembers = await orgDAL.findMembership({
[`${TableName.OrgMembership}.orgId` as "orgId"]: project.orgId,
orgId: project.orgId,
$in: {
[`${TableName.OrgMembership}.id` as "id"]: members.map(({ orgMembershipId }) => orgMembershipId)
}
@ -119,7 +136,7 @@ export const projectMembershipServiceFactory = ({
const existingMembers = await projectMembershipDAL.find({
projectId,
$in: { userId: orgMembers.map(({ userId }) => userId).filter(Boolean) }
$in: { userId: orgMembers.map(({ userId }) => userId).filter(Boolean) as string[] }
});
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
@ -134,7 +151,7 @@ export const projectMembershipServiceFactory = ({
const projectMemberships = await projectMembershipDAL.insertMany(
orgMembers.map(({ userId }) => ({
projectId,
userId
userId: userId as string
})),
tx
);
@ -145,12 +162,12 @@ export const projectMembershipServiceFactory = ({
const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId);
await projectKeyDAL.insertMany(
orgMembers
.filter(({ userId }) => !userIdsToExcludeForProjectKeyAddition.has(userId))
.filter(({ userId }) => !userIdsToExcludeForProjectKeyAddition.has(userId as string))
.map(({ userId, id }) => ({
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
senderId: actorId,
receiverId: userId,
receiverId: userId as string,
projectId
})),
tx

@ -1,6 +1,8 @@
import { TProjectPermission } from "@app/lib/types";
export type TGetProjectMembershipDTO = TProjectPermission;
export type TGetProjectMembershipDTO = {
includeGroupMembers?: boolean;
} & TProjectPermission;
export enum ProjectUserMembershipTemporaryMode {
Relative = "relative"
}

@ -8,7 +8,6 @@ import {
SecretKeyEncoding,
SecretsSchema,
SecretVersionsSchema,
TableName,
TIntegrationAuths,
TSecretApprovalRequestsSecrets,
TSecrets,
@ -274,10 +273,7 @@ export const projectQueueFactory = ({
for (const key of existingProjectKeys) {
const user = await userDAL.findUserEncKeyByUserId(key.receiverId);
const [orgMembership] = await orgDAL.findMembership({
[`${TableName.OrgMembership}.userId` as "userId"]: key.receiverId,
[`${TableName.OrgMembership}.orgId` as "orgId"]: project.orgId
});
const [orgMembership] = await orgDAL.findMembership({ userId: key.receiverId, orgId: project.orgId });
if (!user) {
throw new Error(`User with ID ${key.receiverId} was not found during upgrade.`);

@ -27,7 +27,6 @@ import {
fnSecretBlindIndexCheck,
fnSecretBulkInsert,
fnSecretBulkUpdate,
interpolateSecrets,
recursivelyGetSecretPaths
} from "./secret-fns";
import { TSecretQueueFactory } from "./secret-queue";
@ -886,7 +885,6 @@ export const secretServiceFactory = ({
actorAuthMethod,
environment,
includeImports,
expandSecretReferences,
recursive
}: TGetSecretsRawDTO) => {
const botKey = await projectBotService.getBotKey(projectId);
@ -904,66 +902,17 @@ export const secretServiceFactory = ({
recursive
});
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
const decryptedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => ({
...el,
secrets: importedSecrets.map((sec) =>
decryptSecretRaw(
{ ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath },
botKey
)
)
}));
if (expandSecretReferences) {
const expandSecrets = interpolateSecrets({
folderDAL,
projectId,
secretDAL,
secretEncKey: botKey
});
const batchSecretsExpand = async (
secretBatch: {
secretKey: string;
secretValue: string;
secretComment?: string;
}[]
) => {
const secretRecord: Record<
string,
{
value: string;
comment?: string;
skipMultilineEncoding?: boolean;
}
> = {};
secretBatch.forEach((decryptedSecret) => {
secretRecord[decryptedSecret.secretKey] = {
value: decryptedSecret.secretValue,
comment: decryptedSecret.secretComment
};
});
await expandSecrets(secretRecord);
secretBatch.forEach((decryptedSecret, index) => {
// eslint-disable-next-line no-param-reassign
secretBatch[index].secretValue = secretRecord[decryptedSecret.secretKey].value;
});
};
// expand secrets
await batchSecretsExpand(decryptedSecrets);
// expand imports by batch
await Promise.all(decryptedImports.map((decryptedImport) => batchSecretsExpand(decryptedImport.secrets)));
}
return {
secrets: decryptedSecrets,
imports: decryptedImports
secrets: secrets.map((el) => decryptSecretRaw(el, botKey)),
imports: (imports || [])?.map(({ secrets: importedSecrets, ...el }) => ({
...el,
secrets: importedSecrets.map((sec) =>
decryptSecretRaw(
{ ...sec, environment: el.environment, workspace: projectId, secretPath: el.secretPath },
botKey
)
)
}))
};
};

@ -138,7 +138,6 @@ export type TDeleteBulkSecretDTO = {
} & TProjectPermission;
export type TGetSecretsRawDTO = {
expandSecretReferences?: boolean;
path: string;
environment: string;
includeImports?: boolean;

@ -17,7 +17,6 @@ export type TSmtpSendMail = {
export type TSmtpService = ReturnType<typeof smtpServiceFactory>;
export enum SmtpTemplates {
SignupEmailVerification = "signupEmailVerification.handlebars",
EmailVerification = "emailVerification.handlebars",
SecretReminder = "secretReminder.handlebars",
EmailMfa = "emailMfa.handlebars",

@ -1,15 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Code</title>
</head>
</head>
<body>
<body>
<h2>Confirm your email address</h2>
<p>Your confirmation code is below — enter it in the browser window where you've started confirming your email.</p>
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
<h1>{{code}}</h1>
</body>
<p>Questions about setting up Infisical? Email us at support@infisical.com</p>
</body>
</html>

@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Code</title>
</head>
<body>
<h2>Confirm your email address</h2>
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
<h1>{{code}}</h1>
<p>Questions about setting up Infisical? Email us at support@infisical.com</p>
</body>
</html>

@ -102,8 +102,7 @@ export const superAdminServiceFactory = ({
superAdmin: true,
isGhost: false,
isAccepted: true,
authMethods: [AuthMethod.EMAIL],
isEmailVerified: true
authMethods: [AuthMethod.EMAIL]
},
tx
);

@ -1,4 +0,0 @@
export enum UserAliasType {
LDAP = "ldap",
SAML = "saml"
}

@ -10,7 +10,7 @@ import {
TUserEncryptionKeysUpdate
} from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TUserDALFactory = ReturnType<typeof userDALFactory>;
@ -63,6 +63,99 @@ export const userDALFactory = (db: TDbClient) => {
}
};
const findUsersByProjectId = async (projectId: string, userIds: string[]) => {
try {
const projectMembershipQuery = await db(TableName.ProjectMembership)
.where({ projectId })
.whereIn("userId", userIds)
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.Users))
.select(db.ref("id").withSchema(TableName.ProjectMembership).as("projectMembershipId"));
const groupMembershipQuery = await db(TableName.UserGroupMembership)
.whereIn("userId", userIds)
.join(
TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.GroupProjectMembership}.groupId` // this gives us access to the project id in the group membership
)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.Users))
.select(db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupProjectMembershipId"));
const projectMembershipUsers = projectMembershipQuery.map((user) => ({
...user,
projectMembershipId: user.projectMembershipId,
userGroupMembershipId: null
}));
const groupMembershipUsers = groupMembershipQuery.map((user) => ({
...user,
projectMembershipId: null,
groupProjectMembershipId: user.groupProjectMembershipId
}));
// return [...projectMembershipUsers, ...groupMembershipUsers];
// There may be duplicates in the results since a user can have both a project membership, and access through a group, so we need to filter out potential duplicates.
// We should prioritize project memberships over group memberships.
const memberships = [...projectMembershipUsers, ...groupMembershipUsers];
const uniqueMemberships = memberships.filter((user, index) => {
const firstIndex = memberships.findIndex((u) => u.id === user.id);
return firstIndex === index;
});
return uniqueMemberships;
} catch (error) {
throw new DatabaseError({ error, name: "Find users by project id" });
}
};
// if its a group membership, it should have a isGroupMembership flag
const findUserByProjectId = async (projectId: string, userId: string) => {
try {
const projectMembership = await db(TableName.ProjectMembership)
.where({ projectId, userId })
.join(TableName.Users, `${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.Users))
.select(db.ref("id").withSchema(TableName.ProjectMembership).as("projectMembershipId"))
.first();
const groupProjectMembership = await db(TableName.UserGroupMembership)
.where({ userId })
.join(
TableName.GroupProjectMembership,
`${TableName.UserGroupMembership}.groupId`,
`${TableName.GroupProjectMembership}.groupId` // this gives us access to the project id in the group membership
)
.where(`${TableName.GroupProjectMembership}.projectId`, projectId)
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
.select(selectAllTableCols(TableName.Users))
.select(db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupProjectMembershipId"))
.first();
if (projectMembership) {
return {
...projectMembership,
projectMembershipId: projectMembership.projectMembershipId,
groupProjectMembershipId: null
};
}
if (groupProjectMembership) {
return {
...groupProjectMembership,
projectMembershipId: null,
groupProjectMembershipId: groupProjectMembership.groupProjectMembershipId
};
}
} catch (error) {
throw new DatabaseError({ error, name: "Find user by project id" });
}
};
const findUserByProjectMembershipId = async (projectMembershipId: string) => {
try {
return await db(TableName.ProjectMembership)
@ -146,6 +239,8 @@ export const userDALFactory = (db: TDbClient) => {
return {
...userOrm,
findUserByUsername,
findUsersByProjectId,
findUserByProjectId,
findUserEncKeyByUsername,
findUserEncKeyByUserIdsBatch,
findUserEncKeyByUserId,

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