mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-02 16:55:02 +00:00
Compare commits
534 Commits
ssh-cli-do
...
minor-ui-i
Author | SHA1 | Date | |
---|---|---|---|
72780c61b4 | |||
c4da0305ba | |||
4fdfdc1a39 | |||
4e48ab1eeb | |||
a6671b4355 | |||
4c7ae3475a | |||
49797c3c13 | |||
7d9c5657aa | |||
eda4abb610 | |||
e341bbae9d | |||
7286f9a9e6 | |||
1c9a9283ae | |||
8d52011173 | |||
1b5b937db5 | |||
7b8b024654 | |||
a67badf660 | |||
ba42ea736b | |||
6c7289ebe6 | |||
5cd6a66989 | |||
4e41e84491 | |||
85d71b1085 | |||
9d66659f72 | |||
70c9761abe | |||
6047c4489b | |||
c9d7559983 | |||
66251403bf | |||
b9c4407507 | |||
624be80768 | |||
8d7b5968d3 | |||
b7d4bb0ce2 | |||
598dea0dd3 | |||
7154b19703 | |||
9ce465b3e2 | |||
598e5c0be5 | |||
72f08a6b89 | |||
55d8762351 | |||
3c92ec4dc3 | |||
f2224262a4 | |||
23eac40740 | |||
4ae88c0447 | |||
7aecaad050 | |||
cf61390e52 | |||
3f02481e78 | |||
7adc103ed2 | |||
5bdbf37171 | |||
4f874734ab | |||
eb6fd8259b | |||
1766a44dd0 | |||
624c9ef8da | |||
dfd4b13574 | |||
22b57b7a74 | |||
1ba0b9c204 | |||
a903537441 | |||
92c4d83714 | |||
a6414104ad | |||
071f37666e | |||
cd5078d8b7 | |||
110d0e95b0 | |||
a8c0bbb7ca | |||
6af8a4fab8 | |||
407fd8eda7 | |||
9d976de19b | |||
43ecd31b74 | |||
be99e40050 | |||
800d2c0454 | |||
6d0534b165 | |||
ccee0f5428 | |||
14586c7cd0 | |||
7090eea716 | |||
01d3443139 | |||
c4b23a8d4f | |||
90a2a11fff | |||
95d7c2082c | |||
ab5eb4c696 | |||
65aeb81934 | |||
a406511405 | |||
61da0db49e | |||
0968893d4b | |||
59666740ca | |||
9cc7edc869 | |||
e1b016f76d | |||
1175b9b5af | |||
09521144ec | |||
8759944077 | |||
aac3c355e9 | |||
2a28a462a5 | |||
3328e0850f | |||
216cae9b33 | |||
d24a5d96e3 | |||
89d4d4bc92 | |||
cffcb28bc9 | |||
61388753cf | |||
a6145120e6 | |||
dacffbef08 | |||
4db3e5d208 | |||
2a84d61862 | |||
a5945204ad | |||
55b0dc7f81 | |||
ba03fc256b | |||
ea28c374a7 | |||
e99eb47cf4 | |||
cf107c0c0d | |||
9fcb1c2161 | |||
70515a1ca2 | |||
955cf9303a | |||
a24ef46d7d | |||
ee49f714b9 | |||
657aca516f | |||
b5d60398d6 | |||
c3d515bb95 | |||
7f89a7c860 | |||
23cb05c16d | |||
d74b819f57 | |||
457056b600 | |||
7dc9ea4f6a | |||
3b4b520d42 | |||
23f605bda7 | |||
1c3c8dbdce | |||
317c95384e | |||
7dd959e124 | |||
2049e5668f | |||
0a3e99b334 | |||
c4ad0aa163 | |||
5bb0b7a508 | |||
96bcd42753 | |||
2c75e23acf | |||
907dd4880a | |||
6af7c5c371 | |||
72468d5428 | |||
939ee892e0 | |||
c7ec9ff816 | |||
554e268f88 | |||
a8a27c3045 | |||
27af943ee1 | |||
9b772ad55a | |||
94a1fc2809 | |||
10c10642a1 | |||
3e0f04273c | |||
91f2d0384e | |||
811dc8dd75 | |||
4ee9375a8d | |||
92f697e195 | |||
8062f0238b | |||
1181c684db | |||
dda436bcd9 | |||
89124b18d2 | |||
effd88c4bd | |||
27efc908e2 | |||
8e4226038b | |||
27425a1a64 | |||
18cf3c89c1 | |||
49e6d7a861 | |||
c4446389b0 | |||
7c21dec54d | |||
2ea5710896 | |||
f9ac7442df | |||
a534a4975c | |||
79a616dc1c | |||
a93bfa69c9 | |||
598d14fc54 | |||
08a0550cd7 | |||
d7503573b1 | |||
b5a89edeed | |||
860eaae4c8 | |||
c7a4b6c4e9 | |||
c12c6dcc6e | |||
99c9b644df | |||
d0d5556bd0 | |||
753c28a2d3 | |||
8741414cfa | |||
b8d29793ec | |||
92013dbfbc | |||
c5319588fe | |||
9efb8eaf78 | |||
dfc973c7f7 | |||
3013d1977c | |||
f358e8942d | |||
58f51411c0 | |||
c3970d1ea2 | |||
2dc00a638a | |||
94aed485a5 | |||
e382941424 | |||
bab9c1f454 | |||
2bd4770fb4 | |||
31905fab6e | |||
784acf16d0 | |||
114b89c952 | |||
81420198cb | |||
b949708f45 | |||
2a6b6b03b9 | |||
0ff18e277f | |||
e093f70301 | |||
8e2ff18f35 | |||
3fbfecf7a9 | |||
9087def21c | |||
89c6ab591a | |||
235a33a01c | |||
dd6c217dc8 | |||
78b1b5583a | |||
8f2a504fd0 | |||
1d5b629d8f | |||
14f895cae2 | |||
b7be6bd1d9 | |||
58a97852f6 | |||
980aa9eaae | |||
a35d1aa72b | |||
52d801bce5 | |||
c92c160709 | |||
71ca7a82db | |||
6f799b478d | |||
a89e6b6e58 | |||
99ca9e04f8 | |||
586dbd79b0 | |||
6cdc71b9b1 | |||
f88d6a183f | |||
fa82d4953e | |||
12d9fe9ffd | |||
86acf88a13 | |||
63c7c39e21 | |||
151edc7efa | |||
5fa7f56285 | |||
810b27d121 | |||
51fe7450ae | |||
938c06a2ed | |||
38d431ec77 | |||
d202fdf5c8 | |||
f1b2028542 | |||
5c9b46dfba | |||
a516e50984 | |||
3c1fc024c2 | |||
569439f208 | |||
9afc282679 | |||
8db85cfb84 | |||
664b2f0089 | |||
5e9bd3a7c6 | |||
2c13af6db3 | |||
ec9171d0bc | |||
81362bec8f | |||
5a4d7541a2 | |||
3c97c45455 | |||
4f015d77fb | |||
78e894c2bb | |||
23513158ed | |||
934ef8ab27 | |||
23e9c52f67 | |||
e276752e7c | |||
01ae19fa2b | |||
9df8cf60ef | |||
1b1fe2a700 | |||
338961480c | |||
03debcab5a | |||
645dfafba0 | |||
4a6f759900 | |||
b9d06ff686 | |||
5cc5a4f03d | |||
5ef2be1a9c | |||
8de9ddfb8b | |||
5b40de16cf | |||
11aac3f5dc | |||
9823c7d1aa | |||
d627ecf05d | |||
3ba396f7fa | |||
9c561266ed | |||
36fef11d91 | |||
742932c4a0 | |||
57a77ae5f1 | |||
7c9564c7dc | |||
736aecebf8 | |||
16748357d7 | |||
12863b389b | |||
6341b7e989 | |||
c592ff00a6 | |||
ef87086272 | |||
bd459d994c | |||
440f93f392 | |||
bc32d6cbbf | |||
0cf3115830 | |||
65f2e626ae | |||
8b3e3152a4 | |||
661b31f762 | |||
e78ad1147b | |||
473efa91f0 | |||
b440e918ac | |||
439f253350 | |||
4e68304262 | |||
c4d0896609 | |||
b8115d481c | |||
5fdec97319 | |||
755bb1679a | |||
7142e7a6c6 | |||
11ade92b5b | |||
4147725260 | |||
c359cf162f | |||
9d04b648fa | |||
9edfdb7234 | |||
ff74e020fc | |||
6ee446e574 | |||
c806059b11 | |||
3a5bb31bde | |||
6f38d6c76f | |||
d721a46ec9 | |||
989065ba34 | |||
5ad419c079 | |||
80f72e8040 | |||
0cef728617 | |||
0a735422ed | |||
8370a0d9c0 | |||
71af662998 | |||
27e391f7e0 | |||
2187c7588c | |||
8ab7e8360d | |||
957cab3117 | |||
4bf16f68fc | |||
ab3ee775bb | |||
2a86e6f4d1 | |||
194fbb79f2 | |||
faaba8deb7 | |||
ae21b157a9 | |||
6167c70a74 | |||
0ecf75cbdb | |||
3f8aa0fa4b | |||
6487c83bda | |||
c08fbbdab2 | |||
a4aa65bb81 | |||
258d19cbe4 | |||
de91356127 | |||
ccb07942de | |||
3d278b0925 | |||
956fb2efb4 | |||
13894261ce | |||
d7ffa70906 | |||
b8fa7c5bb6 | |||
2baacfcd8f | |||
31c11f7d2a | |||
c5f06dece4 | |||
662e79ac98 | |||
17249d603b | |||
9bdff9c504 | |||
4552ce6ca4 | |||
ba4b8801eb | |||
36a5f728a1 | |||
502429d914 | |||
27abfa4fff | |||
4bc9bca287 | |||
612c29225d | |||
4d43accc8a | |||
3c89a69410 | |||
e741b63e63 | |||
9cde1995c7 | |||
3ed3856c85 | |||
8054d93851 | |||
02dc23425c | |||
01534c3525 | |||
325ce73b9f | |||
1639bda3f6 | |||
b5b91c929f | |||
9bc549ca8c | |||
cedc88d83a | |||
f866a810c1 | |||
0c034d69ac | |||
e29f7f656c | |||
858e569d4d | |||
5d8f32b774 | |||
bb71f5eb7e | |||
30b431a255 | |||
fd32118685 | |||
8528f3fd2a | |||
60749cfc43 | |||
eb358bcafd | |||
ffbd29c575 | |||
a74f0170da | |||
a0fad34a6d | |||
f0dc5ec876 | |||
c2453f0c84 | |||
2819c8519e | |||
616b013b12 | |||
0b9d890a51 | |||
5ba507bc1c | |||
0ecc196e5d | |||
ddac9f7cc4 | |||
f5adc4d9f3 | |||
34354994d8 | |||
d7c3192099 | |||
1576358805 | |||
e6103d2d3f | |||
8bf8bc77c9 | |||
3219723149 | |||
74b95d92ab | |||
6d3793beff | |||
0df41f3391 | |||
1acac9d479 | |||
0cefd6f837 | |||
5e9dc0b98d | |||
f632847dc6 | |||
faa6d1cf40 | |||
7fb18870e3 | |||
ae841715e5 | |||
baac87c16a | |||
291d29ec41 | |||
b726187ba3 | |||
d98ff32b07 | |||
1fa510b32f | |||
c57f0d8120 | |||
00490f2cff | |||
ee58f538c0 | |||
0fa20f7839 | |||
40ef75d3bd | |||
26af13453c | |||
ad1f71883d | |||
aa39451bc2 | |||
f5548b3e8c | |||
2659ea7170 | |||
d2e3f504fd | |||
ca4151a34d | |||
d4bc104fd1 | |||
7e3a3fcdb0 | |||
7f67912d2f | |||
a7f4020c08 | |||
d2d89034ba | |||
2fc6c564c0 | |||
240b86c1d5 | |||
30d66cef89 | |||
b7d11444a9 | |||
0a6aef0afa | |||
0ac7ec460b | |||
808a901aee | |||
d5412f916f | |||
d0648ca596 | |||
3291f1f908 | |||
965d30bd03 | |||
68fe7c589a | |||
54377a44d3 | |||
8c902d7699 | |||
c25c84aeb3 | |||
4359eb7313 | |||
322536d738 | |||
6c5db3a187 | |||
a337e6badd | |||
524a97e9a6 | |||
c56f598115 | |||
19d32a1a3b | |||
7e5417a0eb | |||
afd6de27fe | |||
7781a6b7e7 | |||
b3b4e41d92 | |||
5225f5136a | |||
398adfaf76 | |||
d77c26fa38 | |||
ef7b81734a | |||
09b489a348 | |||
6b5c50def0 | |||
1f2d52176c | |||
7002e297c8 | |||
71864a131f | |||
9964d2ecaa | |||
3ebbaefc2a | |||
dd5c494bdb | |||
bace8af5a1 | |||
f56196b820 | |||
7042d73410 | |||
cb22ee315e | |||
701eb7cfc6 | |||
bf8df14b01 | |||
1ba8b6394b | |||
c442c8483a | |||
0435305a68 | |||
febf11f502 | |||
64fd15c32d | |||
a2c9494d52 | |||
18460e0678 | |||
3d03fece74 | |||
234e7eb9be | |||
04af313bf0 | |||
9b038ccc45 | |||
9beb384546 | |||
12ec9b4b4e | |||
96b8e7fda8 | |||
93b9108aa3 | |||
99017ea1ae | |||
f32588112e | |||
f9b0b6700a | |||
b45d9398f0 | |||
1d1140237f | |||
937560fd8d | |||
5f4b7b9ea7 | |||
05139820a5 | |||
7f6bc3ecfe | |||
d8cc000ad1 | |||
8fc03c06d9 | |||
50ceedf39f | |||
550096e72b | |||
1190ca2d77 | |||
2fb60201bc | |||
e763a6f683 | |||
cb1b006118 | |||
356e7c5958 | |||
634b500244 | |||
54b4d4ae55 | |||
1a68765f15 | |||
2f6dab3f63 | |||
ae07d38c19 | |||
025b4b8761 | |||
ef688efc8d | |||
8c98565715 | |||
e9358cd1d8 | |||
4daaf80caa | |||
cf7768d8e5 | |||
e76d2f58ea | |||
36a13d182f | |||
8b26670d73 | |||
35d3581e23 | |||
0edf0dac98 | |||
a757ea22a1 | |||
74df374998 | |||
925a594a1b | |||
36af975594 | |||
ee54d460a0 | |||
3c32d8dd90 | |||
9b50d451ec | |||
7ede4e2cf5 | |||
4552f0efa4 | |||
0d35273857 | |||
5ad8dab250 | |||
92a80b3314 | |||
01dcbb0122 | |||
adb0819102 | |||
41ba111a69 | |||
1b48ce21be | |||
2f922d6343 | |||
e67b0540dd | |||
a78455fde6 | |||
967dac9be6 | |||
922b245780 | |||
242595fceb |
18
.env.example
18
.env.example
@ -26,7 +26,8 @@ SITE_URL=http://localhost:8080
|
|||||||
# Mail/SMTP
|
# Mail/SMTP
|
||||||
SMTP_HOST=
|
SMTP_HOST=
|
||||||
SMTP_PORT=
|
SMTP_PORT=
|
||||||
SMTP_NAME=
|
SMTP_FROM_ADDRESS=
|
||||||
|
SMTP_FROM_NAME=
|
||||||
SMTP_USERNAME=
|
SMTP_USERNAME=
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
|
|
||||||
@ -91,17 +92,24 @@ ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT=true
|
|||||||
|
|
||||||
# App Connections
|
# App Connections
|
||||||
|
|
||||||
# aws assume-role
|
# aws assume-role connection
|
||||||
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID=
|
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID=
|
||||||
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY=
|
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY=
|
||||||
|
|
||||||
# github oauth
|
# github oauth connection
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID=
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID=
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET=
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET=
|
||||||
|
|
||||||
#github app
|
#github app connection
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
|
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_SLUG=
|
INF_APP_CONNECTION_GITHUB_APP_SLUG=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_ID=
|
INF_APP_CONNECTION_GITHUB_APP_ID=
|
||||||
|
|
||||||
|
#gcp app connection
|
||||||
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
|
||||||
|
|
||||||
|
# azure app connection
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_ID=
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_SECRET=
|
8
.github/workflows/check-fe-ts-and-lint.yml
vendored
8
.github/workflows/check-fe-ts-and-lint.yml
vendored
@ -18,18 +18,18 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: ☁️ Checkout source
|
- name: ☁️ Checkout source
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: 🔧 Setup Node 16
|
- name: 🔧 Setup Node 20
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: "20"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
cache-dependency-path: frontend/package-lock.json
|
cache-dependency-path: frontend/package-lock.json
|
||||||
- name: 📦 Install dependencies
|
- name: 📦 Install dependencies
|
||||||
run: npm install
|
run: npm install
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
- name: 🏗️ Run Type check
|
- name: 🏗️ Run Type check
|
||||||
run: npm run type:check
|
run: npm run type:check
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
- name: 🏗️ Run Link check
|
- name: 🏗️ Run Link check
|
||||||
run: npm run lint:fix
|
run: npm run lint:fix
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
|
64
.github/workflows/deployment-pipeline.yml
vendored
64
.github/workflows/deployment-pipeline.yml
vendored
@ -5,6 +5,10 @@ permissions:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "infisical-core-deployment"
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
infisical-tests:
|
infisical-tests:
|
||||||
name: Integration tests
|
name: Integration tests
|
||||||
@ -97,7 +101,7 @@ jobs:
|
|||||||
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
environment-variables: "LOG_LEVEL=info"
|
environment-variables: "LOG_LEVEL=info"
|
||||||
- name: Deploy to Amazon ECS service
|
- name: Deploy to Amazon ECS service
|
||||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||||
with:
|
with:
|
||||||
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||||
service: infisical-core-gamma-stage
|
service: infisical-core-gamma-stage
|
||||||
@ -113,10 +117,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: twingate/github-action@v1
|
- uses: twingate/github-action@v1
|
||||||
with:
|
with:
|
||||||
# The Twingate Service Key used to connect Twingate to the proper service
|
|
||||||
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
|
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -153,12 +153,37 @@ jobs:
|
|||||||
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
environment-variables: "LOG_LEVEL=info"
|
environment-variables: "LOG_LEVEL=info"
|
||||||
- name: Deploy to Amazon ECS service
|
- name: Deploy to Amazon ECS service
|
||||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||||
with:
|
with:
|
||||||
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||||
service: infisical-core-platform
|
service: infisical-core-platform
|
||||||
cluster: infisical-core-platform
|
cluster: infisical-core-platform
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
- name: Post slack message
|
||||||
|
uses: slackapi/slack-github-action@v2.0.0
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload: |
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
blocks:
|
||||||
|
- type: "section"
|
||||||
|
text:
|
||||||
|
type: "mrkdwn"
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Application:*\nInfisical Core"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Instance Type:*\nShared Infisical Cloud"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Region:*\nUS"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Git Tag:*\n<https://github.com/Infisical/infisical/commit/${{ steps.commit.outputs.short }}>"
|
||||||
|
|
||||||
|
|
||||||
production-eu:
|
production-eu:
|
||||||
name: EU production deploy
|
name: EU production deploy
|
||||||
@ -204,9 +229,34 @@ jobs:
|
|||||||
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
environment-variables: "LOG_LEVEL=info"
|
environment-variables: "LOG_LEVEL=info"
|
||||||
- name: Deploy to Amazon ECS service
|
- name: Deploy to Amazon ECS service
|
||||||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
|
||||||
with:
|
with:
|
||||||
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||||
service: infisical-core-platform
|
service: infisical-core-platform
|
||||||
cluster: infisical-core-platform
|
cluster: infisical-core-platform
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
- name: Post slack message
|
||||||
|
uses: slackapi/slack-github-action@v2.0.0
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload: |
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
blocks:
|
||||||
|
- type: "section"
|
||||||
|
text:
|
||||||
|
type: "mrkdwn"
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Application:*\nInfisical Core"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Instance Type:*\nShared Infisical Cloud"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Region:*\nEU"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Git Tag:*\n<https://github.com/Infisical/infisical/commit/${{ steps.commit.outputs.short }}>"
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
name: Release Helm Charts
|
name: Release Infisical Core Helm chart
|
||||||
|
|
||||||
on: [workflow_dispatch]
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
@ -17,6 +17,6 @@ jobs:
|
|||||||
- name: Install Cloudsmith CLI
|
- name: Install Cloudsmith CLI
|
||||||
run: pip install --upgrade cloudsmith-cli
|
run: pip install --upgrade cloudsmith-cli
|
||||||
- name: Build and push helm package to Cloudsmith
|
- name: Build and push helm package to Cloudsmith
|
||||||
run: cd helm-charts && sh upload-to-cloudsmith.sh
|
run: cd helm-charts && sh upload-infisical-core-helm-cloudsmith.sh
|
||||||
env:
|
env:
|
||||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
@ -1,4 +1,4 @@
|
|||||||
name: Release Docker image for K8 operator
|
name: Release image + Helm chart K8s Operator
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@ -35,3 +35,18 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
infisical/kubernetes-operator:latest
|
infisical/kubernetes-operator:latest
|
||||||
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
with:
|
||||||
|
version: v3.10.0
|
||||||
|
- name: Install python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
- name: Install Cloudsmith CLI
|
||||||
|
run: pip install --upgrade cloudsmith-cli
|
||||||
|
- name: Build and push helm package to Cloudsmith
|
||||||
|
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
|
||||||
|
env:
|
||||||
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||||
|
@ -7,3 +7,4 @@ docs/self-hosting/configuration/envars.mdx:generic-api-key:106
|
|||||||
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451
|
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451
|
||||||
docs/mint.json:generic-api-key:651
|
docs/mint.json:generic-api-key:651
|
||||||
backend/src/ee/services/hsm/hsm-service.ts:generic-api-key:134
|
backend/src/ee/services/hsm/hsm-service.ts:generic-api-key:134
|
||||||
|
docs/documentation/platform/audit-log-streams/audit-log-streams.mdx:generic-api-key:104
|
||||||
|
@ -8,7 +8,7 @@ FROM node:20-slim AS base
|
|||||||
FROM base AS frontend-dependencies
|
FROM base AS frontend-dependencies
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm ci --only-production --ignore-scripts
|
RUN npm ci --only-production --ignore-scripts
|
||||||
@ -23,17 +23,16 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
|
|||||||
COPY /frontend .
|
COPY /frontend .
|
||||||
|
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ENV NEXT_PUBLIC_ENV production
|
|
||||||
ARG POSTHOG_HOST
|
ARG POSTHOG_HOST
|
||||||
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
|
ENV VITE_POSTHOG_HOST $POSTHOG_HOST
|
||||||
ARG POSTHOG_API_KEY
|
ARG POSTHOG_API_KEY
|
||||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
|
ENV VITE_POSTHOG_API_KEY $POSTHOG_API_KEY
|
||||||
ARG INTERCOM_ID
|
ARG INTERCOM_ID
|
||||||
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
ENV VITE_INTERCOM_ID $INTERCOM_ID
|
||||||
ARG INFISICAL_PLATFORM_VERSION
|
ARG INFISICAL_PLATFORM_VERSION
|
||||||
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||||
ARG CAPTCHA_SITE_KEY
|
ARG CAPTCHA_SITE_KEY
|
||||||
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
@ -44,20 +43,10 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
|
RUN groupadd -r -g 1001 nodejs && useradd -r -u 1001 -g nodejs non-root-user
|
||||||
|
|
||||||
RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images
|
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/dist ./
|
||||||
VOLUME /app/.next/cache/images
|
|
||||||
|
|
||||||
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
|
|
||||||
COPY --from=frontend-builder /app/public ./public
|
|
||||||
RUN chown non-root-user:nodejs ./public/data
|
|
||||||
|
|
||||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
|
|
||||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
|
|
||||||
|
|
||||||
USER non-root-user
|
USER non-root-user
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## BACKEND
|
## BACKEND
|
||||||
##
|
##
|
||||||
@ -137,7 +126,7 @@ RUN apt-get update && apt-get install -y \
|
|||||||
freetds-dev \
|
freetds-dev \
|
||||||
freetds-bin \
|
freetds-bin \
|
||||||
tdsodbc \
|
tdsodbc \
|
||||||
openssh \
|
openssh-client \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Configure ODBC in production
|
# Configure ODBC in production
|
||||||
@ -160,14 +149,11 @@ RUN chmod u+rx /usr/sbin/update-ca-certificates
|
|||||||
|
|
||||||
## set pre baked keys
|
## set pre baked keys
|
||||||
ARG POSTHOG_API_KEY
|
ARG POSTHOG_API_KEY
|
||||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
|
ENV POSTHOG_API_KEY=$POSTHOG_API_KEY
|
||||||
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
|
|
||||||
ARG INTERCOM_ID=intercom-id
|
ARG INTERCOM_ID=intercom-id
|
||||||
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
ENV INTERCOM_ID=$INTERCOM_ID
|
||||||
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
|
||||||
ARG CAPTCHA_SITE_KEY
|
ARG CAPTCHA_SITE_KEY
|
||||||
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \
|
ENV CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
|
||||||
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
|
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
@ -192,4 +178,4 @@ EXPOSE 443
|
|||||||
|
|
||||||
USER non-root-user
|
USER non-root-user
|
||||||
|
|
||||||
CMD ["./standalone-entrypoint.sh"]
|
CMD ["./standalone-entrypoint.sh"]
|
||||||
|
@ -12,7 +12,7 @@ RUN apk add --no-cache libc6-compat
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY frontend/package.json frontend/package-lock.json frontend/next.config.js ./
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm ci --only-production --ignore-scripts
|
RUN npm ci --only-production --ignore-scripts
|
||||||
@ -27,17 +27,16 @@ COPY --from=frontend-dependencies /app/node_modules ./node_modules
|
|||||||
COPY /frontend .
|
COPY /frontend .
|
||||||
|
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ENV NEXT_PUBLIC_ENV production
|
|
||||||
ARG POSTHOG_HOST
|
ARG POSTHOG_HOST
|
||||||
ENV NEXT_PUBLIC_POSTHOG_HOST $POSTHOG_HOST
|
ENV VITE_POSTHOG_HOST $POSTHOG_HOST
|
||||||
ARG POSTHOG_API_KEY
|
ARG POSTHOG_API_KEY
|
||||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY $POSTHOG_API_KEY
|
ENV VITE_POSTHOG_API_KEY $POSTHOG_API_KEY
|
||||||
ARG INTERCOM_ID
|
ARG INTERCOM_ID
|
||||||
ENV NEXT_PUBLIC_INTERCOM_ID $INTERCOM_ID
|
ENV VITE_INTERCOM_ID $INTERCOM_ID
|
||||||
ARG INFISICAL_PLATFORM_VERSION
|
ARG INFISICAL_PLATFORM_VERSION
|
||||||
ENV NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
ENV VITE_INFISICAL_PLATFORM_VERSION $INFISICAL_PLATFORM_VERSION
|
||||||
ARG CAPTCHA_SITE_KEY
|
ARG CAPTCHA_SITE_KEY
|
||||||
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
ENV VITE_CAPTCHA_SITE_KEY $CAPTCHA_SITE_KEY
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
@ -49,20 +48,10 @@ WORKDIR /app
|
|||||||
RUN addgroup --system --gid 1001 nodejs
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
RUN adduser --system --uid 1001 non-root-user
|
RUN adduser --system --uid 1001 non-root-user
|
||||||
|
|
||||||
RUN mkdir -p /app/.next/cache/images && chown non-root-user:nodejs /app/.next/cache/images
|
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/dist ./
|
||||||
VOLUME /app/.next/cache/images
|
|
||||||
|
|
||||||
COPY --chown=non-root-user:nodejs --chmod=555 frontend/scripts ./scripts
|
|
||||||
COPY --from=frontend-builder /app/public ./public
|
|
||||||
RUN chown non-root-user:nodejs ./public/data
|
|
||||||
|
|
||||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/standalone ./
|
|
||||||
COPY --from=frontend-builder --chown=non-root-user:nodejs /app/.next/static ./.next/static
|
|
||||||
|
|
||||||
USER non-root-user
|
USER non-root-user
|
||||||
|
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## BACKEND
|
## BACKEND
|
||||||
##
|
##
|
||||||
@ -159,14 +148,11 @@ RUN chmod u+rx /usr/sbin/update-ca-certificates
|
|||||||
|
|
||||||
## set pre baked keys
|
## set pre baked keys
|
||||||
ARG POSTHOG_API_KEY
|
ARG POSTHOG_API_KEY
|
||||||
ENV NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY \
|
ENV POSTHOG_API_KEY=$POSTHOG_API_KEY
|
||||||
BAKED_NEXT_PUBLIC_POSTHOG_API_KEY=$POSTHOG_API_KEY
|
|
||||||
ARG INTERCOM_ID=intercom-id
|
ARG INTERCOM_ID=intercom-id
|
||||||
ENV NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID \
|
ENV INTERCOM_ID=$INTERCOM_ID
|
||||||
BAKED_NEXT_PUBLIC_INTERCOM_ID=$INTERCOM_ID
|
|
||||||
ARG CAPTCHA_SITE_KEY
|
ARG CAPTCHA_SITE_KEY
|
||||||
ENV NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY \
|
ENV CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
|
||||||
BAKED_NEXT_PUBLIC_CAPTCHA_SITE_KEY=$CAPTCHA_SITE_KEY
|
|
||||||
|
|
||||||
|
|
||||||
COPY --from=backend-runner /app /backend
|
COPY --from=backend-runner /app /backend
|
||||||
@ -189,4 +175,4 @@ EXPOSE 443
|
|||||||
|
|
||||||
USER non-root-user
|
USER non-root-user
|
||||||
|
|
||||||
CMD ["./standalone-entrypoint.sh"]
|
CMD ["./standalone-entrypoint.sh"]
|
||||||
|
3
Makefile
3
Makefile
@ -30,3 +30,6 @@ reviewable-api:
|
|||||||
npm run type:check
|
npm run type:check
|
||||||
|
|
||||||
reviewable: reviewable-ui reviewable-api
|
reviewable: reviewable-ui reviewable-api
|
||||||
|
|
||||||
|
up-dev-sso:
|
||||||
|
docker compose -f docker-compose.dev.yml --profile sso up --build
|
||||||
|
11
README.md
11
README.md
@ -56,7 +56,7 @@ We're on a mission to make security tooling more accessible to everyone, not jus
|
|||||||
- **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments.
|
- **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments.
|
||||||
- **[Infisical Agent](https://infisical.com/docs/infisical-agent/overview)**: Inject secrets into applications without modifying any code logic.
|
- **[Infisical Agent](https://infisical.com/docs/infisical-agent/overview)**: Inject secrets into applications without modifying any code logic.
|
||||||
|
|
||||||
### Internal PKI:
|
### Infisical (Internal) PKI:
|
||||||
|
|
||||||
- **[Private Certificate Authority](https://infisical.com/docs/documentation/platform/pki/private-ca)**: Create CA hierarchies, configure [certificate templates](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) for policy enforcement, and start issuing X.509 certificates.
|
- **[Private Certificate Authority](https://infisical.com/docs/documentation/platform/pki/private-ca)**: Create CA hierarchies, configure [certificate templates](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) for policy enforcement, and start issuing X.509 certificates.
|
||||||
- **[Certificate Management](https://infisical.com/docs/documentation/platform/pki/certificates)**: Manage the certificate lifecycle from [issuance](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) to [revocation](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-revoking-certificates) with support for CRL.
|
- **[Certificate Management](https://infisical.com/docs/documentation/platform/pki/certificates)**: Manage the certificate lifecycle from [issuance](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) to [revocation](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-revoking-certificates) with support for CRL.
|
||||||
@ -64,12 +64,17 @@ We're on a mission to make security tooling more accessible to everyone, not jus
|
|||||||
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
|
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
|
||||||
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
|
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
|
||||||
|
|
||||||
### Key Management (KMS):
|
### Infisical Key Management System (KMS):
|
||||||
|
|
||||||
- **[Cryptographic Keys](https://infisical.com/docs/documentation/platform/kms)**: Centrally manage keys across projects through a user-friendly interface or via the API.
|
- **[Cryptographic Keys](https://infisical.com/docs/documentation/platform/kms)**: Centrally manage keys across projects through a user-friendly interface or via the API.
|
||||||
- **[Encrypt and Decrypt Data](https://infisical.com/docs/documentation/platform/kms#guide-to-encrypting-data)**: Use symmetric keys to encrypt and decrypt data.
|
- **[Encrypt and Decrypt Data](https://infisical.com/docs/documentation/platform/kms#guide-to-encrypting-data)**: Use symmetric keys to encrypt and decrypt data.
|
||||||
|
|
||||||
|
### Infisical SSH
|
||||||
|
|
||||||
|
- **[Signed SSH Certificates](https://infisical.com/docs/documentation/platform/ssh)**: Issue ephemeral SSH credentials for secure, short-lived, and centralized access to infrastructure.
|
||||||
|
|
||||||
### General Platform:
|
### General Platform:
|
||||||
|
|
||||||
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
|
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
|
||||||
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
|
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
|
||||||
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)**: Track every action taken on the platform.
|
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)**: Track every action taken on the platform.
|
||||||
@ -120,7 +125,7 @@ Install pre commit hook to scan each commit before you push to your repository
|
|||||||
infisical scan install --pre-commit-hook
|
infisical scan install --pre-commit-hook
|
||||||
```
|
```
|
||||||
|
|
||||||
Lean about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview)
|
Learn about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview)
|
||||||
|
|
||||||
## Open-source vs. paid
|
## Open-source vs. paid
|
||||||
|
|
||||||
|
128
backend/package-lock.json
generated
128
backend/package-lock.json
generated
@ -26,6 +26,7 @@
|
|||||||
"@fastify/rate-limit": "^9.0.0",
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
"@fastify/request-context": "^5.1.0",
|
"@fastify/request-context": "^5.1.0",
|
||||||
"@fastify/session": "^10.7.0",
|
"@fastify/session": "^10.7.0",
|
||||||
|
"@fastify/static": "^7.0.4",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
@ -5406,6 +5407,7 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz",
|
||||||
"integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==",
|
"integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
@ -5545,6 +5547,7 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz",
|
||||||
"integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==",
|
"integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lukeed/ms": "^2.0.1",
|
"@lukeed/ms": "^2.0.1",
|
||||||
"escape-html": "~1.0.3",
|
"escape-html": "~1.0.3",
|
||||||
@ -5563,16 +5566,85 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fastify/static": {
|
"node_modules/@fastify/static": {
|
||||||
"version": "6.12.0",
|
"version": "7.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz",
|
||||||
"integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==",
|
"integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/accept-negotiator": "^1.0.0",
|
"@fastify/accept-negotiator": "^1.0.0",
|
||||||
"@fastify/send": "^2.0.0",
|
"@fastify/send": "^2.0.0",
|
||||||
"content-disposition": "^0.5.3",
|
"content-disposition": "^0.5.3",
|
||||||
"fastify-plugin": "^4.0.0",
|
"fastify-plugin": "^4.0.0",
|
||||||
"glob": "^8.0.1",
|
"fastq": "^1.17.0",
|
||||||
"p-limit": "^3.1.0"
|
"glob": "^10.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fastify/static/node_modules/brace-expansion": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fastify/static/node_modules/glob": {
|
||||||
|
"version": "10.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||||
|
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"foreground-child": "^3.1.0",
|
||||||
|
"jackspeak": "^3.1.2",
|
||||||
|
"minimatch": "^9.0.4",
|
||||||
|
"minipass": "^7.1.2",
|
||||||
|
"package-json-from-dist": "^1.0.0",
|
||||||
|
"path-scurry": "^1.11.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"glob": "dist/esm/bin.mjs"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fastify/static/node_modules/jackspeak": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||||
|
"license": "BlueOak-1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@isaacs/cliui": "^8.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fastify/static/node_modules/minimatch": {
|
||||||
|
"version": "9.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fastify/static/node_modules/minipass": {
|
||||||
|
"version": "7.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||||
|
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fastify/swagger": {
|
"node_modules/@fastify/swagger": {
|
||||||
@ -5599,6 +5671,20 @@
|
|||||||
"yaml": "^2.2.2"
|
"yaml": "^2.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fastify/swagger-ui/node_modules/@fastify/static": {
|
||||||
|
"version": "6.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-6.12.0.tgz",
|
||||||
|
"integrity": "sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/accept-negotiator": "^1.0.0",
|
||||||
|
"@fastify/send": "^2.0.0",
|
||||||
|
"content-disposition": "^0.5.3",
|
||||||
|
"fastify-plugin": "^4.0.0",
|
||||||
|
"glob": "^8.0.1",
|
||||||
|
"p-limit": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@google-cloud/kms": {
|
"node_modules/@google-cloud/kms": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@google-cloud/kms/-/kms-4.5.0.tgz",
|
||||||
@ -6062,9 +6148,10 @@
|
|||||||
"integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ=="
|
"integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@lukeed/ms": {
|
"node_modules/@lukeed/ms": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
|
||||||
"integrity": "sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==",
|
"integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
@ -13879,9 +13966,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.21.1",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
@ -13903,7 +13990,7 @@
|
|||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.10",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.13.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
@ -13918,6 +14005,10 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-session": {
|
"node_modules/express-session": {
|
||||||
@ -17388,15 +17479,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/ai"
|
"url": "https://github.com/sponsors/ai"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"nanoid": "bin/nanoid.cjs"
|
"nanoid": "bin/nanoid.cjs"
|
||||||
},
|
},
|
||||||
@ -18383,9 +18475,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/path-type": {
|
"node_modules/path-type": {
|
||||||
|
@ -134,6 +134,7 @@
|
|||||||
"@fastify/rate-limit": "^9.0.0",
|
"@fastify/rate-limit": "^9.0.0",
|
||||||
"@fastify/request-context": "^5.1.0",
|
"@fastify/request-context": "^5.1.0",
|
||||||
"@fastify/session": "^10.7.0",
|
"@fastify/session": "^10.7.0",
|
||||||
|
"@fastify/static": "^7.0.4",
|
||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
|
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@ -80,6 +80,7 @@ import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-
|
|||||||
import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
||||||
import { TSecretReplicationServiceFactory } from "@app/services/secret-replication/secret-replication-service";
|
import { TSecretReplicationServiceFactory } from "@app/services/secret-replication/secret-replication-service";
|
||||||
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
|
import { TSecretSyncServiceFactory } from "@app/services/secret-sync/secret-sync-service";
|
||||||
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
||||||
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
||||||
import { TSlackServiceFactory } from "@app/services/slack/slack-service";
|
import { TSlackServiceFactory } from "@app/services/slack/slack-service";
|
||||||
@ -210,6 +211,7 @@ declare module "fastify" {
|
|||||||
projectTemplate: TProjectTemplateServiceFactory;
|
projectTemplate: TProjectTemplateServiceFactory;
|
||||||
totp: TTotpServiceFactory;
|
totp: TTotpServiceFactory;
|
||||||
appConnection: TAppConnectionServiceFactory;
|
appConnection: TAppConnectionServiceFactory;
|
||||||
|
secretSync: TSecretSyncServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
10
backend/src/@types/knex.d.ts
vendored
10
backend/src/@types/knex.d.ts
vendored
@ -218,6 +218,9 @@ import {
|
|||||||
TRateLimit,
|
TRateLimit,
|
||||||
TRateLimitInsert,
|
TRateLimitInsert,
|
||||||
TRateLimitUpdate,
|
TRateLimitUpdate,
|
||||||
|
TResourceMetadata,
|
||||||
|
TResourceMetadataInsert,
|
||||||
|
TResourceMetadataUpdate,
|
||||||
TSamlConfigs,
|
TSamlConfigs,
|
||||||
TSamlConfigsInsert,
|
TSamlConfigsInsert,
|
||||||
TSamlConfigsUpdate,
|
TSamlConfigsUpdate,
|
||||||
@ -369,6 +372,7 @@ import {
|
|||||||
TExternalGroupOrgRoleMappingsInsert,
|
TExternalGroupOrgRoleMappingsInsert,
|
||||||
TExternalGroupOrgRoleMappingsUpdate
|
TExternalGroupOrgRoleMappingsUpdate
|
||||||
} from "@app/db/schemas/external-group-org-role-mappings";
|
} from "@app/db/schemas/external-group-org-role-mappings";
|
||||||
|
import { TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate } from "@app/db/schemas/secret-syncs";
|
||||||
import {
|
import {
|
||||||
TSecretV2TagJunction,
|
TSecretV2TagJunction,
|
||||||
TSecretV2TagJunctionInsert,
|
TSecretV2TagJunctionInsert,
|
||||||
@ -887,10 +891,16 @@ declare module "knex/types/tables" {
|
|||||||
TProjectSplitBackfillIdsInsert,
|
TProjectSplitBackfillIdsInsert,
|
||||||
TProjectSplitBackfillIdsUpdate
|
TProjectSplitBackfillIdsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.ResourceMetadata]: KnexOriginal.CompositeTableType<
|
||||||
|
TResourceMetadata,
|
||||||
|
TResourceMetadataInsert,
|
||||||
|
TResourceMetadataUpdate
|
||||||
|
>;
|
||||||
[TableName.AppConnection]: KnexOriginal.CompositeTableType<
|
[TableName.AppConnection]: KnexOriginal.CompositeTableType<
|
||||||
TAppConnections,
|
TAppConnections,
|
||||||
TAppConnectionsInsert,
|
TAppConnectionsInsert,
|
||||||
TAppConnectionsUpdate
|
TAppConnectionsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretSync]: KnexOriginal.CompositeTableType<TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ResourceMetadata))) {
|
||||||
|
await knex.schema.createTable(TableName.ResourceMetadata, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("key").notNullable();
|
||||||
|
tb.string("value", 1020).notNullable();
|
||||||
|
tb.uuid("orgId").notNullable();
|
||||||
|
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
tb.uuid("userId");
|
||||||
|
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
tb.uuid("identityId");
|
||||||
|
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
tb.uuid("secretId");
|
||||||
|
tb.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
|
||||||
|
if (!hasSecretMetadataField) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
|
||||||
|
t.jsonb("secretMetadata");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ResourceMetadata);
|
||||||
|
|
||||||
|
const hasSecretMetadataField = await knex.schema.hasColumn(TableName.SecretApprovalRequestSecretV2, "secretMetadata");
|
||||||
|
if (hasSecretMetadataField) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretApprovalRequestSecretV2, (t) => {
|
||||||
|
t.dropColumn("secretMetadata");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// find any duplicate group names within organizations
|
||||||
|
const duplicates = await knex(TableName.Groups)
|
||||||
|
.select("orgId", "name")
|
||||||
|
.count("* as count")
|
||||||
|
.groupBy("orgId", "name")
|
||||||
|
.having(knex.raw("count(*) > 1"));
|
||||||
|
|
||||||
|
// for each set of duplicates, update all but one with a numbered suffix
|
||||||
|
for await (const duplicate of duplicates) {
|
||||||
|
const groups = await knex(TableName.Groups)
|
||||||
|
.select("id", "name")
|
||||||
|
.where({
|
||||||
|
orgId: duplicate.orgId,
|
||||||
|
name: duplicate.name
|
||||||
|
})
|
||||||
|
.orderBy("createdAt", "asc"); // keep original name for oldest group
|
||||||
|
|
||||||
|
// skip the first (oldest) group, rename others with numbered suffix
|
||||||
|
for (let i = 1; i < groups.length; i += 1) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await knex(TableName.Groups)
|
||||||
|
.where("id", groups[i].id)
|
||||||
|
.update({
|
||||||
|
name: `${groups[i].name} (${i})`,
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore TS doesn't know about Knex's timestamp types
|
||||||
|
updatedAt: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the unique constraint
|
||||||
|
await knex.schema.alterTable(TableName.Groups, (t) => {
|
||||||
|
t.unique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
// Remove the unique constraint
|
||||||
|
await knex.schema.alterTable(TableName.Groups, (t) => {
|
||||||
|
t.dropUnique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasEnforceCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "enforceCapitalization");
|
||||||
|
const hasAutoCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "autoCapitalization");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||||
|
if (!hasEnforceCapitalizationCol) {
|
||||||
|
t.boolean("enforceCapitalization").defaultTo(false).notNullable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAutoCapitalizationCol) {
|
||||||
|
t.boolean("autoCapitalization").defaultTo(false).alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasEnforceCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "enforceCapitalization");
|
||||||
|
const hasAutoCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "autoCapitalization");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||||
|
if (hasEnforceCapitalizationCol) {
|
||||||
|
t.dropColumn("enforceCapitalization");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAutoCapitalizationCol) {
|
||||||
|
t.boolean("autoCapitalization").defaultTo(true).alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
50
backend/src/db/migrations/20250122055102_secret-sync.ts
Normal file
50
backend/src/db/migrations/20250122055102_secret-sync.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretSync))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretSync, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("name", 32).notNullable();
|
||||||
|
t.string("description");
|
||||||
|
t.string("destination").notNullable();
|
||||||
|
t.boolean("isAutoSyncEnabled").notNullable().defaultTo(true);
|
||||||
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
|
t.jsonb("destinationConfig").notNullable();
|
||||||
|
t.jsonb("syncOptions").notNullable();
|
||||||
|
// we're including projectId in addition to folder ID because we allow folderId to be null (if the folder
|
||||||
|
// is deleted), to preserve sync configuration
|
||||||
|
t.string("projectId").notNullable();
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
t.uuid("folderId");
|
||||||
|
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("SET NULL");
|
||||||
|
t.uuid("connectionId").notNullable();
|
||||||
|
t.foreign("connectionId").references("id").inTable(TableName.AppConnection);
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
// sync secrets to destination
|
||||||
|
t.string("syncStatus");
|
||||||
|
t.string("lastSyncJobId");
|
||||||
|
t.string("lastSyncMessage");
|
||||||
|
t.datetime("lastSyncedAt");
|
||||||
|
// import secrets from destination
|
||||||
|
t.string("importStatus");
|
||||||
|
t.string("lastImportJobId");
|
||||||
|
t.string("lastImportMessage");
|
||||||
|
t.datetime("lastImportedAt");
|
||||||
|
// remove secrets from destination
|
||||||
|
t.string("removeStatus");
|
||||||
|
t.string("lastRemoveJobId");
|
||||||
|
t.string("lastRemoveMessage");
|
||||||
|
t.datetime("lastRemovedAt");
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SecretSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretSync);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SecretSync);
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasManageGroupMembershipsCol = await knex.schema.hasColumn(TableName.OidcConfig, "manageGroupMemberships");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
|
||||||
|
if (!hasManageGroupMembershipsCol) {
|
||||||
|
tb.boolean("manageGroupMemberships").notNullable().defaultTo(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasManageGroupMembershipsCol = await knex.schema.hasColumn(TableName.OidcConfig, "manageGroupMemberships");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
|
||||||
|
if (hasManageGroupMembershipsCol) {
|
||||||
|
t.dropColumn("manageGroupMemberships");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||||
|
t.unique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
t.unique(["projectId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||||
|
t.dropUnique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
t.dropUnique(["projectId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasTable = await knex.schema.hasTable(TableName.IdentityGcpAuth);
|
||||||
|
const hasAllowedProjectsColumn = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedProjects");
|
||||||
|
const hasAllowedServiceAccountsColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.IdentityGcpAuth,
|
||||||
|
"allowedServiceAccounts"
|
||||||
|
);
|
||||||
|
const hasAllowedZones = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedZones");
|
||||||
|
if (hasTable) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
|
||||||
|
if (hasAllowedProjectsColumn) t.string("allowedProjects", 2500).alter();
|
||||||
|
if (hasAllowedServiceAccountsColumn) t.string("allowedServiceAccounts", 5000).alter();
|
||||||
|
if (hasAllowedZones) t.string("allowedZones", 2500).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasTable = await knex.schema.hasTable(TableName.IdentityGcpAuth);
|
||||||
|
const hasAllowedProjectsColumn = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedProjects");
|
||||||
|
const hasAllowedServiceAccountsColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.IdentityGcpAuth,
|
||||||
|
"allowedServiceAccounts"
|
||||||
|
);
|
||||||
|
const hasAllowedZones = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedZones");
|
||||||
|
if (hasTable) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
|
||||||
|
if (hasAllowedProjectsColumn) t.string("allowedProjects").alter();
|
||||||
|
if (hasAllowedServiceAccountsColumn) t.string("allowedServiceAccounts").alter();
|
||||||
|
if (hasAllowedZones) t.string("allowedZones").alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlugCol = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (hasSlugCol) {
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (t) => {
|
||||||
|
t.dropColumn("slug");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlugCol = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (!hasSlugCol) {
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (t) => {
|
||||||
|
t.string("slug", 32);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSync)) {
|
||||||
|
const hasLastSyncMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastSyncMessage");
|
||||||
|
const hasLastImportMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastImportMessage");
|
||||||
|
const hasLastRemoveMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastRemoveMessage");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
if (hasLastSyncMessage) t.string("lastSyncMessage", 1024).alter();
|
||||||
|
if (hasLastImportMessage) t.string("lastImportMessage", 1024).alter();
|
||||||
|
if (hasLastRemoveMessage) t.string("lastRemoveMessage", 1024).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSync)) {
|
||||||
|
const hasLastSyncMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastSyncMessage");
|
||||||
|
const hasLastImportMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastImportMessage");
|
||||||
|
const hasLastRemoveMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastRemoveMessage");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
if (hasLastSyncMessage) t.string("lastSyncMessage").alter();
|
||||||
|
if (hasLastImportMessage) t.string("lastImportMessage").alter();
|
||||||
|
if (hasLastRemoveMessage) t.string("lastRemoveMessage").alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -17,9 +17,9 @@ export const IdentityGcpAuthsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
allowedServiceAccounts: z.string(),
|
allowedServiceAccounts: z.string().nullable().optional(),
|
||||||
allowedProjects: z.string(),
|
allowedProjects: z.string().nullable().optional(),
|
||||||
allowedZones: z.string()
|
allowedZones: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
|
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
|
||||||
|
@ -71,6 +71,7 @@ export * from "./project-user-additional-privilege";
|
|||||||
export * from "./project-user-membership-roles";
|
export * from "./project-user-membership-roles";
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
export * from "./rate-limit";
|
export * from "./rate-limit";
|
||||||
|
export * from "./resource-metadata";
|
||||||
export * from "./saml-configs";
|
export * from "./saml-configs";
|
||||||
export * from "./scim-tokens";
|
export * from "./scim-tokens";
|
||||||
export * from "./secret-approval-policies";
|
export * from "./secret-approval-policies";
|
||||||
|
@ -16,8 +16,7 @@ export const KmsKeysSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
projectId: z.string().nullable().optional(),
|
projectId: z.string().nullable().optional()
|
||||||
slug: z.string().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||||
|
@ -80,6 +80,7 @@ export enum TableName {
|
|||||||
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
||||||
// used by both identity and users
|
// used by both identity and users
|
||||||
IdentityMetadata = "identity_metadata",
|
IdentityMetadata = "identity_metadata",
|
||||||
|
ResourceMetadata = "resource_metadata",
|
||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
AccessApprovalPolicy = "access_approval_policies",
|
AccessApprovalPolicy = "access_approval_policies",
|
||||||
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
||||||
@ -130,7 +131,8 @@ export enum TableName {
|
|||||||
WorkflowIntegrations = "workflow_integrations",
|
WorkflowIntegrations = "workflow_integrations",
|
||||||
SlackIntegrations = "slack_integrations",
|
SlackIntegrations = "slack_integrations",
|
||||||
ProjectSlackConfigs = "project_slack_configs",
|
ProjectSlackConfigs = "project_slack_configs",
|
||||||
AppConnection = "app_connections"
|
AppConnection = "app_connections",
|
||||||
|
SecretSync = "secret_syncs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
@ -214,3 +216,12 @@ export enum ProjectType {
|
|||||||
KMS = "kms",
|
KMS = "kms",
|
||||||
SSH = "ssh"
|
SSH = "ssh"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ActionProjectType {
|
||||||
|
SecretManager = ProjectType.SecretManager,
|
||||||
|
CertificateManager = ProjectType.CertificateManager,
|
||||||
|
KMS = ProjectType.KMS,
|
||||||
|
SSH = ProjectType.SSH,
|
||||||
|
// project operations that happen on all types
|
||||||
|
Any = "any"
|
||||||
|
}
|
||||||
|
@ -27,7 +27,8 @@ export const OidcConfigsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
lastUsed: z.date().nullable().optional()
|
lastUsed: z.date().nullable().optional(),
|
||||||
|
manageGroupMemberships: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
||||||
|
@ -13,7 +13,7 @@ export const ProjectsSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
autoCapitalization: z.boolean().default(true).nullable().optional(),
|
autoCapitalization: z.boolean().default(false).nullable().optional(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
@ -25,7 +25,8 @@ export const ProjectsSchema = z.object({
|
|||||||
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
||||||
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
type: z.string()
|
type: z.string(),
|
||||||
|
enforceCapitalization: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
24
backend/src/db/schemas/resource-metadata.ts
Normal file
24
backend/src/db/schemas/resource-metadata.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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 ResourceMetadataSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
identityId: z.string().uuid().nullable().optional(),
|
||||||
|
secretId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TResourceMetadata = z.infer<typeof ResourceMetadataSchema>;
|
||||||
|
export type TResourceMetadataInsert = Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>;
|
||||||
|
export type TResourceMetadataUpdate = Partial<Omit<z.input<typeof ResourceMetadataSchema>, TImmutableDBKeys>>;
|
@ -24,7 +24,8 @@ export const SecretApprovalRequestsSecretsV2Schema = z.object({
|
|||||||
requestId: z.string().uuid(),
|
requestId: z.string().uuid(),
|
||||||
op: z.string(),
|
op: z.string(),
|
||||||
secretId: z.string().uuid().nullable().optional(),
|
secretId: z.string().uuid().nullable().optional(),
|
||||||
secretVersion: z.string().uuid().nullable().optional()
|
secretVersion: z.string().uuid().nullable().optional(),
|
||||||
|
secretMetadata: z.unknown().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>;
|
export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>;
|
||||||
|
40
backend/src/db/schemas/secret-syncs.ts
Normal file
40
backend/src/db/schemas/secret-syncs.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// 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 SecretSyncsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
destination: z.string(),
|
||||||
|
isAutoSyncEnabled: z.boolean().default(true),
|
||||||
|
version: z.number().default(1),
|
||||||
|
destinationConfig: z.unknown(),
|
||||||
|
syncOptions: z.unknown(),
|
||||||
|
projectId: z.string(),
|
||||||
|
folderId: z.string().uuid().nullable().optional(),
|
||||||
|
connectionId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
syncStatus: z.string().nullable().optional(),
|
||||||
|
lastSyncJobId: z.string().nullable().optional(),
|
||||||
|
lastSyncMessage: z.string().nullable().optional(),
|
||||||
|
lastSyncedAt: z.date().nullable().optional(),
|
||||||
|
importStatus: z.string().nullable().optional(),
|
||||||
|
lastImportJobId: z.string().nullable().optional(),
|
||||||
|
lastImportMessage: z.string().nullable().optional(),
|
||||||
|
lastImportedAt: z.date().nullable().optional(),
|
||||||
|
removeStatus: z.string().nullable().optional(),
|
||||||
|
lastRemoveJobId: z.string().nullable().optional(),
|
||||||
|
lastRemoveMessage: z.string().nullable().optional(),
|
||||||
|
lastRemovedAt: z.date().nullable().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretSyncs = z.infer<typeof SecretSyncsSchema>;
|
||||||
|
export type TSecretSyncsInsert = Omit<z.input<typeof SecretSyncsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretSyncsUpdate = Partial<Omit<z.input<typeof SecretSyncsSchema>, TImmutableDBKeys>>;
|
@ -22,6 +22,7 @@ import { registerSecretApprovalPolicyRouter } from "./secret-approval-policy-rou
|
|||||||
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
|
import { registerSecretApprovalRequestRouter } from "./secret-approval-request-router";
|
||||||
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
|
import { registerSecretRotationProviderRouter } from "./secret-rotation-provider-router";
|
||||||
import { registerSecretRotationRouter } from "./secret-rotation-router";
|
import { registerSecretRotationRouter } from "./secret-rotation-router";
|
||||||
|
import { registerSecretRouter } from "./secret-router";
|
||||||
import { registerSecretScanningRouter } from "./secret-scanning-router";
|
import { registerSecretScanningRouter } from "./secret-scanning-router";
|
||||||
import { registerSecretVersionRouter } from "./secret-version-router";
|
import { registerSecretVersionRouter } from "./secret-version-router";
|
||||||
import { registerSnapshotRouter } from "./snapshot-router";
|
import { registerSnapshotRouter } from "./snapshot-router";
|
||||||
@ -92,6 +93,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
await server.register(registerLdapRouter, { prefix: "/ldap" });
|
||||||
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
await server.register(registerSecretScanningRouter, { prefix: "/secret-scanning" });
|
||||||
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
await server.register(registerSecretRotationRouter, { prefix: "/secret-rotations" });
|
||||||
|
await server.register(registerSecretRouter, { prefix: "/secrets" });
|
||||||
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
||||||
await server.register(registerGroupRouter, { prefix: "/groups" });
|
await server.register(registerGroupRouter, { prefix: "/groups" });
|
||||||
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
|
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
|
||||||
|
@ -153,7 +153,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
discoveryURL: true,
|
discoveryURL: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
allowedEmailDomains: true
|
allowedEmailDomains: true,
|
||||||
|
manageGroupMemberships: true
|
||||||
}).extend({
|
}).extend({
|
||||||
clientId: z.string(),
|
clientId: z.string(),
|
||||||
clientSecret: z.string()
|
clientSecret: z.string()
|
||||||
@ -207,7 +208,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
userinfoEndpoint: z.string().trim(),
|
userinfoEndpoint: z.string().trim(),
|
||||||
clientId: z.string().trim(),
|
clientId: z.string().trim(),
|
||||||
clientSecret: z.string().trim(),
|
clientSecret: z.string().trim(),
|
||||||
isActive: z.boolean()
|
isActive: z.boolean(),
|
||||||
|
manageGroupMemberships: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.merge(z.object({ orgSlug: z.string() })),
|
.merge(z.object({ orgSlug: z.string() })),
|
||||||
@ -223,7 +225,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
userinfoEndpoint: true,
|
userinfoEndpoint: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
allowedEmailDomains: true,
|
allowedEmailDomains: true,
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
manageGroupMemberships: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -272,7 +275,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientId: z.string().trim(),
|
clientId: z.string().trim(),
|
||||||
clientSecret: z.string().trim(),
|
clientSecret: z.string().trim(),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
orgSlug: z.string().trim()
|
orgSlug: z.string().trim(),
|
||||||
|
manageGroupMemberships: z.boolean().optional().default(false)
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
||||||
@ -334,7 +338,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
userinfoEndpoint: true,
|
userinfoEndpoint: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
allowedEmailDomains: true
|
allowedEmailDomains: true,
|
||||||
|
manageGroupMemberships: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -350,4 +355,25 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
return oidc;
|
return oidc;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/manage-group-memberships",
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
orgId: z.string().trim().min(1, "Org ID is required")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
isEnabled: z.boolean()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const isEnabled = await server.services.oidc.isOidcManageGroupMembershipsEnabled(req.query.orgId, req.permission);
|
||||||
|
|
||||||
|
return { isEnabled };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
),
|
),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().nullish(),
|
description: z.string().trim().nullish(),
|
||||||
|
// TODO(scott): once UI refactored permissions: OrgPermissionSchema.array()
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -96,6 +97,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
description: z.string().trim().nullish(),
|
description: z.string().trim().nullish(),
|
||||||
|
// TODO(scott): once UI refactored permissions: OrgPermissionSchema.array().optional()
|
||||||
permissions: z.any().array().optional()
|
permissions: z.any().array().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@ -84,7 +84,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
samlConfig.audience = `spn:${ssoConfig.issuer}`;
|
samlConfig.audience = `spn:${ssoConfig.issuer}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ssoConfig.authProvider === SamlProviders.GOOGLE_SAML) {
|
if (
|
||||||
|
ssoConfig.authProvider === SamlProviders.GOOGLE_SAML ||
|
||||||
|
ssoConfig.authProvider === SamlProviders.AUTH0_SAML
|
||||||
|
) {
|
||||||
samlConfig.wantAssertionsSigned = false;
|
samlConfig.wantAssertionsSigned = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +126,10 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
`email: ${email} firstName: ${profile.firstName as string}`
|
`email: ${email} firstName: ${profile.firstName as string}`
|
||||||
);
|
);
|
||||||
|
|
||||||
throw new Error("Invalid saml request. Missing email or first name");
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Missing email or first name. Please double check your SAML attribute mapping for the selected provider."
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const userMetadata = Object.keys(profile.attributes || {})
|
const userMetadata = Object.keys(profile.attributes || {})
|
||||||
|
@ -12,6 +12,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
|
|
||||||
const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge(
|
const approvalRequestUser = z.object({ userId: z.string().nullable().optional() }).merge(
|
||||||
UsersSchema.pick({
|
UsersSchema.pick({
|
||||||
@ -274,6 +275,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
.extend({
|
.extend({
|
||||||
op: z.string(),
|
op: z.string(),
|
||||||
tags: tagSchema,
|
tags: tagSchema,
|
||||||
|
secretMetadata: ResourceMetadataSchema.nullish(),
|
||||||
secret: z
|
secret: z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
@ -291,7 +293,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
secretKey: z.string(),
|
secretKey: z.string(),
|
||||||
secretValue: z.string().optional(),
|
secretValue: z.string().optional(),
|
||||||
secretComment: z.string().optional(),
|
secretComment: z.string().optional(),
|
||||||
tags: tagSchema
|
tags: tagSchema,
|
||||||
|
secretMetadata: ResourceMetadataSchema.nullish()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
|
71
backend/src/ee/routes/v1/secret-router.ts
Normal file
71
backend/src/ee/routes/v1/secret-router.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { ProjectPermissionActions } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { RAW_SECRETS } from "@app/lib/api-docs";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const AccessListEntrySchema = z
|
||||||
|
.object({
|
||||||
|
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
|
||||||
|
id: z.string(),
|
||||||
|
membershipId: z.string(),
|
||||||
|
name: z.string()
|
||||||
|
})
|
||||||
|
.array();
|
||||||
|
|
||||||
|
export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:secretName/access-list",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get list of users, machine identities, and groups with access to a secret",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
secretName: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.secretName)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
workspaceId: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.workspaceId),
|
||||||
|
environment: z.string().trim().describe(RAW_SECRETS.GET_ACCESS_LIST.environment),
|
||||||
|
secretPath: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.default("/")
|
||||||
|
.transform(removeTrailingSlash)
|
||||||
|
.describe(RAW_SECRETS.GET_ACCESS_LIST.secretPath)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
groups: AccessListEntrySchema,
|
||||||
|
identities: AccessListEntrySchema,
|
||||||
|
users: AccessListEntrySchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { secretName } = req.params;
|
||||||
|
const { secretPath, environment, workspaceId: projectId } = req.query;
|
||||||
|
|
||||||
|
return server.services.secret.getSecretAccessList({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
projectId,
|
||||||
|
secretName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -1,9 +1,13 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
import {
|
||||||
|
SecretScanningResolvedStatus,
|
||||||
|
SecretScanningRiskStatus
|
||||||
|
} from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@ -97,6 +101,45 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/organization/:organizationId/risks/export",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
querystring: z.object({
|
||||||
|
repositoryNames: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.nullable()
|
||||||
|
.transform((val) => (val ? val.split(",") : undefined)),
|
||||||
|
resolvedStatus: z.nativeEnum(SecretScanningResolvedStatus).optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
risks: SecretScanningGitRisksSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const risks = await server.services.secretScanning.getAllRisksByOrg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
orgId: req.params.organizationId,
|
||||||
|
filter: {
|
||||||
|
repositoryNames: req.query.repositoryNames,
|
||||||
|
resolvedStatus: req.query.resolvedStatus
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { risks };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/organization/:organizationId/risks",
|
url: "/organization/:organizationId/risks",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@ -105,20 +148,46 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
|
||||||
|
querystring: z.object({
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
limit: z.coerce.number().min(1).max(20000).default(100),
|
||||||
|
orderBy: z.enum(["createdAt", "name"]).default("createdAt"),
|
||||||
|
orderDirection: z.nativeEnum(OrderByDirection).default(OrderByDirection.DESC),
|
||||||
|
repositoryNames: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.nullable()
|
||||||
|
.transform((val) => (val ? val.split(",") : undefined)),
|
||||||
|
resolvedStatus: z.nativeEnum(SecretScanningResolvedStatus).optional()
|
||||||
|
}),
|
||||||
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ risks: SecretScanningGitRisksSchema.array() })
|
200: z.object({
|
||||||
|
risks: SecretScanningGitRisksSchema.array(),
|
||||||
|
totalCount: z.number(),
|
||||||
|
repos: z.array(z.string())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { risks } = await server.services.secretScanning.getRisksByOrg({
|
const { risks, totalCount, repos } = await server.services.secretScanning.getRisksByOrg({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId,
|
||||||
|
filter: {
|
||||||
|
limit: req.query.limit,
|
||||||
|
offset: req.query.offset,
|
||||||
|
orderBy: req.query.orderBy,
|
||||||
|
orderDirection: req.query.orderDirection,
|
||||||
|
repositoryNames: req.query.repositoryNames,
|
||||||
|
resolvedStatus: req.query.resolvedStatus
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { risks };
|
return { risks, totalCount, repos };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@ -87,14 +87,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
@ -193,7 +193,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
// Anyone in the project should be able to get the policies.
|
// Anyone in the project should be able to get the policies.
|
||||||
await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: project.id,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||||
return accessApprovalPolicies;
|
return accessApprovalPolicies;
|
||||||
@ -237,14 +244,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
if (!accessApprovalPolicy) {
|
if (!accessApprovalPolicy) {
|
||||||
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||||
}
|
}
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
accessApprovalPolicy.projectId,
|
projectId: accessApprovalPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
@ -321,14 +328,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
||||||
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
policy.projectId,
|
projectId: policy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
@ -372,13 +379,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
@ -411,13 +419,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
policy.projectId,
|
projectId: policy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@ -100,13 +100,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
// Anyone can create an access approval request.
|
// Anyone can create an access approval request.
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
@ -213,7 +214,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||||
const approvalUrl = `${cfg.SITE_URL}/project/${project.id}/approval`;
|
const approvalUrl = `${cfg.SITE_URL}/secret-manager/${project.id}/approval`;
|
||||||
|
|
||||||
await triggerSlackNotification({
|
await triggerSlackNotification({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
@ -273,13 +274,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
@ -318,13 +320,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
accessApprovalRequest.projectId,
|
projectId: accessApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
@ -422,13 +425,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw new Error(`Failed to connect with the source ${(err as Error)?.message}`);
|
throw new BadRequestError({ message: `Failed to connect with upstream source: ${(err as Error)?.message}` });
|
||||||
});
|
});
|
||||||
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
|
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
|
||||||
const logStream = await auditLogStreamDAL.create({
|
const logStream = await auditLogStreamDAL.create({
|
||||||
|
@ -39,11 +39,13 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
offset = 0,
|
offset = 0,
|
||||||
actorId,
|
actorId,
|
||||||
actorType,
|
actorType,
|
||||||
|
secretPath,
|
||||||
eventType,
|
eventType,
|
||||||
eventMetadata
|
eventMetadata
|
||||||
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
||||||
actorId?: string;
|
actorId?: string;
|
||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
|
secretPath?: string;
|
||||||
eventType?: EventType[];
|
eventType?: EventType[];
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
},
|
},
|
||||||
@ -88,6 +90,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (projectId && secretPath) {
|
||||||
|
void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object('secretPath', ?::text)`, [secretPath]);
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by actor type
|
// Filter by actor type
|
||||||
if (actorType) {
|
if (actorType) {
|
||||||
void sqlQuery.where("actor", actorType);
|
void sqlQuery.where("actor", actorType);
|
||||||
@ -100,10 +106,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
// Filter by date range
|
// Filter by date range
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate);
|
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" >= ?::timestamptz`, [startDate]);
|
||||||
}
|
}
|
||||||
if (endDate) {
|
if (endDate) {
|
||||||
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
|
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" <= ?::timestamptz`, [endDate]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
@ -26,13 +27,14 @@ export const auditLogServiceFactory = ({
|
|||||||
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
||||||
// Filter logs for specific project
|
// Filter logs for specific project
|
||||||
if (filter.projectId) {
|
if (filter.projectId) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
filter.projectId,
|
projectId: filter.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
} else {
|
} else {
|
||||||
// Organization-wide logs
|
// Organization-wide logs
|
||||||
@ -44,10 +46,6 @@ export const auditLogServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE (dangtony98): Update this to organization-level audit log permission check once audit logs are moved
|
|
||||||
* to the organization level ✅
|
|
||||||
*/
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +60,7 @@ export const auditLogServiceFactory = ({
|
|||||||
actorId: filter.auditLogActorId,
|
actorId: filter.auditLogActorId,
|
||||||
actorType: filter.actorType,
|
actorType: filter.actorType,
|
||||||
eventMetadata: filter.eventMetadata,
|
eventMetadata: filter.eventMetadata,
|
||||||
|
secretPath: filter.secretPath,
|
||||||
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,7 +78,8 @@ export const auditLogServiceFactory = ({
|
|||||||
}
|
}
|
||||||
// add all cases in which project id or org id cannot be added
|
// add all cases in which project id or org id cannot be added
|
||||||
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
||||||
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
|
if (!data.projectId && !data.orgId)
|
||||||
|
throw new BadRequestError({ message: "Must specify either project id or org id" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return auditLogQueue.pushToLog(data);
|
return auditLogQueue.pushToLog(data);
|
||||||
|
@ -13,6 +13,13 @@ import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
|||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
||||||
|
import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
TCreateSecretSyncDTO,
|
||||||
|
TDeleteSecretSyncDTO,
|
||||||
|
TSecretSyncRaw,
|
||||||
|
TUpdateSecretSyncDTO
|
||||||
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
export type TListProjectAuditLogDTO = {
|
export type TListProjectAuditLogDTO = {
|
||||||
filter: {
|
filter: {
|
||||||
@ -25,13 +32,14 @@ export type TListProjectAuditLogDTO = {
|
|||||||
projectId?: string;
|
projectId?: string;
|
||||||
auditLogActorId?: string;
|
auditLogActorId?: string;
|
||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
|
secretPath?: string;
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateAuditLogDTO = {
|
export type TCreateAuditLogDTO = {
|
||||||
event: Event;
|
event: Event;
|
||||||
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor;
|
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor | UnknownUserActor;
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
} & BaseAuthData;
|
} & BaseAuthData;
|
||||||
@ -215,6 +223,7 @@ export enum EventType {
|
|||||||
UPDATE_CMEK = "update-cmek",
|
UPDATE_CMEK = "update-cmek",
|
||||||
DELETE_CMEK = "delete-cmek",
|
DELETE_CMEK = "delete-cmek",
|
||||||
GET_CMEKS = "get-cmeks",
|
GET_CMEKS = "get-cmeks",
|
||||||
|
GET_CMEK = "get-cmek",
|
||||||
CMEK_ENCRYPT = "cmek-encrypt",
|
CMEK_ENCRYPT = "cmek-encrypt",
|
||||||
CMEK_DECRYPT = "cmek-decrypt",
|
CMEK_DECRYPT = "cmek-decrypt",
|
||||||
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
|
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
|
||||||
@ -226,10 +235,24 @@ export enum EventType {
|
|||||||
DELETE_PROJECT_TEMPLATE = "delete-project-template",
|
DELETE_PROJECT_TEMPLATE = "delete-project-template",
|
||||||
APPLY_PROJECT_TEMPLATE = "apply-project-template",
|
APPLY_PROJECT_TEMPLATE = "apply-project-template",
|
||||||
GET_APP_CONNECTIONS = "get-app-connections",
|
GET_APP_CONNECTIONS = "get-app-connections",
|
||||||
|
GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details",
|
||||||
GET_APP_CONNECTION = "get-app-connection",
|
GET_APP_CONNECTION = "get-app-connection",
|
||||||
CREATE_APP_CONNECTION = "create-app-connection",
|
CREATE_APP_CONNECTION = "create-app-connection",
|
||||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
UPDATE_APP_CONNECTION = "update-app-connection",
|
||||||
DELETE_APP_CONNECTION = "delete-app-connection"
|
DELETE_APP_CONNECTION = "delete-app-connection",
|
||||||
|
CREATE_SHARED_SECRET = "create-shared-secret",
|
||||||
|
DELETE_SHARED_SECRET = "delete-shared-secret",
|
||||||
|
READ_SHARED_SECRET = "read-shared-secret",
|
||||||
|
GET_SECRET_SYNCS = "get-secret-syncs",
|
||||||
|
GET_SECRET_SYNC = "get-secret-sync",
|
||||||
|
CREATE_SECRET_SYNC = "create-secret-sync",
|
||||||
|
UPDATE_SECRET_SYNC = "update-secret-sync",
|
||||||
|
DELETE_SECRET_SYNC = "delete-secret-sync",
|
||||||
|
SECRET_SYNC_SYNC_SECRETS = "secret-sync-sync-secrets",
|
||||||
|
SECRET_SYNC_IMPORT_SECRETS = "secret-sync-import-secrets",
|
||||||
|
SECRET_SYNC_REMOVE_SECRETS = "secret-sync-remove-secrets",
|
||||||
|
OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER = "oidc-group-membership-mapping-assign-user",
|
||||||
|
OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER = "oidc-group-membership-mapping-remove-user"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@ -252,6 +275,8 @@ interface ScimClientActorMetadata {}
|
|||||||
|
|
||||||
interface PlatformActorMetadata {}
|
interface PlatformActorMetadata {}
|
||||||
|
|
||||||
|
interface UnknownUserActorMetadata {}
|
||||||
|
|
||||||
export interface UserActor {
|
export interface UserActor {
|
||||||
type: ActorType.USER;
|
type: ActorType.USER;
|
||||||
metadata: UserActorMetadata;
|
metadata: UserActorMetadata;
|
||||||
@ -267,6 +292,11 @@ export interface PlatformActor {
|
|||||||
metadata: PlatformActorMetadata;
|
metadata: PlatformActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UnknownUserActor {
|
||||||
|
type: ActorType.UNKNOWN_USER;
|
||||||
|
metadata: UnknownUserActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IdentityActor {
|
export interface IdentityActor {
|
||||||
type: ActorType.IDENTITY;
|
type: ActorType.IDENTITY;
|
||||||
metadata: IdentityActorMetadata;
|
metadata: IdentityActorMetadata;
|
||||||
@ -288,6 +318,8 @@ interface GetSecretsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TSecretMetadata = { key: string; value: string }[];
|
||||||
|
|
||||||
interface GetSecretEvent {
|
interface GetSecretEvent {
|
||||||
type: EventType.GET_SECRET;
|
type: EventType.GET_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -296,6 +328,7 @@ interface GetSecretEvent {
|
|||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,6 +340,7 @@ interface CreateSecretEvent {
|
|||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +349,12 @@ interface CreateSecretBatchEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
|
secrets: Array<{
|
||||||
|
secretId: string;
|
||||||
|
secretKey: string;
|
||||||
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,6 +366,7 @@ interface UpdateSecretEvent {
|
|||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +375,7 @@ interface UpdateSecretBatchEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
|
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number; secretMetadata?: TSecretMetadata }>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -733,9 +773,9 @@ interface AddIdentityGcpAuthEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
type: string;
|
type: string;
|
||||||
allowedServiceAccounts: string;
|
allowedServiceAccounts?: string | null;
|
||||||
allowedProjects: string;
|
allowedProjects?: string | null;
|
||||||
allowedZones: string;
|
allowedZones?: string | null;
|
||||||
accessTokenTTL: number;
|
accessTokenTTL: number;
|
||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
@ -755,9 +795,9 @@ interface UpdateIdentityGcpAuthEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
allowedServiceAccounts?: string;
|
allowedServiceAccounts?: string | null;
|
||||||
allowedProjects?: string;
|
allowedProjects?: string | null;
|
||||||
allowedZones?: string;
|
allowedZones?: string | null;
|
||||||
accessTokenTTL?: number;
|
accessTokenTTL?: number;
|
||||||
accessTokenMaxTTL?: number;
|
accessTokenMaxTTL?: number;
|
||||||
accessTokenNumUsesLimit?: number;
|
accessTokenNumUsesLimit?: number;
|
||||||
@ -1808,6 +1848,13 @@ interface GetCmeksEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetCmekEvent {
|
||||||
|
type: EventType.GET_CMEK;
|
||||||
|
metadata: {
|
||||||
|
keyId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CmekEncryptEvent {
|
interface CmekEncryptEvent {
|
||||||
type: EventType.CMEK_ENCRYPT;
|
type: EventType.CMEK_ENCRYPT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -1883,6 +1930,15 @@ interface GetAppConnectionsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetAvailableAppConnectionsDetailsEvent {
|
||||||
|
type: EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS;
|
||||||
|
metadata: {
|
||||||
|
app?: AppConnection;
|
||||||
|
count: number;
|
||||||
|
connectionIds: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface GetAppConnectionEvent {
|
interface GetAppConnectionEvent {
|
||||||
type: EventType.GET_APP_CONNECTION;
|
type: EventType.GET_APP_CONNECTION;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -1907,6 +1963,127 @@ interface DeleteAppConnectionEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateSharedSecretEvent {
|
||||||
|
type: EventType.CREATE_SHARED_SECRET;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
accessType: string;
|
||||||
|
name?: string;
|
||||||
|
expiresAfterViews?: number;
|
||||||
|
usingPassword: boolean;
|
||||||
|
expiresAt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSharedSecretEvent {
|
||||||
|
type: EventType.DELETE_SHARED_SECRET;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReadSharedSecretEvent {
|
||||||
|
type: EventType.READ_SHARED_SECRET;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
accessType: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSecretSyncsEvent {
|
||||||
|
type: EventType.GET_SECRET_SYNCS;
|
||||||
|
metadata: {
|
||||||
|
destination?: SecretSync;
|
||||||
|
count: number;
|
||||||
|
syncIds: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSecretSyncEvent {
|
||||||
|
type: EventType.GET_SECRET_SYNC;
|
||||||
|
metadata: {
|
||||||
|
destination: SecretSync;
|
||||||
|
syncId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateSecretSyncEvent {
|
||||||
|
type: EventType.CREATE_SECRET_SYNC;
|
||||||
|
metadata: Omit<TCreateSecretSyncDTO, "projectId"> & { syncId: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSecretSyncEvent {
|
||||||
|
type: EventType.UPDATE_SECRET_SYNC;
|
||||||
|
metadata: TUpdateSecretSyncDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSecretSyncEvent {
|
||||||
|
type: EventType.DELETE_SECRET_SYNC;
|
||||||
|
metadata: TDeleteSecretSyncDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretSyncSyncSecretsEvent {
|
||||||
|
type: EventType.SECRET_SYNC_SYNC_SECRETS;
|
||||||
|
metadata: Pick<
|
||||||
|
TSecretSyncRaw,
|
||||||
|
"syncOptions" | "destinationConfig" | "destination" | "syncStatus" | "connectionId" | "folderId"
|
||||||
|
> & {
|
||||||
|
syncId: string;
|
||||||
|
syncMessage: string | null;
|
||||||
|
jobId: string;
|
||||||
|
jobRanAt: Date;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretSyncImportSecretsEvent {
|
||||||
|
type: EventType.SECRET_SYNC_IMPORT_SECRETS;
|
||||||
|
metadata: Pick<
|
||||||
|
TSecretSyncRaw,
|
||||||
|
"syncOptions" | "destinationConfig" | "destination" | "importStatus" | "connectionId" | "folderId"
|
||||||
|
> & {
|
||||||
|
syncId: string;
|
||||||
|
importMessage: string | null;
|
||||||
|
jobId: string;
|
||||||
|
jobRanAt: Date;
|
||||||
|
importBehavior: SecretSyncImportBehavior;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretSyncRemoveSecretsEvent {
|
||||||
|
type: EventType.SECRET_SYNC_REMOVE_SECRETS;
|
||||||
|
metadata: Pick<
|
||||||
|
TSecretSyncRaw,
|
||||||
|
"syncOptions" | "destinationConfig" | "destination" | "removeStatus" | "connectionId" | "folderId"
|
||||||
|
> & {
|
||||||
|
syncId: string;
|
||||||
|
removeMessage: string | null;
|
||||||
|
jobId: string;
|
||||||
|
jobRanAt: Date;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OidcGroupMembershipMappingAssignUserEvent {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER;
|
||||||
|
metadata: {
|
||||||
|
assignedToGroups: { id: string; name: string }[];
|
||||||
|
userId: string;
|
||||||
|
userEmail: string;
|
||||||
|
userGroupsClaim: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OidcGroupMembershipMappingRemoveUserEvent {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER;
|
||||||
|
metadata: {
|
||||||
|
removedFromGroups: { id: string; name: string }[];
|
||||||
|
userId: string;
|
||||||
|
userEmail: string;
|
||||||
|
userGroupsClaim: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@ -2068,6 +2245,7 @@ export type Event =
|
|||||||
| CreateCmekEvent
|
| CreateCmekEvent
|
||||||
| UpdateCmekEvent
|
| UpdateCmekEvent
|
||||||
| DeleteCmekEvent
|
| DeleteCmekEvent
|
||||||
|
| GetCmekEvent
|
||||||
| GetCmeksEvent
|
| GetCmeksEvent
|
||||||
| CmekEncryptEvent
|
| CmekEncryptEvent
|
||||||
| CmekDecryptEvent
|
| CmekDecryptEvent
|
||||||
@ -2080,7 +2258,21 @@ export type Event =
|
|||||||
| DeleteProjectTemplateEvent
|
| DeleteProjectTemplateEvent
|
||||||
| ApplyProjectTemplateEvent
|
| ApplyProjectTemplateEvent
|
||||||
| GetAppConnectionsEvent
|
| GetAppConnectionsEvent
|
||||||
|
| GetAvailableAppConnectionsDetailsEvent
|
||||||
| GetAppConnectionEvent
|
| GetAppConnectionEvent
|
||||||
| CreateAppConnectionEvent
|
| CreateAppConnectionEvent
|
||||||
| UpdateAppConnectionEvent
|
| UpdateAppConnectionEvent
|
||||||
| DeleteAppConnectionEvent;
|
| DeleteAppConnectionEvent
|
||||||
|
| CreateSharedSecretEvent
|
||||||
|
| DeleteSharedSecretEvent
|
||||||
|
| ReadSharedSecretEvent
|
||||||
|
| GetSecretSyncsEvent
|
||||||
|
| GetSecretSyncEvent
|
||||||
|
| CreateSecretSyncEvent
|
||||||
|
| UpdateSecretSyncEvent
|
||||||
|
| DeleteSecretSyncEvent
|
||||||
|
| SecretSyncSyncSecretsEvent
|
||||||
|
| SecretSyncImportSecretsEvent
|
||||||
|
| SecretSyncRemoveSecretsEvent
|
||||||
|
| OidcGroupMembershipMappingAssignUserEvent
|
||||||
|
| OidcGroupMembershipMappingRemoveUserEvent;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
@ -66,13 +67,14 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
@ -67,14 +67,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -147,14 +147,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -227,14 +227,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -297,13 +297,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -339,13 +340,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
@ -73,14 +73,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -145,14 +145,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -229,14 +229,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -290,13 +290,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -340,13 +341,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
isInternal
|
isInternal
|
||||||
}: TListDynamicSecretsMultiEnvDTO) => {
|
}: TListDynamicSecretsMultiEnvDTO) => {
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
// verify user has access to each env in request
|
// verify user has access to each env in request
|
||||||
environmentSlugs.forEach((environmentSlug) =>
|
environmentSlugs.forEach((environmentSlug) =>
|
||||||
@ -383,13 +385,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
search,
|
search,
|
||||||
projectId
|
projectId
|
||||||
}: TGetDynamicSecretsCountDTO) => {
|
}: TGetDynamicSecretsCountDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -431,13 +434,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@ -462,13 +466,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO,
|
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO,
|
||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => {
|
) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor.type,
|
actor: actor.type,
|
||||||
actor.id,
|
actorId: actor.id,
|
||||||
projectId,
|
projectId,
|
||||||
actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actor.orgId
|
actorOrgId: actor.orgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
@ -507,13 +512,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
...params
|
...params
|
||||||
}: TListDynamicSecretsMultiEnvDTO) => {
|
}: TListDynamicSecretsMultiEnvDTO) => {
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
// verify user has access to each env in request
|
// verify user has access to each env in request
|
||||||
environmentSlugs.forEach((environmentSlug) =>
|
environmentSlugs.forEach((environmentSlug) =>
|
||||||
|
@ -34,6 +34,8 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
|
|
||||||
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
|
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
|
||||||
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
|
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
|
||||||
|
const isMsSQLClient = providerInputs.client === SqlProviders.MsSQL;
|
||||||
|
|
||||||
const db = knex({
|
const db = knex({
|
||||||
client: providerInputs.client,
|
client: providerInputs.client,
|
||||||
connection: {
|
connection: {
|
||||||
@ -43,7 +45,16 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
user: providerInputs.username,
|
user: providerInputs.username,
|
||||||
password: providerInputs.password,
|
password: providerInputs.password,
|
||||||
ssl,
|
ssl,
|
||||||
pool: { min: 0, max: 1 }
|
pool: { min: 0, max: 1 },
|
||||||
|
// @ts-expect-error this is because of knexjs type signature issue. This is directly passed to driver
|
||||||
|
// https://github.com/knex/knex/blob/b6507a7129d2b9fafebf5f831494431e64c6a8a0/lib/dialects/mssql/index.js#L66
|
||||||
|
// https://github.com/tediousjs/tedious/blob/ebb023ed90969a7ec0e4b036533ad52739d921f7/test/config.ci.ts#L19
|
||||||
|
options: isMsSQLClient
|
||||||
|
? {
|
||||||
|
trustServerCertificate: !providerInputs.ca,
|
||||||
|
cryptoCredentialsDetails: providerInputs.ca ? { ca: providerInputs.ca } : {}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
},
|
},
|
||||||
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
|
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import { KMSServiceException } from "@aws-sdk/client-kms";
|
||||||
|
import { STSServiceException } from "@aws-sdk/client-sts";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
@ -71,7 +73,16 @@ export const externalKmsServiceFactory = ({
|
|||||||
switch (provider.type) {
|
switch (provider.type) {
|
||||||
case KmsProviders.Aws:
|
case KmsProviders.Aws:
|
||||||
{
|
{
|
||||||
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
|
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs }).catch((error) => {
|
||||||
|
if (error instanceof STSServiceException || error instanceof KMSServiceException) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: error.message ? `AWS error: ${error.message}` : ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
// if missing kms key this generate a new kms key id and returns new provider input
|
// if missing kms key this generate a new kms key id and returns new provider input
|
||||||
const newProviderInput = await externalKms.generateInputKmsKey();
|
const newProviderInput = await externalKms.generateInputKmsKey();
|
||||||
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
||||||
|
@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
||||||
|
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@ -32,7 +33,7 @@ type TGroupServiceFactoryDep = {
|
|||||||
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
|
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById"
|
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
||||||
@ -45,6 +46,7 @@ type TGroupServiceFactoryDep = {
|
|||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
||||||
@ -59,7 +61,8 @@ export const groupServiceFactory = ({
|
|||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService,
|
||||||
|
oidcConfigDAL
|
||||||
}: TGroupServiceFactoryDep) => {
|
}: TGroupServiceFactoryDep) => {
|
||||||
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
@ -88,12 +91,26 @@ export const groupServiceFactory = ({
|
|||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
||||||
|
|
||||||
const group = await groupDAL.create({
|
const group = await groupDAL.transaction(async (tx) => {
|
||||||
name,
|
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
|
||||||
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
if (existingGroup) {
|
||||||
orgId: actorOrgId,
|
throw new BadRequestError({
|
||||||
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
message: `Failed to create group with name '${name}'. Group with the same name already exists`
|
||||||
roleId: customRole?.id
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGroup = await groupDAL.create(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
||||||
|
orgId: actorOrgId,
|
||||||
|
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return newGroup;
|
||||||
});
|
});
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
@ -145,21 +162,36 @@ export const groupServiceFactory = ({
|
|||||||
if (isCustomRole) customRole = customOrgRole;
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [updatedGroup] = await groupDAL.update(
|
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||||
{
|
if (name) {
|
||||||
id: group.id
|
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
|
||||||
},
|
|
||||||
{
|
if (existingGroup && existingGroup.id !== id) {
|
||||||
name,
|
throw new BadRequestError({
|
||||||
slug: slug ? slugify(slug) : undefined,
|
message: `Failed to update group with name '${name}'. Group with the same name already exists`
|
||||||
...(role
|
});
|
||||||
? {
|
}
|
||||||
role: customRole ? OrgMembershipRole.Custom : role,
|
|
||||||
roleId: customRole?.id ?? null
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
const [updated] = await groupDAL.update(
|
||||||
|
{
|
||||||
|
id: group.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
slug: slug ? slugify(slug) : undefined,
|
||||||
|
...(role
|
||||||
|
? {
|
||||||
|
role: customRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id ?? null
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
|
||||||
return updatedGroup;
|
return updatedGroup;
|
||||||
};
|
};
|
||||||
@ -282,6 +314,18 @@ export const groupServiceFactory = ({
|
|||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const oidcConfig = await oidcConfigDAL.findOne({
|
||||||
|
orgId: group.orgId,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oidcConfig?.manageGroupMemberships) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Cannot add user to group: OIDC group membership mapping is enabled - user must be assigned to this group in your OIDC provider."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
@ -337,6 +381,18 @@ export const groupServiceFactory = ({
|
|||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const oidcConfig = await oidcConfigDAL.findOne({
|
||||||
|
orgId: group.orgId,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oidcConfig?.manageGroupMemberships) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Cannot remove user from group: OIDC group membership mapping is enabled - user must be removed from this group in your OIDC provider."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
|
@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
|
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
|
||||||
@ -55,24 +55,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityId,
|
actorId: identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@ -135,24 +137,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
);
|
);
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@ -215,24 +219,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
);
|
);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
@ -260,13 +266,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
@ -294,13 +301,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
@ -329,13 +337,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
|
@ -2,6 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"
|
|||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
@ -62,25 +63,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityId,
|
actorId: identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@ -143,26 +146,28 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@ -242,25 +247,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
||||||
@ -299,13 +306,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
@ -341,13 +349,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
|
@ -476,14 +476,14 @@ export const ldapConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
||||||
|
@ -24,6 +24,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
rbac: false,
|
rbac: false,
|
||||||
customRateLimits: false,
|
customRateLimits: false,
|
||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
|
secretAccessInsights: false,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
auditLogStreams: false,
|
auditLogStreams: false,
|
||||||
@ -49,8 +50,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
},
|
},
|
||||||
pkiEst: false,
|
pkiEst: false,
|
||||||
enforceMfa: false,
|
enforceMfa: false,
|
||||||
projectTemplates: false,
|
projectTemplates: false
|
||||||
appConnections: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
@ -246,8 +246,7 @@ export const licenseServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
|
const getOrgPlan = async ({ orgId, actor, actorId, actorOrgId, actorAuthMethod, projectId }: TOrgPlanDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Billing);
|
|
||||||
const plan = await getPlan(orgId, projectId);
|
const plan = await getPlan(orgId, projectId);
|
||||||
return plan;
|
return plan;
|
||||||
};
|
};
|
||||||
|
@ -48,6 +48,7 @@ export type TFeatureSet = {
|
|||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
hsm: false;
|
hsm: false;
|
||||||
oidcSSO: false;
|
oidcSSO: false;
|
||||||
|
secretAccessInsights: false;
|
||||||
scim: false;
|
scim: false;
|
||||||
ldap: false;
|
ldap: false;
|
||||||
groups: false;
|
groups: false;
|
||||||
@ -67,7 +68,6 @@ export type TFeatureSet = {
|
|||||||
pkiEst: boolean;
|
pkiEst: boolean;
|
||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
projectTemplates: false;
|
projectTemplates: false;
|
||||||
appConnections: false; // TODO: remove once live
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
@ -5,6 +5,11 @@ import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet }
|
|||||||
|
|
||||||
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
||||||
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
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";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@ -18,13 +23,18 @@ import {
|
|||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-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 { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
||||||
@ -45,7 +55,14 @@ import {
|
|||||||
type TOidcConfigServiceFactoryDep = {
|
type TOidcConfigServiceFactoryDep = {
|
||||||
userDAL: Pick<
|
userDAL: Pick<
|
||||||
TUserDALFactory,
|
TUserDALFactory,
|
||||||
"create" | "findOne" | "transaction" | "updateById" | "findById" | "findUserEncKeyByUserId"
|
| "create"
|
||||||
|
| "findOne"
|
||||||
|
| "updateById"
|
||||||
|
| "findById"
|
||||||
|
| "findUserEncKeyByUserId"
|
||||||
|
| "findUserEncKeyByUserIdsBatch"
|
||||||
|
| "find"
|
||||||
|
| "transaction"
|
||||||
>;
|
>;
|
||||||
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
||||||
orgDAL: Pick<
|
orgDAL: Pick<
|
||||||
@ -57,8 +74,23 @@ type TOidcConfigServiceFactoryDep = {
|
|||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
|
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getUserOrgPermission">;
|
||||||
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "update" | "create">;
|
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "update" | "create">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "findByOrgId">;
|
||||||
|
userGroupMembershipDAL: Pick<
|
||||||
|
TUserGroupMembershipDALFactory,
|
||||||
|
| "find"
|
||||||
|
| "transaction"
|
||||||
|
| "insertMany"
|
||||||
|
| "findGroupMembershipsByUserIdInOrg"
|
||||||
|
| "delete"
|
||||||
|
| "filterProjectsByUserMembership"
|
||||||
|
>;
|
||||||
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
||||||
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFactory>;
|
export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFactory>;
|
||||||
@ -73,7 +105,14 @@ export const oidcConfigServiceFactory = ({
|
|||||||
tokenService,
|
tokenService,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
oidcConfigDAL
|
oidcConfigDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
|
auditLogService
|
||||||
}: TOidcConfigServiceFactoryDep) => {
|
}: TOidcConfigServiceFactoryDep) => {
|
||||||
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||||
@ -156,11 +195,21 @@ export const oidcConfigServiceFactory = ({
|
|||||||
isActive: oidcCfg.isActive,
|
isActive: oidcCfg.isActive,
|
||||||
allowedEmailDomains: oidcCfg.allowedEmailDomains,
|
allowedEmailDomains: oidcCfg.allowedEmailDomains,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret
|
clientSecret,
|
||||||
|
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const oidcLogin = async ({ externalId, email, firstName, lastName, orgId, callbackPort }: TOidcLoginDTO) => {
|
const oidcLogin = async ({
|
||||||
|
externalId,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
orgId,
|
||||||
|
callbackPort,
|
||||||
|
groups = [],
|
||||||
|
manageGroupMemberships
|
||||||
|
}: TOidcLoginDTO) => {
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
||||||
@ -315,6 +364,83 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manageGroupMemberships) {
|
||||||
|
const userGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(user.id, orgId);
|
||||||
|
const orgGroups = await groupDAL.findByOrgId(orgId);
|
||||||
|
|
||||||
|
const userGroupsNames = userGroups.map((membership) => membership.groupName);
|
||||||
|
const missingGroupsMemberships = groups.filter((groupName) => !userGroupsNames.includes(groupName));
|
||||||
|
const groupsToAddUserTo = orgGroups.filter((group) => missingGroupsMemberships.includes(group.name));
|
||||||
|
|
||||||
|
for await (const group of groupsToAddUserTo) {
|
||||||
|
await addUsersToGroupByUserIds({
|
||||||
|
userIds: [user.id],
|
||||||
|
group,
|
||||||
|
userDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
orgDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectBotDAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToAddUserTo.length) {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
actor: {
|
||||||
|
type: ActorType.PLATFORM,
|
||||||
|
metadata: {}
|
||||||
|
},
|
||||||
|
orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER,
|
||||||
|
metadata: {
|
||||||
|
userId: user.id,
|
||||||
|
userEmail: user.email ?? user.username,
|
||||||
|
assignedToGroups: groupsToAddUserTo.map(({ id, name }) => ({ id, name })),
|
||||||
|
userGroupsClaim: groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const membershipsToRemove = userGroups
|
||||||
|
.filter((membership) => !groups.includes(membership.groupName))
|
||||||
|
.map((membership) => membership.groupId);
|
||||||
|
const groupsToRemoveUserFrom = orgGroups.filter((group) => membershipsToRemove.includes(group.id));
|
||||||
|
|
||||||
|
for await (const group of groupsToRemoveUserFrom) {
|
||||||
|
await removeUsersFromGroupByUserIds({
|
||||||
|
userIds: [user.id],
|
||||||
|
group,
|
||||||
|
userDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
projectKeyDAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToRemoveUserFrom.length) {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
actor: {
|
||||||
|
type: ActorType.PLATFORM,
|
||||||
|
metadata: {}
|
||||||
|
},
|
||||||
|
orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER,
|
||||||
|
metadata: {
|
||||||
|
userId: user.id,
|
||||||
|
userEmail: user.email ?? user.username,
|
||||||
|
removedFromGroups: groupsToRemoveUserFrom.map(({ id, name }) => ({ id, name })),
|
||||||
|
userGroupsClaim: groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
|
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
|
||||||
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
|
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
|
||||||
@ -385,7 +511,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
tokenEndpoint,
|
tokenEndpoint,
|
||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret
|
clientSecret,
|
||||||
|
manageGroupMemberships
|
||||||
}: TUpdateOidcCfgDTO) => {
|
}: TUpdateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({
|
||||||
slug: orgSlug
|
slug: orgSlug
|
||||||
@ -448,7 +575,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
jwksUri,
|
jwksUri,
|
||||||
isActive,
|
isActive,
|
||||||
lastUsed: null
|
lastUsed: null,
|
||||||
|
manageGroupMemberships
|
||||||
};
|
};
|
||||||
|
|
||||||
if (clientId !== undefined) {
|
if (clientId !== undefined) {
|
||||||
@ -491,7 +619,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
tokenEndpoint,
|
tokenEndpoint,
|
||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret
|
clientSecret,
|
||||||
|
manageGroupMemberships
|
||||||
}: TCreateOidcCfgDTO) => {
|
}: TCreateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({
|
||||||
slug: orgSlug
|
slug: orgSlug
|
||||||
@ -589,7 +718,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
clientIdTag,
|
clientIdTag,
|
||||||
encryptedClientSecret,
|
encryptedClientSecret,
|
||||||
clientSecretIV,
|
clientSecretIV,
|
||||||
clientSecretTag
|
clientSecretTag,
|
||||||
|
manageGroupMemberships
|
||||||
});
|
});
|
||||||
|
|
||||||
return oidcCfg;
|
return oidcCfg;
|
||||||
@ -683,7 +813,9 @@ export const oidcConfigServiceFactory = ({
|
|||||||
firstName: claims.given_name ?? "",
|
firstName: claims.given_name ?? "",
|
||||||
lastName: claims.family_name ?? "",
|
lastName: claims.family_name ?? "",
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
callbackPort
|
groups: claims.groups as string[] | undefined,
|
||||||
|
callbackPort,
|
||||||
|
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
||||||
})
|
})
|
||||||
.then(({ isUserCompleted, providerAuthToken }) => {
|
.then(({ isUserCompleted, providerAuthToken }) => {
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
@ -697,5 +829,16 @@ export const oidcConfigServiceFactory = ({
|
|||||||
return strategy;
|
return strategy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { oidcLogin, getOrgAuthStrategy, getOidc, updateOidcCfg, createOidcCfg };
|
const isOidcManageGroupMembershipsEnabled = async (orgId: string, actor: OrgServiceActor) => {
|
||||||
|
await permissionService.getUserOrgPermission(actor.id, orgId, actor.authMethod, actor.orgId);
|
||||||
|
|
||||||
|
const oidcConfig = await oidcConfigDAL.findOne({
|
||||||
|
orgId,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(oidcConfig?.manageGroupMemberships);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { oidcLogin, getOrgAuthStrategy, getOidc, updateOidcCfg, createOidcCfg, isOidcManageGroupMembershipsEnabled };
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,8 @@ export type TOidcLoginDTO = {
|
|||||||
lastName?: string;
|
lastName?: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
callbackPort?: string;
|
callbackPort?: string;
|
||||||
|
groups?: string[];
|
||||||
|
manageGroupMemberships?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetOidcCfgDTO =
|
export type TGetOidcCfgDTO =
|
||||||
@ -37,6 +39,7 @@ export type TCreateOidcCfgDTO = {
|
|||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
orgSlug: string;
|
||||||
|
manageGroupMemberships: boolean;
|
||||||
} & TGenericPermission;
|
} & TGenericPermission;
|
||||||
|
|
||||||
export type TUpdateOidcCfgDTO = Partial<{
|
export type TUpdateOidcCfgDTO = Partial<{
|
||||||
@ -52,5 +55,6 @@ export type TUpdateOidcCfgDTO = Partial<{
|
|||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
orgSlug: string;
|
||||||
|
manageGroupMemberships: boolean;
|
||||||
}> &
|
}> &
|
||||||
TGenericPermission;
|
TGenericPermission;
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CASL_ACTION_SCHEMA_ENUM,
|
||||||
|
CASL_ACTION_SCHEMA_NATIVE_ENUM
|
||||||
|
} from "@app/ee/services/permission/permission-schemas";
|
||||||
|
import { PermissionConditionSchema } from "@app/ee/services/permission/permission-types";
|
||||||
|
import { PermissionConditionOperators } from "@app/lib/casl";
|
||||||
|
|
||||||
export enum OrgPermissionActions {
|
export enum OrgPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
@ -7,6 +15,14 @@ export enum OrgPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionAppConnectionActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
Connect = "connect"
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
AccessAllProjects = "access-all-projects"
|
AccessAllProjects = "access-all-projects"
|
||||||
}
|
}
|
||||||
@ -31,6 +47,10 @@ export enum OrgPermissionSubjects {
|
|||||||
AppConnections = "app-connections"
|
AppConnections = "app-connections"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AppConnectionSubjectFields = {
|
||||||
|
connectionId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
|
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Role]
|
| [OrgPermissionActions, OrgPermissionSubjects.Role]
|
||||||
@ -47,9 +67,109 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AppConnections]
|
| [
|
||||||
|
OrgPermissionAppConnectionActions,
|
||||||
|
(
|
||||||
|
| OrgPermissionSubjects.AppConnections
|
||||||
|
| (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
||||||
|
|
||||||
|
const AppConnectionConditionSchema = z
|
||||||
|
.object({
|
||||||
|
connectionId: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
|
export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Workspace).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_ENUM([OrgPermissionActions.Create]).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Role).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Member).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Settings).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.IncidentAccount).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Sso).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Scim).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Ldap).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Groups).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.SecretScanning).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Billing).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Identity).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Kms).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.AuditLogs).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.ProjectTemplates).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.AppConnections).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionAppConnectionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: AppConnectionConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.AdminConsole).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionAdminConsoleAction).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
// ws permissions
|
// ws permissions
|
||||||
@ -125,10 +245,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Read, OrgPermissionSubjects.AppConnections);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Create, OrgPermissionSubjects.AppConnections);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Edit, OrgPermissionSubjects.AppConnections);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Delete, OrgPermissionSubjects.AppConnections);
|
||||||
|
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
|
||||||
|
|
||||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||||
|
|
||||||
@ -160,7 +281,7 @@ const buildMemberPermission = () => {
|
|||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
@ -125,6 +125,404 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProjectGroupPermissions = async (projectId: string) => {
|
||||||
|
try {
|
||||||
|
const docs = await db
|
||||||
|
.replicaNode()(TableName.GroupProjectMembership)
|
||||||
|
.join(TableName.Groups, `${TableName.Groups}.id`, `${TableName.GroupProjectMembership}.groupId`)
|
||||||
|
.join(
|
||||||
|
TableName.GroupProjectMembershipRole,
|
||||||
|
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.GroupProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.leftJoin<TProjectRoles>(
|
||||||
|
{ groupCustomRoles: TableName.ProjectRoles },
|
||||||
|
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||||
|
`groupCustomRoles.id`
|
||||||
|
)
|
||||||
|
.where(`${TableName.GroupProjectMembership}.projectId`, "=", projectId)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
||||||
|
db.ref("id").withSchema(TableName.Groups).as("groupId"),
|
||||||
|
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
||||||
|
db.ref("slug").withSchema("groupCustomRoles").as("groupProjectMembershipRoleCustomRoleSlug"),
|
||||||
|
db.ref("permissions").withSchema("groupCustomRoles").as("groupProjectMembershipRolePermission"),
|
||||||
|
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRoleId"),
|
||||||
|
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("groupProjectMembershipRole"),
|
||||||
|
db
|
||||||
|
.ref("customRoleId")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("groupProjectMembershipRoleCustomRoleId"),
|
||||||
|
db
|
||||||
|
.ref("isTemporary")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("groupProjectMembershipRoleIsTemporary"),
|
||||||
|
db
|
||||||
|
.ref("temporaryMode")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("groupProjectMembershipRoleTemporaryMode"),
|
||||||
|
db
|
||||||
|
.ref("temporaryRange")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("groupProjectMembershipRoleTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("groupProjectMembershipRoleTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("groupProjectMembershipRoleTemporaryAccessEndTime")
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupPermissions = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "groupId",
|
||||||
|
parentMapper: ({ groupId, groupName, membershipId }) => ({
|
||||||
|
groupId,
|
||||||
|
username: groupName,
|
||||||
|
id: membershipId
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "groupProjectMembershipRoleId",
|
||||||
|
label: "groupRoles" as const,
|
||||||
|
mapper: ({
|
||||||
|
groupProjectMembershipRoleId,
|
||||||
|
groupProjectMembershipRole,
|
||||||
|
groupProjectMembershipRolePermission,
|
||||||
|
groupProjectMembershipRoleCustomRoleSlug,
|
||||||
|
groupProjectMembershipRoleIsTemporary,
|
||||||
|
groupProjectMembershipRoleTemporaryMode,
|
||||||
|
groupProjectMembershipRoleTemporaryAccessEndTime,
|
||||||
|
groupProjectMembershipRoleTemporaryAccessStartTime,
|
||||||
|
groupProjectMembershipRoleTemporaryRange
|
||||||
|
}) => ({
|
||||||
|
id: groupProjectMembershipRoleId,
|
||||||
|
role: groupProjectMembershipRole,
|
||||||
|
customRoleSlug: groupProjectMembershipRoleCustomRoleSlug,
|
||||||
|
permissions: groupProjectMembershipRolePermission,
|
||||||
|
temporaryRange: groupProjectMembershipRoleTemporaryRange,
|
||||||
|
temporaryMode: groupProjectMembershipRoleTemporaryMode,
|
||||||
|
temporaryAccessStartTime: groupProjectMembershipRoleTemporaryAccessStartTime,
|
||||||
|
temporaryAccessEndTime: groupProjectMembershipRoleTemporaryAccessEndTime,
|
||||||
|
isTemporary: groupProjectMembershipRoleIsTemporary
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return groupPermissions
|
||||||
|
.map((groupPermission) => {
|
||||||
|
if (!groupPermission) return undefined;
|
||||||
|
|
||||||
|
const activeGroupRoles =
|
||||||
|
groupPermission?.groupRoles?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...groupPermission,
|
||||||
|
roles: activeGroupRoles
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item): item is NonNullable<typeof item> => Boolean(item));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "GetProjectGroupPermissions" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProjectUserPermissions = async (projectId: string) => {
|
||||||
|
try {
|
||||||
|
const docs = await db
|
||||||
|
.replicaNode()(TableName.Users)
|
||||||
|
.where("isGhost", "=", false)
|
||||||
|
.leftJoin(TableName.GroupProjectMembership, (queryBuilder) => {
|
||||||
|
void queryBuilder.on(`${TableName.GroupProjectMembership}.projectId`, db.raw("?", [projectId]));
|
||||||
|
})
|
||||||
|
.leftJoin(
|
||||||
|
TableName.GroupProjectMembershipRole,
|
||||||
|
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.GroupProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.leftJoin<TProjectRoles>(
|
||||||
|
{ groupCustomRoles: TableName.ProjectRoles },
|
||||||
|
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||||
|
`groupCustomRoles.id`
|
||||||
|
)
|
||||||
|
.join(TableName.ProjectMembership, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.ProjectMembership}.projectId`, db.raw("?", [projectId]))
|
||||||
|
.andOn(`${TableName.ProjectMembership}.userId`, `${TableName.Users}.id`);
|
||||||
|
})
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectUserMembershipRole,
|
||||||
|
`${TableName.ProjectUserMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.ProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectRoles,
|
||||||
|
`${TableName.ProjectUserMembershipRole}.customRoleId`,
|
||||||
|
`${TableName.ProjectRoles}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.ProjectUserAdditionalPrivilege, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.ProjectUserAdditionalPrivilege}.projectId`, db.raw("?", [projectId]))
|
||||||
|
.andOn(`${TableName.ProjectUserAdditionalPrivilege}.userId`, `${TableName.Users}.id`);
|
||||||
|
})
|
||||||
|
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
|
||||||
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
|
||||||
|
.andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
|
})
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||||
|
db.ref("username").withSchema(TableName.Users).as("username"),
|
||||||
|
// groups specific
|
||||||
|
db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipUpdatedAt"),
|
||||||
|
db.ref("slug").withSchema("groupCustomRoles").as("userGroupProjectMembershipRoleCustomRoleSlug"),
|
||||||
|
db.ref("permissions").withSchema("groupCustomRoles").as("userGroupProjectMembershipRolePermission"),
|
||||||
|
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRoleId"),
|
||||||
|
db.ref("role").withSchema(TableName.GroupProjectMembershipRole).as("userGroupProjectMembershipRole"),
|
||||||
|
db
|
||||||
|
.ref("customRoleId")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("userGroupProjectMembershipRoleCustomRoleId"),
|
||||||
|
db
|
||||||
|
.ref("isTemporary")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("userGroupProjectMembershipRoleIsTemporary"),
|
||||||
|
db
|
||||||
|
.ref("temporaryMode")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("userGroupProjectMembershipRoleTemporaryMode"),
|
||||||
|
db
|
||||||
|
.ref("temporaryRange")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("userGroupProjectMembershipRoleTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("userGroupProjectMembershipRoleTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.GroupProjectMembershipRole)
|
||||||
|
.as("userGroupProjectMembershipRoleTemporaryAccessEndTime"),
|
||||||
|
// user specific
|
||||||
|
db.ref("id").withSchema(TableName.ProjectMembership).as("membershipId"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.ProjectMembership).as("membershipCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.ProjectMembership).as("membershipUpdatedAt"),
|
||||||
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("userProjectMembershipRoleCustomRoleSlug"),
|
||||||
|
db.ref("permissions").withSchema(TableName.ProjectRoles).as("userProjectCustomRolePermission"),
|
||||||
|
db.ref("id").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRoleId"),
|
||||||
|
db.ref("role").withSchema(TableName.ProjectUserMembershipRole).as("userProjectMembershipRole"),
|
||||||
|
db
|
||||||
|
.ref("temporaryMode")
|
||||||
|
.withSchema(TableName.ProjectUserMembershipRole)
|
||||||
|
.as("userProjectMembershipRoleTemporaryMode"),
|
||||||
|
db
|
||||||
|
.ref("isTemporary")
|
||||||
|
.withSchema(TableName.ProjectUserMembershipRole)
|
||||||
|
.as("userProjectMembershipRoleIsTemporary"),
|
||||||
|
db
|
||||||
|
.ref("temporaryRange")
|
||||||
|
.withSchema(TableName.ProjectUserMembershipRole)
|
||||||
|
.as("userProjectMembershipRoleTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.ProjectUserMembershipRole)
|
||||||
|
.as("userProjectMembershipRoleTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.ProjectUserMembershipRole)
|
||||||
|
.as("userProjectMembershipRoleTemporaryAccessEndTime"),
|
||||||
|
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesId"),
|
||||||
|
db
|
||||||
|
.ref("permissions")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userAdditionalPrivilegesPermissions"),
|
||||||
|
db
|
||||||
|
.ref("temporaryMode")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userAdditionalPrivilegesTemporaryMode"),
|
||||||
|
db
|
||||||
|
.ref("isTemporary")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userAdditionalPrivilegesIsTemporary"),
|
||||||
|
db
|
||||||
|
.ref("temporaryRange")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userAdditionalPrivilegesTemporaryRange"),
|
||||||
|
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userAdditionalPrivilegesUserId"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userAdditionalPrivilegesTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
|
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
|
||||||
|
// general
|
||||||
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
|
||||||
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
|
db.ref("orgId").withSchema(TableName.Project),
|
||||||
|
db.ref("type").withSchema(TableName.Project).as("projectType"),
|
||||||
|
db.ref("id").withSchema(TableName.Project).as("projectId")
|
||||||
|
);
|
||||||
|
|
||||||
|
const userPermissions = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "userId",
|
||||||
|
parentMapper: ({
|
||||||
|
orgId,
|
||||||
|
username,
|
||||||
|
orgAuthEnforced,
|
||||||
|
membershipId,
|
||||||
|
groupMembershipId,
|
||||||
|
membershipCreatedAt,
|
||||||
|
groupMembershipCreatedAt,
|
||||||
|
groupMembershipUpdatedAt,
|
||||||
|
membershipUpdatedAt,
|
||||||
|
projectType,
|
||||||
|
userId
|
||||||
|
}) => ({
|
||||||
|
orgId,
|
||||||
|
orgAuthEnforced,
|
||||||
|
userId,
|
||||||
|
projectId,
|
||||||
|
username,
|
||||||
|
projectType,
|
||||||
|
id: membershipId || groupMembershipId,
|
||||||
|
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
|
||||||
|
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "userGroupProjectMembershipRoleId",
|
||||||
|
label: "userGroupRoles" as const,
|
||||||
|
mapper: ({
|
||||||
|
userGroupProjectMembershipRoleId,
|
||||||
|
userGroupProjectMembershipRole,
|
||||||
|
userGroupProjectMembershipRolePermission,
|
||||||
|
userGroupProjectMembershipRoleCustomRoleSlug,
|
||||||
|
userGroupProjectMembershipRoleIsTemporary,
|
||||||
|
userGroupProjectMembershipRoleTemporaryMode,
|
||||||
|
userGroupProjectMembershipRoleTemporaryAccessEndTime,
|
||||||
|
userGroupProjectMembershipRoleTemporaryAccessStartTime,
|
||||||
|
userGroupProjectMembershipRoleTemporaryRange
|
||||||
|
}) => ({
|
||||||
|
id: userGroupProjectMembershipRoleId,
|
||||||
|
role: userGroupProjectMembershipRole,
|
||||||
|
customRoleSlug: userGroupProjectMembershipRoleCustomRoleSlug,
|
||||||
|
permissions: userGroupProjectMembershipRolePermission,
|
||||||
|
temporaryRange: userGroupProjectMembershipRoleTemporaryRange,
|
||||||
|
temporaryMode: userGroupProjectMembershipRoleTemporaryMode,
|
||||||
|
temporaryAccessStartTime: userGroupProjectMembershipRoleTemporaryAccessStartTime,
|
||||||
|
temporaryAccessEndTime: userGroupProjectMembershipRoleTemporaryAccessEndTime,
|
||||||
|
isTemporary: userGroupProjectMembershipRoleIsTemporary
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "userProjectMembershipRoleId",
|
||||||
|
label: "projectMembershipRoles" as const,
|
||||||
|
mapper: ({
|
||||||
|
userProjectMembershipRoleId,
|
||||||
|
userProjectMembershipRole,
|
||||||
|
userProjectCustomRolePermission,
|
||||||
|
userProjectMembershipRoleIsTemporary,
|
||||||
|
userProjectMembershipRoleTemporaryMode,
|
||||||
|
userProjectMembershipRoleTemporaryRange,
|
||||||
|
userProjectMembershipRoleTemporaryAccessEndTime,
|
||||||
|
userProjectMembershipRoleTemporaryAccessStartTime,
|
||||||
|
userProjectMembershipRoleCustomRoleSlug
|
||||||
|
}) => ({
|
||||||
|
id: userProjectMembershipRoleId,
|
||||||
|
role: userProjectMembershipRole,
|
||||||
|
customRoleSlug: userProjectMembershipRoleCustomRoleSlug,
|
||||||
|
permissions: userProjectCustomRolePermission,
|
||||||
|
temporaryRange: userProjectMembershipRoleTemporaryRange,
|
||||||
|
temporaryMode: userProjectMembershipRoleTemporaryMode,
|
||||||
|
temporaryAccessStartTime: userProjectMembershipRoleTemporaryAccessStartTime,
|
||||||
|
temporaryAccessEndTime: userProjectMembershipRoleTemporaryAccessEndTime,
|
||||||
|
isTemporary: userProjectMembershipRoleIsTemporary
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "userAdditionalPrivilegesId",
|
||||||
|
label: "additionalPrivileges" as const,
|
||||||
|
mapper: ({
|
||||||
|
userAdditionalPrivilegesId,
|
||||||
|
userAdditionalPrivilegesPermissions,
|
||||||
|
userAdditionalPrivilegesIsTemporary,
|
||||||
|
userAdditionalPrivilegesTemporaryMode,
|
||||||
|
userAdditionalPrivilegesTemporaryRange,
|
||||||
|
userAdditionalPrivilegesTemporaryAccessEndTime,
|
||||||
|
userAdditionalPrivilegesTemporaryAccessStartTime
|
||||||
|
}) => ({
|
||||||
|
id: userAdditionalPrivilegesId,
|
||||||
|
permissions: userAdditionalPrivilegesPermissions,
|
||||||
|
temporaryRange: userAdditionalPrivilegesTemporaryRange,
|
||||||
|
temporaryMode: userAdditionalPrivilegesTemporaryMode,
|
||||||
|
temporaryAccessStartTime: userAdditionalPrivilegesTemporaryAccessStartTime,
|
||||||
|
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
|
||||||
|
isTemporary: userAdditionalPrivilegesIsTemporary
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return userPermissions
|
||||||
|
.map((userPermission) => {
|
||||||
|
if (!userPermission) return undefined;
|
||||||
|
if (!userPermission?.userGroupRoles?.[0] && !userPermission?.projectMembershipRoles?.[0]) return undefined;
|
||||||
|
|
||||||
|
// when introducting cron mode change it here
|
||||||
|
const activeRoles =
|
||||||
|
userPermission?.projectMembershipRoles?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
const activeGroupRoles =
|
||||||
|
userPermission?.userGroupRoles?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
const activeAdditionalPrivileges =
|
||||||
|
userPermission?.additionalPrivileges?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...userPermission,
|
||||||
|
roles: [...activeRoles, ...activeGroupRoles],
|
||||||
|
additionalPrivileges: activeAdditionalPrivileges
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((item): item is NonNullable<typeof item> => Boolean(item));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "GetProjectUserPermissions" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId");
|
const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId");
|
||||||
@ -414,6 +812,163 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProjectIdentityPermissions = async (projectId: string) => {
|
||||||
|
try {
|
||||||
|
const docs = await db
|
||||||
|
.replicaNode()(TableName.IdentityProjectMembership)
|
||||||
|
.join(
|
||||||
|
TableName.IdentityProjectMembershipRole,
|
||||||
|
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityProjectMembership}.identityId`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectRoles,
|
||||||
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
|
`${TableName.ProjectRoles}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.IdentityProjectAdditionalPrivilege,
|
||||||
|
`${TableName.IdentityProjectAdditionalPrivilege}.projectMembershipId`,
|
||||||
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
// Join the Project table to later select orgId
|
||||||
|
TableName.Project,
|
||||||
|
`${TableName.IdentityProjectMembership}.projectId`,
|
||||||
|
`${TableName.Project}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
||||||
|
.andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
|
})
|
||||||
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
|
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
||||||
|
db.ref("id").withSchema(TableName.Identity).as("identityId"),
|
||||||
|
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||||
|
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
|
||||||
|
db.ref("type").withSchema(TableName.Project).as("projectType"),
|
||||||
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||||
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||||
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
|
db.ref("permissions").withSchema(TableName.ProjectRoles),
|
||||||
|
db.ref("id").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApId"),
|
||||||
|
db.ref("permissions").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApPermissions"),
|
||||||
|
db
|
||||||
|
.ref("temporaryMode")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryMode"),
|
||||||
|
db.ref("isTemporary").withSchema(TableName.IdentityProjectAdditionalPrivilege).as("identityApIsTemporary"),
|
||||||
|
db
|
||||||
|
.ref("temporaryRange")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryRange"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessStartTime")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryAccessStartTime"),
|
||||||
|
db
|
||||||
|
.ref("temporaryAccessEndTime")
|
||||||
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
|
.as("identityApTemporaryAccessEndTime"),
|
||||||
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
||||||
|
);
|
||||||
|
|
||||||
|
const permissions = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "identityId",
|
||||||
|
parentMapper: ({
|
||||||
|
membershipId,
|
||||||
|
membershipCreatedAt,
|
||||||
|
membershipUpdatedAt,
|
||||||
|
orgId,
|
||||||
|
identityName,
|
||||||
|
projectType,
|
||||||
|
identityId
|
||||||
|
}) => ({
|
||||||
|
id: membershipId,
|
||||||
|
identityId,
|
||||||
|
username: identityName,
|
||||||
|
projectId,
|
||||||
|
createdAt: membershipCreatedAt,
|
||||||
|
updatedAt: membershipUpdatedAt,
|
||||||
|
orgId,
|
||||||
|
projectType,
|
||||||
|
// just a prefilled value
|
||||||
|
orgAuthEnforced: false
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "id",
|
||||||
|
label: "roles" as const,
|
||||||
|
mapper: (data) =>
|
||||||
|
IdentityProjectMembershipRoleSchema.extend({
|
||||||
|
permissions: z.unknown(),
|
||||||
|
customRoleSlug: z.string().optional().nullable()
|
||||||
|
}).parse(data)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "identityApId",
|
||||||
|
label: "additionalPrivileges" as const,
|
||||||
|
mapper: ({
|
||||||
|
identityApId,
|
||||||
|
identityApPermissions,
|
||||||
|
identityApIsTemporary,
|
||||||
|
identityApTemporaryMode,
|
||||||
|
identityApTemporaryRange,
|
||||||
|
identityApTemporaryAccessEndTime,
|
||||||
|
identityApTemporaryAccessStartTime
|
||||||
|
}) => ({
|
||||||
|
id: identityApId,
|
||||||
|
permissions: identityApPermissions,
|
||||||
|
temporaryRange: identityApTemporaryRange,
|
||||||
|
temporaryMode: identityApTemporaryMode,
|
||||||
|
temporaryAccessEndTime: identityApTemporaryAccessEndTime,
|
||||||
|
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
||||||
|
isTemporary: identityApIsTemporary
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return permissions
|
||||||
|
.map((permission) => {
|
||||||
|
if (!permission) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// when introducting cron mode change it here
|
||||||
|
const activeRoles = permission?.roles.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
);
|
||||||
|
const activeAdditionalPrivileges = permission?.additionalPrivileges?.filter(
|
||||||
|
({ isTemporary, temporaryAccessEndTime }) =>
|
||||||
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
|
);
|
||||||
|
|
||||||
|
return { ...permission, roles: activeRoles, additionalPrivileges: activeAdditionalPrivileges };
|
||||||
|
})
|
||||||
|
.filter((item): item is NonNullable<typeof item> => Boolean(item));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "GetProjectIdentityPermissions" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
||||||
try {
|
try {
|
||||||
const docs = await db
|
const docs = await db
|
||||||
@ -568,6 +1123,9 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
getOrgPermission,
|
getOrgPermission,
|
||||||
getOrgIdentityPermission,
|
getOrgIdentityPermission,
|
||||||
getProjectPermission,
|
getProjectPermission,
|
||||||
getProjectIdentityPermission
|
getProjectIdentityPermission,
|
||||||
|
getProjectUserPermissions,
|
||||||
|
getProjectIdentityPermissions,
|
||||||
|
getProjectGroupPermissions
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
9
backend/src/ee/services/permission/permission-schemas.ts
Normal file
9
backend/src/ee/services/permission/permission-schemas.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const CASL_ACTION_SCHEMA_NATIVE_ENUM = <ACTION extends z.EnumLike>(actions: ACTION) =>
|
||||||
|
z
|
||||||
|
.union([z.nativeEnum(actions), z.nativeEnum(actions).array().min(1)])
|
||||||
|
.transform((el) => (typeof el === "string" ? [el] : el));
|
||||||
|
|
||||||
|
export const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
|
||||||
|
z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
|
@ -1,3 +1,6 @@
|
|||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export type TBuildProjectPermissionDTO = {
|
export type TBuildProjectPermissionDTO = {
|
||||||
permissions?: unknown;
|
permissions?: unknown;
|
||||||
role: string;
|
role: string;
|
||||||
@ -7,3 +10,34 @@ export type TBuildOrgPermissionDTO = {
|
|||||||
permissions?: unknown;
|
permissions?: unknown;
|
||||||
role: string;
|
role: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
export type TGetUserProjectPermissionArg = {
|
||||||
|
userId: string;
|
||||||
|
projectId: string;
|
||||||
|
authMethod: ActorAuthMethod;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
userOrgId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetIdentityProjectPermissionArg = {
|
||||||
|
identityId: string;
|
||||||
|
projectId: string;
|
||||||
|
identityOrgId?: string;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetServiceTokenProjectPermissionArg = {
|
||||||
|
serviceTokenId: string;
|
||||||
|
projectId: string;
|
||||||
|
actorOrgId?: string;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetProjectPermissionArg = {
|
||||||
|
actor: ActorType;
|
||||||
|
actorId: string;
|
||||||
|
projectId: string;
|
||||||
|
actorAuthMethod: ActorAuthMethod;
|
||||||
|
actorOrgId?: string;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
};
|
||||||
|
@ -4,9 +4,9 @@ import { MongoQuery } from "@ucast/mongo2js";
|
|||||||
import handlebars from "handlebars";
|
import handlebars from "handlebars";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ActionProjectType,
|
||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectType,
|
|
||||||
ServiceTokenScopes,
|
ServiceTokenScopes,
|
||||||
TIdentityProjectMemberships,
|
TIdentityProjectMemberships,
|
||||||
TProjectMemberships
|
TProjectMemberships
|
||||||
@ -23,7 +23,14 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
|
|||||||
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
||||||
import { TPermissionDALFactory } from "./permission-dal";
|
import { TPermissionDALFactory } from "./permission-dal";
|
||||||
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
|
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
|
||||||
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-service-types";
|
import {
|
||||||
|
TBuildOrgPermissionDTO,
|
||||||
|
TBuildProjectPermissionDTO,
|
||||||
|
TGetIdentityProjectPermissionArg,
|
||||||
|
TGetProjectPermissionArg,
|
||||||
|
TGetServiceTokenProjectPermissionArg,
|
||||||
|
TGetUserProjectPermissionArg
|
||||||
|
} from "./permission-service-types";
|
||||||
import {
|
import {
|
||||||
buildServiceTokenProjectPermission,
|
buildServiceTokenProjectPermission,
|
||||||
projectAdminPermissions,
|
projectAdminPermissions,
|
||||||
@ -193,12 +200,13 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// user permission for a project in an organization
|
// user permission for a project in an organization
|
||||||
const getUserProjectPermission = async (
|
const getUserProjectPermission = async ({
|
||||||
userId: string,
|
userId,
|
||||||
projectId: string,
|
projectId,
|
||||||
authMethod: ActorAuthMethod,
|
authMethod,
|
||||||
userOrgId?: string
|
userOrgId,
|
||||||
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
actionProjectType
|
||||||
|
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
||||||
|
|
||||||
@ -219,6 +227,12 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
|
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
|
||||||
|
|
||||||
|
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// join two permissions and pass to build the final permission set
|
// join two permissions and pass to build the final permission set
|
||||||
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
const additionalPrivileges =
|
const additionalPrivileges =
|
||||||
@ -256,13 +270,6 @@ export const permissionServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
permission,
|
permission,
|
||||||
membership: userProjectPermission,
|
membership: userProjectPermission,
|
||||||
ForbidOnInvalidProjectType: (productType: ProjectType) => {
|
|
||||||
if (productType !== userProjectPermission.projectType) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
userProjectPermission.roles.findIndex(
|
userProjectPermission.roles.findIndex(
|
||||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
@ -270,11 +277,12 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIdentityProjectPermission = async (
|
const getIdentityProjectPermission = async ({
|
||||||
identityId: string,
|
identityId,
|
||||||
projectId: string,
|
projectId,
|
||||||
identityOrgId: string | undefined
|
identityOrgId,
|
||||||
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
actionProjectType
|
||||||
|
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
if (!identityProjectPermission)
|
if (!identityProjectPermission)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
@ -293,6 +301,12 @@ export const permissionServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const rolePermissions =
|
const rolePermissions =
|
||||||
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
const additionalPrivileges =
|
const additionalPrivileges =
|
||||||
@ -331,13 +345,6 @@ export const permissionServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
permission,
|
permission,
|
||||||
membership: identityProjectPermission,
|
membership: identityProjectPermission,
|
||||||
ForbidOnInvalidProjectType: (productType: ProjectType) => {
|
|
||||||
if (productType !== identityProjectPermission.projectType) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
identityProjectPermission.roles.findIndex(
|
identityProjectPermission.roles.findIndex(
|
||||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
@ -345,11 +352,12 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getServiceTokenProjectPermission = async (
|
const getServiceTokenProjectPermission = async ({
|
||||||
serviceTokenId: string,
|
serviceTokenId,
|
||||||
projectId: string,
|
projectId,
|
||||||
actorOrgId: string | undefined
|
actorOrgId,
|
||||||
) => {
|
actionProjectType
|
||||||
|
}: TGetServiceTokenProjectPermissionArg) => {
|
||||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||||
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
||||||
|
|
||||||
@ -373,17 +381,16 @@ export const permissionServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||||
return {
|
return {
|
||||||
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
||||||
membership: undefined,
|
membership: undefined
|
||||||
ForbidOnInvalidProjectType: (productType: ProjectType) => {
|
|
||||||
if (productType !== serviceTokenProject.type) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${productType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -392,7 +399,6 @@ export const permissionServiceFactory = ({
|
|||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
membership: undefined;
|
membership: undefined;
|
||||||
hasRole: (arg: string) => boolean;
|
hasRole: (arg: string) => boolean;
|
||||||
ForbidOnInvalidProjectType: (type: ProjectType) => void;
|
|
||||||
} // service token doesn't have both membership and roles
|
} // service token doesn't have both membership and roles
|
||||||
: {
|
: {
|
||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
@ -402,23 +408,156 @@ export const permissionServiceFactory = ({
|
|||||||
roles: Array<{ role: string }>;
|
roles: Array<{ role: string }>;
|
||||||
};
|
};
|
||||||
hasRole: (role: string) => boolean;
|
hasRole: (role: string) => boolean;
|
||||||
ForbidOnInvalidProjectType: (type: ProjectType) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectPermission = async <T extends ActorType>(
|
const getProjectPermissions = async (projectId: string) => {
|
||||||
type: T,
|
// fetch user permissions
|
||||||
id: string,
|
const rawUserProjectPermissions = await permissionDAL.getProjectUserPermissions(projectId);
|
||||||
projectId: string,
|
const userPermissions = rawUserProjectPermissions.map((userProjectPermission) => {
|
||||||
actorAuthMethod: ActorAuthMethod,
|
const rolePermissions =
|
||||||
actorOrgId: string | undefined
|
userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
): Promise<TProjectPermissionRT<T>> => {
|
const additionalPrivileges =
|
||||||
switch (type) {
|
userProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||||
|
role: ProjectMembershipRole.Custom,
|
||||||
|
permissions
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||||
|
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
||||||
|
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
|
||||||
|
objectify(
|
||||||
|
userProjectPermission.metadata,
|
||||||
|
(i) => i.key,
|
||||||
|
(i) => i.value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
id: userProjectPermission.userId,
|
||||||
|
username: userProjectPermission.username,
|
||||||
|
metadata: metadataKeyValuePair
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ data: false }
|
||||||
|
);
|
||||||
|
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||||
|
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
permission,
|
||||||
|
id: userProjectPermission.userId,
|
||||||
|
name: userProjectPermission.username,
|
||||||
|
membershipId: userProjectPermission.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// fetch identity permissions
|
||||||
|
const rawIdentityProjectPermissions = await permissionDAL.getProjectIdentityPermissions(projectId);
|
||||||
|
const identityPermissions = rawIdentityProjectPermissions.map((identityProjectPermission) => {
|
||||||
|
const rolePermissions =
|
||||||
|
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
|
const additionalPrivileges =
|
||||||
|
identityProjectPermission.additionalPrivileges?.map(({ permissions }) => ({
|
||||||
|
role: ProjectMembershipRole.Custom,
|
||||||
|
permissions
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||||
|
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
||||||
|
const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
|
||||||
|
objectify(
|
||||||
|
identityProjectPermission.metadata,
|
||||||
|
(i) => i.key,
|
||||||
|
(i) => i.value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
id: identityProjectPermission.identityId,
|
||||||
|
username: identityProjectPermission.username,
|
||||||
|
metadata: metadataKeyValuePair
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ data: false }
|
||||||
|
);
|
||||||
|
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||||
|
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
permission,
|
||||||
|
id: identityProjectPermission.identityId,
|
||||||
|
name: identityProjectPermission.username,
|
||||||
|
membershipId: identityProjectPermission.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// fetch group permissions
|
||||||
|
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId);
|
||||||
|
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {
|
||||||
|
const rolePermissions =
|
||||||
|
groupProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
|
const rules = buildProjectPermissionRules(rolePermissions);
|
||||||
|
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
|
||||||
|
conditionsMatcher
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
permission,
|
||||||
|
id: groupProjectPermission.groupId,
|
||||||
|
name: groupProjectPermission.username,
|
||||||
|
membershipId: groupProjectPermission.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
userPermissions,
|
||||||
|
identityPermissions,
|
||||||
|
groupPermissions
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getProjectPermission = async <T extends ActorType>({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType
|
||||||
|
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||||
|
switch (actor) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
return getUserProjectPermission({
|
||||||
|
userId: actorId,
|
||||||
|
projectId,
|
||||||
|
authMethod: actorAuthMethod,
|
||||||
|
userOrgId: actorOrgId,
|
||||||
|
actionProjectType
|
||||||
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.SERVICE:
|
case ActorType.SERVICE:
|
||||||
return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
return getServiceTokenProjectPermission({
|
||||||
|
serviceTokenId: actorId,
|
||||||
|
projectId,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType
|
||||||
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
return getIdentityProjectPermission({
|
||||||
|
identityId: actorId,
|
||||||
|
projectId,
|
||||||
|
identityOrgId: actorOrgId,
|
||||||
|
actionProjectType
|
||||||
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Invalid actor provided",
|
message: "Invalid actor provided",
|
||||||
@ -455,6 +594,7 @@ export const permissionServiceFactory = ({
|
|||||||
getOrgPermission,
|
getOrgPermission,
|
||||||
getUserProjectPermission,
|
getUserProjectPermission,
|
||||||
getProjectPermission,
|
getProjectPermission,
|
||||||
|
getProjectPermissions,
|
||||||
getOrgPermissionByRole,
|
getOrgPermissionByRole,
|
||||||
getProjectPermissionByRole,
|
getProjectPermissionByRole,
|
||||||
buildOrgPermission,
|
buildOrgPermission,
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CASL_ACTION_SCHEMA_ENUM,
|
||||||
|
CASL_ACTION_SCHEMA_NATIVE_ENUM
|
||||||
|
} from "@app/ee/services/permission/permission-schemas";
|
||||||
import { conditionsMatcher, PermissionConditionOperators } from "@app/lib/casl";
|
import { conditionsMatcher, PermissionConditionOperators } from "@app/lib/casl";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
|
|
||||||
@ -30,6 +34,16 @@ export enum ProjectPermissionDynamicSecretActions {
|
|||||||
Lease = "lease"
|
Lease = "lease"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionSecretSyncActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
SyncSecrets = "sync-secrets",
|
||||||
|
ImportSecrets = "import-secrets",
|
||||||
|
RemoveSecrets = "remove-secrets"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSub {
|
export enum ProjectPermissionSub {
|
||||||
Role = "role",
|
Role = "role",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
@ -60,7 +74,8 @@ export enum ProjectPermissionSub {
|
|||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
Cmek = "cmek"
|
Cmek = "cmek",
|
||||||
|
SecretSyncs = "secret-syncs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SecretSubjectFields = {
|
export type SecretSubjectFields = {
|
||||||
@ -140,6 +155,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
|
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
||||||
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
|
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||||
@ -147,14 +163,27 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
||||||
|
|
||||||
const CASL_ACTION_SCHEMA_NATIVE_ENUM = <ACTION extends z.EnumLike>(actions: ACTION) =>
|
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
|
||||||
|
const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([
|
||||||
|
z.string().refine((val) => val.startsWith("/"), SECRET_PATH_MISSING_SLASH_ERR_MSG),
|
||||||
z
|
z
|
||||||
.union([z.nativeEnum(actions), z.nativeEnum(actions).array().min(1)])
|
.object({
|
||||||
.transform((el) => (typeof el === "string" ? [el] : el));
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ].refine(
|
||||||
|
(val) => val.startsWith("/"),
|
||||||
const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
|
SECRET_PATH_MISSING_SLASH_ERR_MSG
|
||||||
z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
|
),
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ].refine(
|
||||||
|
(val) => val.startsWith("/"),
|
||||||
|
SECRET_PATH_MISSING_SLASH_ERR_MSG
|
||||||
|
),
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN].refine(
|
||||||
|
(val) => val.every((el) => el.startsWith("/")),
|
||||||
|
SECRET_PATH_MISSING_SLASH_ERR_MSG
|
||||||
|
),
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]);
|
||||||
// akhilmhdh: don't modify this for v2
|
// akhilmhdh: don't modify this for v2
|
||||||
// if you want to update create a new schema
|
// if you want to update create a new schema
|
||||||
const SecretConditionV1Schema = z
|
const SecretConditionV1Schema = z
|
||||||
@ -169,17 +198,7 @@ const SecretConditionV1Schema = z
|
|||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
]),
|
]),
|
||||||
secretPath: z.union([
|
secretPath: SECRET_PATH_PERMISSION_OPERATOR_SCHEMA
|
||||||
z.string(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
@ -196,17 +215,7 @@ const SecretConditionV2Schema = z
|
|||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
]),
|
]),
|
||||||
secretPath: z.union([
|
secretPath: SECRET_PATH_PERMISSION_OPERATOR_SCHEMA,
|
||||||
z.string(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
]),
|
|
||||||
secretName: z.union([
|
secretName: z.union([
|
||||||
z.string(),
|
z.string(),
|
||||||
z
|
z
|
||||||
@ -392,10 +401,15 @@ const GeneralPermissionSchema = [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.SecretSyncs).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretSyncActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -549,6 +563,18 @@ const buildAdminPermissionRules = () => {
|
|||||||
],
|
],
|
||||||
ProjectPermissionSub.Cmek
|
ProjectPermissionSub.Cmek
|
||||||
);
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretSyncActions.Create,
|
||||||
|
ProjectPermissionSecretSyncActions.Edit,
|
||||||
|
ProjectPermissionSecretSyncActions.Delete,
|
||||||
|
ProjectPermissionSecretSyncActions.Read,
|
||||||
|
ProjectPermissionSecretSyncActions.SyncSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.ImportSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.RemoveSecrets
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretSyncs
|
||||||
|
);
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -713,6 +739,19 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionSub.Cmek
|
ProjectPermissionSub.Cmek
|
||||||
);
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretSyncActions.Create,
|
||||||
|
ProjectPermissionSecretSyncActions.Edit,
|
||||||
|
ProjectPermissionSecretSyncActions.Delete,
|
||||||
|
ProjectPermissionSecretSyncActions.Read,
|
||||||
|
ProjectPermissionSecretSyncActions.SyncSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.ImportSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.RemoveSecrets
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretSyncs
|
||||||
|
);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -746,6 +785,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateAuthorities);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateAuthorities);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
||||||
|
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
|||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
@ -55,21 +55,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!projectMembership)
|
if (!projectMembership)
|
||||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
projectMembership.userId,
|
actorId: projectMembership.userId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@ -140,21 +142,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
projectMembership.userId,
|
actorId: projectMembership.userId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@ -224,13 +228,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||||
@ -260,13 +265,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -286,13 +292,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!projectMembership)
|
if (!projectMembership)
|
||||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
|
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
|
||||||
|
@ -421,14 +421,14 @@ export const samlConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
||||||
|
@ -6,7 +6,8 @@ export enum SamlProviders {
|
|||||||
AZURE_SAML = "azure-saml",
|
AZURE_SAML = "azure-saml",
|
||||||
JUMPCLOUD_SAML = "jumpcloud-saml",
|
JUMPCLOUD_SAML = "jumpcloud-saml",
|
||||||
GOOGLE_SAML = "google-saml",
|
GOOGLE_SAML = "google-saml",
|
||||||
KEYCLOAK_SAML = "keycloak-saml"
|
KEYCLOAK_SAML = "keycloak-saml",
|
||||||
|
AUTH0_SAML = "auth0-saml"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateSamlCfgDTO = {
|
export type TCreateSamlCfgDTO = {
|
||||||
|
@ -531,7 +531,7 @@ export const scimServiceFactory = ({
|
|||||||
firstName: scimUser.name.givenName,
|
firstName: scimUser.name.givenName,
|
||||||
email: scimUser.emails[0].value,
|
email: scimUser.emails[0].value,
|
||||||
lastName: scimUser.name.familyName,
|
lastName: scimUser.name.familyName,
|
||||||
isEmailVerified: hasEmailChanged ? trustScimEmails : true
|
isEmailVerified: hasEmailChanged ? trustScimEmails : undefined
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -790,6 +790,21 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const newGroup = await groupDAL.transaction(async (tx) => {
|
const newGroup = await groupDAL.transaction(async (tx) => {
|
||||||
|
const conflictingGroup = await groupDAL.findOne(
|
||||||
|
{
|
||||||
|
name: displayName,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (conflictingGroup) {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: `Group with name '${displayName}' already exists in the organization`,
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const group = await groupDAL.create(
|
const group = await groupDAL.create(
|
||||||
{
|
{
|
||||||
name: displayName,
|
name: displayName,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@ -79,14 +79,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
if (!groupApprovers.length && approvals > approvers.length)
|
if (!groupApprovers.length && approvals > approvers.length)
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
@ -193,14 +193,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalPolicy.projectId,
|
projectId: secretApprovalPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -288,14 +288,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
if (!sapPolicy)
|
if (!sapPolicy)
|
||||||
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
|
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sapPolicy.projectId,
|
projectId: sapPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
@ -328,13 +328,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
projectId
|
projectId
|
||||||
}: TListSapDTO) => {
|
}: TListSapDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
||||||
@ -372,7 +373,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
}: TGetBoardSapDTO) => {
|
}: TGetBoardSapDTO) => {
|
||||||
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||||
};
|
};
|
||||||
@ -392,13 +400,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sapPolicy.projectId,
|
projectId: sapPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ export const sendApprovalEmailsFn = async ({
|
|||||||
firstName: reviewerUser.firstName,
|
firstName: reviewerUser.firstName,
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
organizationName: project.organization.name,
|
organizationName: project.organization.name,
|
||||||
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval?requestId=${secretApprovalRequest.id}`
|
approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval?requestId=${secretApprovalRequest.id}`
|
||||||
},
|
},
|
||||||
template: SmtpTemplates.SecretApprovalRequestNeedsReview
|
template: SmtpTemplates.SecretApprovalRequestNeedsReview
|
||||||
});
|
});
|
||||||
|
@ -256,6 +256,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
|
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
|
||||||
db.ref("id").withSchema("secVerTag")
|
db.ref("id").withSchema("secVerTag")
|
||||||
)
|
)
|
||||||
|
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
||||||
.select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2))
|
.select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2))
|
||||||
.select({
|
.select({
|
||||||
secVerTagId: "secVerTag.id",
|
secVerTagId: "secVerTag.id",
|
||||||
@ -279,6 +280,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
|
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
|
||||||
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
|
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
|
||||||
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
|
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.ResourceMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.ResourceMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.ResourceMetadata).as("metadataValue")
|
||||||
);
|
);
|
||||||
const formatedDoc = sqlNestRelationships({
|
const formatedDoc = sqlNestRelationships({
|
||||||
data: doc,
|
data: doc,
|
||||||
@ -338,9 +344,19 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "oldSecretMetadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
|
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
|
||||||
...el,
|
...el,
|
||||||
secret: secret?.[0],
|
secret: secret?.[0],
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ActionProjectType,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectType,
|
|
||||||
SecretEncryptionAlgo,
|
SecretEncryptionAlgo,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
SecretType,
|
SecretType,
|
||||||
@ -22,6 +22,8 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
|
|||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
|
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
|
||||||
|
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import {
|
import {
|
||||||
decryptSecretWithBot,
|
decryptSecretWithBot,
|
||||||
@ -91,6 +93,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
||||||
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">;
|
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">;
|
||||||
@ -138,18 +141,20 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
secretVersionTagV2BridgeDAL,
|
secretVersionTagV2BridgeDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectSlackConfigDAL
|
projectSlackConfigDAL,
|
||||||
|
resourceMetadataDAL
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
await permissionService.getProjectPermission(
|
await permissionService.getProjectPermission({
|
||||||
actor as ActorType.USER,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
|
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
|
||||||
return count;
|
return count;
|
||||||
@ -169,7 +174,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}: TListApprovalsDTO) => {
|
}: TListApprovalsDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@ -212,13 +224,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
@ -241,6 +254,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretKey: el.key,
|
secretKey: el.key,
|
||||||
id: el.id,
|
id: el.id,
|
||||||
version: el.version,
|
version: el.version,
|
||||||
|
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
|
||||||
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
secretComment: el.encryptedComment
|
secretComment: el.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||||
@ -269,7 +283,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretComment: el.secretVersion.encryptedComment
|
secretComment: el.secretVersion.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
||||||
: "",
|
: "",
|
||||||
tags: el.secretVersion.tags
|
tags: el.secretVersion.tags,
|
||||||
|
secretMetadata: el.oldSecretMetadata as ResourceMetadataDTO
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
@ -330,13 +345,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
projectId: secretApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
@ -396,13 +412,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
projectId: secretApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
@ -452,13 +469,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
@ -543,6 +561,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
? await fnSecretV2BridgeBulkInsert({
|
? await fnSecretV2BridgeBulkInsert({
|
||||||
tx,
|
tx,
|
||||||
folderId,
|
folderId,
|
||||||
|
orgId: actorOrgId,
|
||||||
inputSecrets: secretCreationCommits.map((el) => ({
|
inputSecrets: secretCreationCommits.map((el) => ({
|
||||||
tagIds: el?.tags.map(({ id }) => id),
|
tagIds: el?.tags.map(({ id }) => id),
|
||||||
version: 1,
|
version: 1,
|
||||||
@ -550,6 +569,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
encryptedValue: el.encryptedValue,
|
encryptedValue: el.encryptedValue,
|
||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||||
key: el.key,
|
key: el.key,
|
||||||
|
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
|
||||||
references: el.encryptedValue
|
references: el.encryptedValue
|
||||||
? getAllSecretReferencesV2Bridge(
|
? getAllSecretReferencesV2Bridge(
|
||||||
secretManagerDecryptor({
|
secretManagerDecryptor({
|
||||||
@ -559,6 +579,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
: [],
|
: [],
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
})),
|
})),
|
||||||
|
resourceMetadataDAL,
|
||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
secretVersionDAL: secretVersionV2BridgeDAL,
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
@ -568,6 +589,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const updatedSecrets = secretUpdationCommits.length
|
const updatedSecrets = secretUpdationCommits.length
|
||||||
? await fnSecretV2BridgeBulkUpdate({
|
? await fnSecretV2BridgeBulkUpdate({
|
||||||
folderId,
|
folderId,
|
||||||
|
orgId: actorOrgId,
|
||||||
tx,
|
tx,
|
||||||
inputSecrets: secretUpdationCommits.map((el) => {
|
inputSecrets: secretUpdationCommits.map((el) => {
|
||||||
const encryptedValue =
|
const encryptedValue =
|
||||||
@ -592,6 +614,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||||
key: el.key,
|
key: el.key,
|
||||||
tags: el?.tags.map(({ id }) => id),
|
tags: el?.tags.map(({ id }) => id),
|
||||||
|
secretMetadata: el.secretMetadata as ResourceMetadataDTO,
|
||||||
...encryptedValue
|
...encryptedValue
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -599,7 +622,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
secretVersionDAL: secretVersionV2BridgeDAL,
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL: secretVersionTagV2BridgeDAL
|
secretVersionTagDAL: secretVersionTagV2BridgeDAL,
|
||||||
|
resourceMetadataDAL
|
||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
const deletedSecret = secretDeletionCommits.length
|
const deletedSecret = secretDeletionCommits.length
|
||||||
@ -824,6 +848,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
projectId,
|
projectId,
|
||||||
|
orgId: actorOrgId,
|
||||||
secretPath: folder.path,
|
secretPath: folder.path,
|
||||||
environmentSlug: folder.environmentSlug,
|
environmentSlug: folder.environmentSlug,
|
||||||
actorId,
|
actorId,
|
||||||
@ -852,7 +877,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
bypassReason,
|
bypassReason,
|
||||||
secretPath: policy.secretPath,
|
secretPath: policy.secretPath,
|
||||||
environment: env.name,
|
environment: env.name,
|
||||||
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval`
|
approvalUrl: `${cfg.SITE_URL}/secret-manager/${project.id}/approval`
|
||||||
},
|
},
|
||||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||||
});
|
});
|
||||||
@ -876,14 +901,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}: TGenerateSecretApprovalRequestDTO) => {
|
}: TGenerateSecretApprovalRequestDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
@ -1157,14 +1182,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
||||||
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@ -1208,6 +1233,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
),
|
),
|
||||||
skipMultilineEncoding: createdSecret.skipMultilineEncoding,
|
skipMultilineEncoding: createdSecret.skipMultilineEncoding,
|
||||||
key: createdSecret.secretKey,
|
key: createdSecret.secretKey,
|
||||||
|
secretMetadata: createdSecret.secretMetadata,
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
@ -1241,9 +1267,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
if (secrets.length)
|
|
||||||
|
if (secrets.length !== secretsWithNewName.length)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Secret does not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
message: `Secret does not exist: ${secrets.map((el) => el.key).join(",")}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1263,12 +1290,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
reminderNote,
|
reminderNote,
|
||||||
secretComment,
|
secretComment,
|
||||||
metadata,
|
metadata,
|
||||||
skipMultilineEncoding
|
skipMultilineEncoding,
|
||||||
|
secretMetadata
|
||||||
}) => {
|
}) => {
|
||||||
const secretId = updatingSecretsGroupByKey[secretKey][0].id;
|
const secretId = updatingSecretsGroupByKey[secretKey][0].id;
|
||||||
if (tagIds?.length) commitTagIds[secretKey] = tagIds;
|
if (tagIds?.length) commitTagIds[secretKey] = tagIds;
|
||||||
return {
|
return {
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
|
secretMetadata,
|
||||||
key: newSecretName || secretKey,
|
key: newSecretName || secretKey,
|
||||||
encryptedComment: setKnexStringValue(
|
encryptedComment: setKnexStringValue(
|
||||||
secretComment,
|
secretComment,
|
||||||
@ -1370,7 +1399,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
reminderRepeatDays,
|
reminderRepeatDays,
|
||||||
encryptedValue,
|
encryptedValue,
|
||||||
secretId,
|
secretId,
|
||||||
secretVersion
|
secretVersion,
|
||||||
|
secretMetadata
|
||||||
}) => ({
|
}) => ({
|
||||||
version,
|
version,
|
||||||
requestId: doc.id,
|
requestId: doc.id,
|
||||||
@ -1383,7 +1413,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
reminderRepeatDays,
|
reminderRepeatDays,
|
||||||
reminderNote,
|
reminderNote,
|
||||||
encryptedComment,
|
encryptedComment,
|
||||||
key
|
key,
|
||||||
|
secretMetadata: JSON.stringify(secretMetadata)
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
tx
|
tx
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
|
import { TImmutableDBKeys, TSecretApprovalPolicies, TSecretApprovalRequestsSecrets } from "@app/db/schemas";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
import { SecretOperations } from "@app/services/secret/secret-types";
|
import { SecretOperations } from "@app/services/secret/secret-types";
|
||||||
|
|
||||||
export enum RequestState {
|
export enum RequestState {
|
||||||
@ -34,6 +35,7 @@ export type TApprovalCreateSecretV2Bridge = {
|
|||||||
reminderRepeatDays?: number | null;
|
reminderRepeatDays?: number | null;
|
||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
metadata?: Record<string, string>;
|
metadata?: Record<string, string>;
|
||||||
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
tagIds?: string[];
|
tagIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ import { ActorType } from "@app/services/auth/auth-type";
|
|||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
|
import { TResourceMetadataDALFactory } from "@app/services/resource-metadata/resource-metadata-dal";
|
||||||
|
import { ResourceMetadataDTO } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
|
import { fnSecretBulkInsert, fnSecretBulkUpdate } from "@app/services/secret/secret-fns";
|
||||||
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
|
import { TSecretQueueFactory, uniqueSecretQueueKey } from "@app/services/secret/secret-queue";
|
||||||
@ -56,6 +58,7 @@ type TSecretReplicationServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "find" | "insertMany">;
|
||||||
secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">;
|
secretVersionV2TagBridgeDAL: Pick<TSecretVersionV2TagDALFactory, "find" | "insertMany">;
|
||||||
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">;
|
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "replicateSecrets">;
|
||||||
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
|
queueService: Pick<TQueueServiceFactory, "start" | "listen" | "queue" | "stopJobById">;
|
||||||
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
|
secretApprovalPolicyService: Pick<TSecretApprovalPolicyServiceFactory, "getSecretApprovalPolicy">;
|
||||||
@ -121,7 +124,8 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretVersionV2TagBridgeDAL,
|
secretVersionV2TagBridgeDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
secretV2BridgeDAL,
|
secretV2BridgeDAL,
|
||||||
kmsService
|
kmsService,
|
||||||
|
resourceMetadataDAL
|
||||||
}: TSecretReplicationServiceFactoryDep) => {
|
}: TSecretReplicationServiceFactoryDep) => {
|
||||||
const $getReplicatedSecrets = (
|
const $getReplicatedSecrets = (
|
||||||
botKey: string,
|
botKey: string,
|
||||||
@ -151,8 +155,10 @@ export const secretReplicationServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const $getReplicatedSecretsV2 = (
|
const $getReplicatedSecretsV2 = (
|
||||||
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[],
|
localSecrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[],
|
||||||
importedSecrets: { secrets: (TSecretsV2 & { secretKey: string; secretValue?: string })[] }[]
|
importedSecrets: {
|
||||||
|
secrets: (TSecretsV2 & { secretKey: string; secretValue?: string; secretMetadata?: ResourceMetadataDTO })[];
|
||||||
|
}[]
|
||||||
) => {
|
) => {
|
||||||
const deDupe = new Set<string>();
|
const deDupe = new Set<string>();
|
||||||
const secrets = [...localSecrets];
|
const secrets = [...localSecrets];
|
||||||
@ -178,6 +184,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
environmentSlug,
|
environmentSlug,
|
||||||
projectId,
|
projectId,
|
||||||
|
orgId,
|
||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
pickOnlyImportIds,
|
pickOnlyImportIds,
|
||||||
@ -222,6 +229,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
.map(({ folderId }) =>
|
.map(({ folderId }) =>
|
||||||
secretQueueService.replicateSecrets({
|
secretQueueService.replicateSecrets({
|
||||||
projectId,
|
projectId,
|
||||||
|
orgId,
|
||||||
secretPath: foldersGroupedById[folderId][0]?.path as string,
|
secretPath: foldersGroupedById[folderId][0]?.path as string,
|
||||||
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
|
environmentSlug: foldersGroupedById[folderId][0]?.environmentSlug as string,
|
||||||
actorId,
|
actorId,
|
||||||
@ -267,6 +275,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
|
const sourceSecrets = $getReplicatedSecretsV2(sourceDecryptedLocalSecrets, sourceImportedSecrets);
|
||||||
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
|
const sourceSecretsGroupByKey = groupBy(sourceSecrets, (i) => i.key);
|
||||||
|
|
||||||
@ -333,13 +342,29 @@ export const secretReplicationServiceFactory = ({
|
|||||||
.map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create
|
.map((el) => ({ ...el, operation: SecretOperations.Create })); // rewrite update ops to create
|
||||||
|
|
||||||
const locallyUpdatedSecrets = sourceSecrets
|
const locallyUpdatedSecrets = sourceSecrets
|
||||||
.filter(
|
.filter(({ key, secretKey, secretValue, secretMetadata }) => {
|
||||||
({ key, secretKey, secretValue }) =>
|
const sourceSecretMetadataJson = JSON.stringify(
|
||||||
|
(secretMetadata ?? []).map((entry) => ({
|
||||||
|
key: entry.key,
|
||||||
|
value: entry.value
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const destinationSecretMetadataJson = JSON.stringify(
|
||||||
|
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretMetadata ?? []).map((entry) => ({
|
||||||
|
key: entry.key,
|
||||||
|
value: entry.value
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
destinationLocalSecretsGroupedByKey[key]?.[0] &&
|
destinationLocalSecretsGroupedByKey[key]?.[0] &&
|
||||||
// if key or value changed
|
// if key or value changed
|
||||||
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey ||
|
(destinationLocalSecretsGroupedByKey[key]?.[0]?.secretKey !== secretKey ||
|
||||||
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue)
|
destinationLocalSecretsGroupedByKey[key]?.[0]?.secretValue !== secretValue ||
|
||||||
)
|
sourceSecretMetadataJson !== destinationSecretMetadataJson)
|
||||||
|
);
|
||||||
|
})
|
||||||
.map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create
|
.map((el) => ({ ...el, operation: SecretOperations.Update })); // rewrite update ops to create
|
||||||
|
|
||||||
const locallyDeletedSecrets = destinationLocalSecrets
|
const locallyDeletedSecrets = destinationLocalSecrets
|
||||||
@ -387,6 +412,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
op: operation,
|
op: operation,
|
||||||
requestId: approvalRequestDoc.id,
|
requestId: approvalRequestDoc.id,
|
||||||
metadata: doc.metadata,
|
metadata: doc.metadata,
|
||||||
|
secretMetadata: JSON.stringify(doc.secretMetadata),
|
||||||
key: doc.key,
|
key: doc.key,
|
||||||
encryptedValue: doc.encryptedValue,
|
encryptedValue: doc.encryptedValue,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
@ -406,10 +432,12 @@ export const secretReplicationServiceFactory = ({
|
|||||||
if (locallyCreatedSecrets.length) {
|
if (locallyCreatedSecrets.length) {
|
||||||
await fnSecretV2BridgeBulkInsert({
|
await fnSecretV2BridgeBulkInsert({
|
||||||
folderId: destinationReplicationFolderId,
|
folderId: destinationReplicationFolderId,
|
||||||
|
orgId,
|
||||||
secretVersionDAL: secretVersionV2BridgeDAL,
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
tx,
|
tx,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
|
resourceMetadataDAL,
|
||||||
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
|
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
|
||||||
inputSecrets: locallyCreatedSecrets.map((doc) => {
|
inputSecrets: locallyCreatedSecrets.map((doc) => {
|
||||||
return {
|
return {
|
||||||
@ -419,6 +447,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue,
|
encryptedValue: doc.encryptedValue,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
|
secretMetadata: doc.secretMetadata,
|
||||||
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -426,10 +455,12 @@ export const secretReplicationServiceFactory = ({
|
|||||||
}
|
}
|
||||||
if (locallyUpdatedSecrets.length) {
|
if (locallyUpdatedSecrets.length) {
|
||||||
await fnSecretV2BridgeBulkUpdate({
|
await fnSecretV2BridgeBulkUpdate({
|
||||||
|
orgId,
|
||||||
folderId: destinationReplicationFolderId,
|
folderId: destinationReplicationFolderId,
|
||||||
secretVersionDAL: secretVersionV2BridgeDAL,
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
tx,
|
tx,
|
||||||
|
resourceMetadataDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
|
secretVersionTagDAL: secretVersionV2TagBridgeDAL,
|
||||||
inputSecrets: locallyUpdatedSecrets.map((doc) => {
|
inputSecrets: locallyUpdatedSecrets.map((doc) => {
|
||||||
@ -445,6 +476,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue as Buffer,
|
encryptedValue: doc.encryptedValue as Buffer,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
|
secretMetadata: doc.secretMetadata,
|
||||||
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -466,6 +498,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
|
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
projectId,
|
projectId,
|
||||||
|
orgId,
|
||||||
secretPath: destinationFolder.path,
|
secretPath: destinationFolder.path,
|
||||||
environmentSlug: destinationFolder.environmentSlug,
|
environmentSlug: destinationFolder.environmentSlug,
|
||||||
actorId,
|
actorId,
|
||||||
@ -751,6 +784,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
|
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
projectId,
|
projectId,
|
||||||
|
orgId,
|
||||||
secretPath: destinationFolder.path,
|
secretPath: destinationFolder.path,
|
||||||
environmentSlug: destinationFolder.environmentSlug,
|
environmentSlug: destinationFolder.environmentSlug,
|
||||||
actorId,
|
actorId,
|
||||||
|
@ -180,6 +180,8 @@ export const secretRotationQueueFactory = ({
|
|||||||
provider.template.client === TDbProviderClients.MsSqlServer
|
provider.template.client === TDbProviderClients.MsSqlServer
|
||||||
? ({
|
? ({
|
||||||
encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT,
|
encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT,
|
||||||
|
// when ca is provided use that
|
||||||
|
trustServerCertificate: !ca,
|
||||||
cryptoCredentialsDetails: ca ? { ca } : {}
|
cryptoCredentialsDetails: ca ? { ca } : {}
|
||||||
} as Record<string, unknown>)
|
} as Record<string, unknown>)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
|
|
||||||
import { ProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
@ -53,14 +53,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
projectId
|
projectId
|
||||||
}: TProjectPermission) => {
|
}: TProjectPermission) => {
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -82,14 +82,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
environment
|
environment
|
||||||
}: TCreateSecretRotationDTO) => {
|
}: TCreateSecretRotationDTO) => {
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretRotation
|
ProjectPermissionSub.SecretRotation
|
||||||
@ -191,13 +191,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
|
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@ -236,14 +237,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
|
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
doc.projectId,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
|
||||||
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
|
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
|
||||||
await secretRotationQueue.addToQueue(doc.id, doc.interval);
|
await secretRotationQueue.addToQueue(doc.id, doc.interval);
|
||||||
@ -254,14 +255,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
const doc = await secretRotationDAL.findById(rotationId);
|
const doc = await secretRotationDAL.findById(rotationId);
|
||||||
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
|
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
doc.projectId,
|
projectId: doc.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretRotation
|
ProjectPermissionSub.SecretRotation
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { Knex } from "knex";
|
import knex, { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TSecretScanningGitRisksInsert } from "@app/db/schemas";
|
import { TableName, TSecretScanningGitRisksInsert } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { SecretScanningResolvedStatus, TGetOrgRisksDTO } from "./secret-scanning-types";
|
||||||
|
|
||||||
export type TSecretScanningDALFactory = ReturnType<typeof secretScanningDALFactory>;
|
export type TSecretScanningDALFactory = ReturnType<typeof secretScanningDALFactory>;
|
||||||
|
|
||||||
@ -19,5 +22,70 @@ export const secretScanningDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...gitRiskOrm, upsert };
|
const findByOrgId = async (orgId: string, filter: TGetOrgRisksDTO["filter"], tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
// Find statements
|
||||||
|
const sqlQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
|
||||||
|
// eslint-disable-next-line func-names
|
||||||
|
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId);
|
||||||
|
|
||||||
|
if (filter.repositoryNames) {
|
||||||
|
void sqlQuery.whereIn(`${TableName.SecretScanningGitRisk}.repositoryFullName`, filter.repositoryNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.resolvedStatus) {
|
||||||
|
if (filter.resolvedStatus !== SecretScanningResolvedStatus.All) {
|
||||||
|
const isResolved = filter.resolvedStatus === SecretScanningResolvedStatus.Resolved;
|
||||||
|
|
||||||
|
void sqlQuery.where(`${TableName.SecretScanningGitRisk}.isResolved`, isResolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select statements
|
||||||
|
void sqlQuery
|
||||||
|
.select(selectAllTableCols(TableName.SecretScanningGitRisk))
|
||||||
|
.limit(filter.limit)
|
||||||
|
.offset(filter.offset);
|
||||||
|
|
||||||
|
if (filter.orderBy) {
|
||||||
|
const orderDirection = filter.orderDirection || OrderByDirection.ASC;
|
||||||
|
|
||||||
|
void sqlQuery.orderBy(filter.orderBy, orderDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
|
||||||
|
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
const uniqueReposQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
|
||||||
|
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId)
|
||||||
|
.distinct("repositoryFullName")
|
||||||
|
.select("repositoryFullName");
|
||||||
|
|
||||||
|
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
||||||
|
const docs = await sqlQuery.timeout(1000 * 120);
|
||||||
|
const uniqueRepos = await uniqueReposQuery.timeout(1000 * 120);
|
||||||
|
const totalCount = await countQuery;
|
||||||
|
|
||||||
|
return {
|
||||||
|
risks: docs,
|
||||||
|
totalCount: Number(totalCount?.[0].count),
|
||||||
|
repos: uniqueRepos
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((r) => r.repositoryFullName!)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof knex.KnexTimeoutError) {
|
||||||
|
throw new GatewayTimeoutError({
|
||||||
|
error,
|
||||||
|
message: "Failed to fetch secret leaks due to timeout. Add more search filters."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DatabaseError({ error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...gitRiskOrm, upsert, findByOrgId };
|
||||||
};
|
};
|
||||||
|
@ -15,6 +15,7 @@ import { TSecretScanningDALFactory } from "./secret-scanning-dal";
|
|||||||
import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
|
import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
|
||||||
import {
|
import {
|
||||||
SecretScanningRiskStatus,
|
SecretScanningRiskStatus,
|
||||||
|
TGetAllOrgRisksDTO,
|
||||||
TGetOrgInstallStatusDTO,
|
TGetOrgInstallStatusDTO,
|
||||||
TGetOrgRisksDTO,
|
TGetOrgRisksDTO,
|
||||||
TInstallAppSessionDTO,
|
TInstallAppSessionDTO,
|
||||||
@ -118,11 +119,21 @@ export const secretScanningServiceFactory = ({
|
|||||||
return Boolean(appInstallation);
|
return Boolean(appInstallation);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetOrgRisksDTO) => {
|
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId, filter }: TGetOrgRisksDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
|
const results = await secretScanningDAL.findByOrgId(orgId, filter);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetAllOrgRisksDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
|
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
|
||||||
return { risks };
|
return risks;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRiskStatus = async ({
|
const updateRiskStatus = async ({
|
||||||
@ -189,6 +200,7 @@ export const secretScanningServiceFactory = ({
|
|||||||
linkInstallationToOrg,
|
linkInstallationToOrg,
|
||||||
getOrgInstallationStatus,
|
getOrgInstallationStatus,
|
||||||
getRisksByOrg,
|
getRisksByOrg,
|
||||||
|
getAllRisksByOrg,
|
||||||
updateRiskStatus,
|
updateRiskStatus,
|
||||||
handleRepoPushEvent,
|
handleRepoPushEvent,
|
||||||
handleRepoDeleteEvent
|
handleRepoDeleteEvent
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { TOrgPermission } from "@app/lib/types";
|
import { OrderByDirection, TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export enum SecretScanningRiskStatus {
|
export enum SecretScanningRiskStatus {
|
||||||
FalsePositive = "RESOLVED_FALSE_POSITIVE",
|
FalsePositive = "RESOLVED_FALSE_POSITIVE",
|
||||||
@ -7,6 +7,12 @@ export enum SecretScanningRiskStatus {
|
|||||||
Unresolved = "UNRESOLVED"
|
Unresolved = "UNRESOLVED"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SecretScanningResolvedStatus {
|
||||||
|
All = "all",
|
||||||
|
Resolved = "resolved",
|
||||||
|
Unresolved = "unresolved"
|
||||||
|
}
|
||||||
|
|
||||||
export type TInstallAppSessionDTO = TOrgPermission;
|
export type TInstallAppSessionDTO = TOrgPermission;
|
||||||
|
|
||||||
export type TLinkInstallSessionDTO = {
|
export type TLinkInstallSessionDTO = {
|
||||||
@ -16,7 +22,22 @@ export type TLinkInstallSessionDTO = {
|
|||||||
|
|
||||||
export type TGetOrgInstallStatusDTO = TOrgPermission;
|
export type TGetOrgInstallStatusDTO = TOrgPermission;
|
||||||
|
|
||||||
export type TGetOrgRisksDTO = TOrgPermission;
|
type RiskFilter = {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
orderBy?: "createdAt" | "name";
|
||||||
|
orderDirection?: OrderByDirection;
|
||||||
|
repositoryNames?: string[];
|
||||||
|
resolvedStatus?: SecretScanningResolvedStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetOrgRisksDTO = {
|
||||||
|
filter: RiskFilter;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export type TGetAllOrgRisksDTO = {
|
||||||
|
filter: Omit<RiskFilter, "offset" | "limit" | "orderBy" | "orderDirection">;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TUpdateRiskStatusDTO = {
|
export type TUpdateRiskStatusDTO = {
|
||||||
riskId: string;
|
riskId: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { InternalServerError, NotFoundError } from "@app/lib/errors";
|
import { InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
@ -83,13 +83,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
path
|
path
|
||||||
}: TProjectSnapshotCountDTO) => {
|
}: TProjectSnapshotCountDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
@ -119,13 +120,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
limit = 20,
|
limit = 20,
|
||||||
offset = 0
|
offset = 0
|
||||||
}: TProjectSnapshotListDTO) => {
|
}: TProjectSnapshotListDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
@ -147,13 +149,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
|
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
|
||||||
const snapshot = await snapshotDAL.findById(id);
|
const snapshot = await snapshotDAL.findById(id);
|
||||||
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
|
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
snapshot.projectId,
|
projectId: snapshot.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||||
@ -322,14 +325,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
|
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
|
||||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
snapshot.projectId,
|
projectId: snapshot.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretRollback
|
ProjectPermissionSub.SecretRollback
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@ -54,15 +54,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
@ -127,15 +127,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
certTemplate.projectId,
|
projectId: certTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
@ -196,15 +196,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
certificateTemplate.projectId,
|
projectId: certificateTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
@ -223,15 +223,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
certTemplate.projectId,
|
projectId: certTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
@ -65,15 +65,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TCreateSshCaDTO) => {
|
}: TCreateSshCaDTO) => {
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@ -118,15 +118,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@ -187,15 +187,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@ -226,15 +226,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@ -268,15 +268,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sshCertificateTemplate.projectId,
|
projectId: sshCertificateTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificates
|
ProjectPermissionSub.SshCertificates
|
||||||
@ -390,15 +390,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sshCertificateTemplate.projectId,
|
projectId: sshCertificateTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificates
|
ProjectPermissionSub.SshCertificates
|
||||||
@ -488,15 +488,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
@ -27,13 +28,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
projectDAL
|
projectDAL
|
||||||
}: TTrustedIpServiceFactoryDep) => {
|
}: TTrustedIpServiceFactoryDep) => {
|
||||||
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
|
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
const trustedIps = await trustedIpDAL.find({
|
const trustedIps = await trustedIpDAL.find({
|
||||||
projectId
|
projectId
|
||||||
@ -51,13 +53,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
comment,
|
comment,
|
||||||
isActive
|
isActive
|
||||||
}: TCreateIpDTO) => {
|
}: TCreateIpDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
@ -96,13 +99,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
comment,
|
comment,
|
||||||
trustedIpId
|
trustedIpId
|
||||||
}: TUpdateIpDTO) => {
|
}: TUpdateIpDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
@ -141,13 +145,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
trustedIpId
|
trustedIpId
|
||||||
}: TDeleteIpDTO) => {
|
}: TDeleteIpDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
|
@ -23,6 +23,8 @@ export const KeyStorePrefixes = {
|
|||||||
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const,
|
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
|
SecretSyncLock: (syncId: string) => `secret-sync-mutex-${syncId}` as const,
|
||||||
|
SecretSyncLastRunTimestamp: (syncId: string) => `secret-sync-last-run-${syncId}` as const,
|
||||||
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
||||||
`identity-access-token-status:${identityAccessTokenId}`,
|
`identity-access-token-status:${identityAccessTokenId}`,
|
||||||
ServiceTokenStatusUpdate: (serviceTokenId: string) => `service-token-status:${serviceTokenId}`
|
ServiceTokenStatusUpdate: (serviceTokenId: string) => `service-token-status:${serviceTokenId}`
|
||||||
@ -30,6 +32,7 @@ export const KeyStorePrefixes = {
|
|||||||
|
|
||||||
export const KeyStoreTtls = {
|
export const KeyStoreTtls = {
|
||||||
SetSyncSecretIntegrationLastRunTimestampInSeconds: 60,
|
SetSyncSecretIntegrationLastRunTimestampInSeconds: 60,
|
||||||
|
SetSecretSyncLastRunTimestampInSeconds: 60,
|
||||||
AccessTokenStatusUpdateInSeconds: 120
|
AccessTokenStatusUpdateInSeconds: 120
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps";
|
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
||||||
|
|
||||||
export const GROUPS = {
|
export const GROUPS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
@ -474,7 +476,7 @@ export const PROJECTS = {
|
|||||||
},
|
},
|
||||||
ADD_GROUP_TO_PROJECT: {
|
ADD_GROUP_TO_PROJECT: {
|
||||||
projectId: "The ID of the project to add the group to.",
|
projectId: "The ID of the project to add the group to.",
|
||||||
groupId: "The ID of the group to add to the project.",
|
groupIdOrName: "The ID or name of the group to add to the project.",
|
||||||
role: "The role for the group to assume in the project."
|
role: "The role for the group to assume in the project."
|
||||||
},
|
},
|
||||||
UPDATE_GROUP_IN_PROJECT: {
|
UPDATE_GROUP_IN_PROJECT: {
|
||||||
@ -686,7 +688,9 @@ export const RAW_SECRETS = {
|
|||||||
environment: "The slug of the environment to list secrets from.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
secretPath: "The secret path to list secrets from.",
|
secretPath: "The secret path to list secrets from.",
|
||||||
includeImports: "Weather to include imported secrets or not.",
|
includeImports: "Weather to include imported secrets or not.",
|
||||||
tagSlugs: "The comma separated tag slugs to filter secrets."
|
tagSlugs: "The comma separated tag slugs to filter secrets.",
|
||||||
|
metadataFilter:
|
||||||
|
"The secret metadata key-value pairs to filter secrets by. When querying for multiple metadata pairs, the query is treated as an AND operation. Secret metadata format is key=value1,value=value2|key=value3,value=value4."
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
secretName: "The name of the secret to create.",
|
secretName: "The name of the secret to create.",
|
||||||
@ -741,6 +745,12 @@ export const RAW_SECRETS = {
|
|||||||
workspaceId: "The ID of the project where the secret is located.",
|
workspaceId: "The ID of the project where the secret is located.",
|
||||||
environment: "The slug of the environment where the the secret is located.",
|
environment: "The slug of the environment where the the secret is located.",
|
||||||
secretPath: "The folder path where the secret is located."
|
secretPath: "The folder path where the secret is located."
|
||||||
|
},
|
||||||
|
GET_ACCESS_LIST: {
|
||||||
|
secretName: "The name of the secret to get the access list for.",
|
||||||
|
workspaceId: "The ID of the project where the secret is located.",
|
||||||
|
environment: "The slug of the environment where the the secret is located.",
|
||||||
|
secretPath: "The folder path where the secret is located."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -820,6 +830,8 @@ export const AUDIT_LOGS = {
|
|||||||
projectId:
|
projectId:
|
||||||
"Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.",
|
"Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.",
|
||||||
eventType: "The type of the event to export.",
|
eventType: "The type of the event to export.",
|
||||||
|
secretPath:
|
||||||
|
"The path of the secret to query audit logs for. Note that the projectId parameter must also be provided.",
|
||||||
userAgentType: "Choose which consuming application to export audit logs for.",
|
userAgentType: "Choose which consuming application to export audit logs for.",
|
||||||
eventMetadata:
|
eventMetadata:
|
||||||
"Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.",
|
"Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.",
|
||||||
@ -1150,7 +1162,8 @@ export const INTEGRATION = {
|
|||||||
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
|
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
|
||||||
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
|
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
|
||||||
shouldEnableDelete: "The flag to enable deletion of secrets.",
|
shouldEnableDelete: "The flag to enable deletion of secrets.",
|
||||||
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy."
|
octopusDeployScopeValues: "Specifies the scope values to set on synced secrets to Octopus Deploy.",
|
||||||
|
metadataSyncMode: "The mode for syncing metadata to external system"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@ -1580,6 +1593,13 @@ export const KMS = {
|
|||||||
orderDirection: "The direction to order keys in.",
|
orderDirection: "The direction to order keys in.",
|
||||||
search: "The text string to filter key names by."
|
search: "The text string to filter key names by."
|
||||||
},
|
},
|
||||||
|
GET_KEY_BY_ID: {
|
||||||
|
keyId: "The ID of the KMS key to retrieve."
|
||||||
|
},
|
||||||
|
GET_KEY_BY_NAME: {
|
||||||
|
keyName: "The name of the KMS key to retrieve.",
|
||||||
|
projectId: "The ID of the project the key belongs to."
|
||||||
|
},
|
||||||
ENCRYPT: {
|
ENCRYPT: {
|
||||||
keyId: "The ID of the key to encrypt the data with.",
|
keyId: "The ID of the key to encrypt the data with.",
|
||||||
plaintext: "The plaintext to be encrypted (base64 encoded)."
|
plaintext: "The plaintext to be encrypted (base64 encoded)."
|
||||||
@ -1636,6 +1656,98 @@ export const AppConnections = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
DELETE: (app: AppConnection) => ({
|
DELETE: (app: AppConnection) => ({
|
||||||
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} connection to be deleted.`
|
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to be deleted.`
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SecretSyncs = {
|
||||||
|
LIST: (destination?: SecretSync) => ({
|
||||||
|
projectId: `The ID of the project to list ${destination ? SECRET_SYNC_NAME_MAP[destination] : "Secret"} Syncs from.`
|
||||||
|
}),
|
||||||
|
GET_BY_ID: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to retrieve.`
|
||||||
|
}),
|
||||||
|
GET_BY_NAME: (destination: SecretSync) => ({
|
||||||
|
syncName: `The name of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to retrieve.`,
|
||||||
|
projectId: `The ID of the project the ${SECRET_SYNC_NAME_MAP[destination]} Sync is associated with.`
|
||||||
|
}),
|
||||||
|
CREATE: (destination: SecretSync) => {
|
||||||
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
|
return {
|
||||||
|
name: `The name of the ${destinationName} Sync to create. Must be slug-friendly.`,
|
||||||
|
description: `An optional description for the ${destinationName} Sync.`,
|
||||||
|
projectId: "The ID of the project to create the sync in.",
|
||||||
|
environment: `The slug of the project environment to sync secrets from.`,
|
||||||
|
secretPath: `The folder path to sync secrets from.`,
|
||||||
|
connectionId: `The ID of the ${
|
||||||
|
APP_CONNECTION_NAME_MAP[SECRET_SYNC_CONNECTION_MAP[destination]]
|
||||||
|
} Connection to use for syncing.`,
|
||||||
|
isAutoSyncEnabled: `Whether secrets should be automatically synced when changes occur at the source location or not.`,
|
||||||
|
syncOptions: "Optional parameters to modify how secrets are synced."
|
||||||
|
};
|
||||||
|
},
|
||||||
|
UPDATE: (destination: SecretSync) => {
|
||||||
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
|
return {
|
||||||
|
syncId: `The ID of the ${destinationName} Sync to be updated.`,
|
||||||
|
connectionId: `The updated ID of the ${
|
||||||
|
APP_CONNECTION_NAME_MAP[SECRET_SYNC_CONNECTION_MAP[destination]]
|
||||||
|
} Connection to use for syncing.`,
|
||||||
|
name: `The updated name of the ${destinationName} Sync. Must be slug-friendly.`,
|
||||||
|
environment: `The updated slug of the project environment to sync secrets from.`,
|
||||||
|
secretPath: `The updated folder path to sync secrets from.`,
|
||||||
|
description: `The updated description of the ${destinationName} Sync.`,
|
||||||
|
isAutoSyncEnabled: `Whether secrets should be automatically synced when changes occur at the source location or not.`,
|
||||||
|
syncOptions: "Optional parameters to modify how secrets are synced."
|
||||||
|
};
|
||||||
|
},
|
||||||
|
DELETE: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to be deleted.`,
|
||||||
|
removeSecrets: `Whether previously synced secrets should be removed prior to deletion.`
|
||||||
|
}),
|
||||||
|
SYNC_SECRETS: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to trigger a sync for.`
|
||||||
|
}),
|
||||||
|
IMPORT_SECRETS: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to trigger importing secrets for.`,
|
||||||
|
importBehavior: `Specify whether Infisical should prioritize secret values from Infisical or ${SECRET_SYNC_NAME_MAP[destination]}.`
|
||||||
|
}),
|
||||||
|
REMOVE_SECRETS: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to trigger removing secrets for.`
|
||||||
|
}),
|
||||||
|
SYNC_OPTIONS: (destination: SecretSync) => {
|
||||||
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
|
return {
|
||||||
|
INITIAL_SYNC_BEHAVIOR: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
|
||||||
|
PREPEND_PREFIX: `Optionally prepend a prefix to your secrets' keys when syncing to ${destinationName}.`,
|
||||||
|
APPEND_SUFFIX: `Optionally append a suffix to your secrets' keys when syncing to ${destinationName}.`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
DESTINATION_CONFIG: {
|
||||||
|
AWS_PARAMETER_STORE: {
|
||||||
|
REGION: "The AWS region to sync secrets to.",
|
||||||
|
PATH: "The Parameter Store path to sync secrets to."
|
||||||
|
},
|
||||||
|
AWS_SECRETS_MANAGER: {
|
||||||
|
REGION: "The AWS region to sync secrets to.",
|
||||||
|
MAPPING_BEHAVIOR:
|
||||||
|
"How secrets from Infisical should be mapped to AWS Secrets Manager; one-to-one or many-to-one.",
|
||||||
|
SECRET_NAME: "The secret name in AWS Secrets Manager to sync to when using mapping behavior many-to-one."
|
||||||
|
},
|
||||||
|
GITHUB: {
|
||||||
|
ORG: "The name of the GitHub organization.",
|
||||||
|
OWNER: "The name of the GitHub account owner of the repository.",
|
||||||
|
REPO: "The name of the GitHub repository.",
|
||||||
|
ENV: "The name of the GitHub environment."
|
||||||
|
},
|
||||||
|
AZURE_KEY_VAULT: {
|
||||||
|
VAULT_BASE_URL:
|
||||||
|
"The base URL of the Azure Key Vault to sync secrets to. Example: https://example.vault.azure.net/"
|
||||||
|
},
|
||||||
|
AZURE_APP_CONFIGURATION: {
|
||||||
|
CONFIGURATION_URL:
|
||||||
|
"The URL of the Azure App Configuration to sync secrets to. Example: https://example.azconfig.io/",
|
||||||
|
LABEL: "An optional label to assign to secrets created in Azure App Configuration."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -157,6 +157,8 @@ const envSchema = z
|
|||||||
INFISICAL_CLOUD: zodStrBool.default("false"),
|
INFISICAL_CLOUD: zodStrBool.default("false"),
|
||||||
MAINTENANCE_MODE: zodStrBool.default("false"),
|
MAINTENANCE_MODE: zodStrBool.default("false"),
|
||||||
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
||||||
|
CAPTCHA_SITE_KEY: zpStr(z.string().optional()),
|
||||||
|
INTERCOM_ID: zpStr(z.string().optional()),
|
||||||
|
|
||||||
// TELEMETRY
|
// TELEMETRY
|
||||||
OTEL_TELEMETRY_COLLECTION_ENABLED: zodStrBool.default("false"),
|
OTEL_TELEMETRY_COLLECTION_ENABLED: zodStrBool.default("false"),
|
||||||
@ -197,7 +199,36 @@ const envSchema = z
|
|||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET: zpStr(z.string().optional()),
|
||||||
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||||
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
|
||||||
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional())
|
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
// gcp app
|
||||||
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
// azure app
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_ID: zpStr(z.string().optional()),
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_SECRET: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
/* CORS ----------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS: zpStr(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return undefined;
|
||||||
|
return JSON.parse(val) as string[];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
CORS_ALLOWED_HEADERS: zpStr(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return undefined;
|
||||||
|
return JSON.parse(val) as string[];
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
// To ensure that basic encryption is always possible.
|
// To ensure that basic encryption is always possible.
|
||||||
.refine(
|
.refine(
|
||||||
|
@ -116,7 +116,7 @@ export const decryptAsymmetric = ({ ciphertext, nonce, publicKey, privateKey }:
|
|||||||
|
|
||||||
export const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64");
|
export const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64");
|
||||||
|
|
||||||
export const generateHash = (value: string) => crypto.createHash("sha256").update(value).digest("hex");
|
export const generateHash = (value: string | Buffer) => crypto.createHash("sha256").update(value).digest("hex");
|
||||||
|
|
||||||
export const generateAsymmetricKeyPair = () => {
|
export const generateAsymmetricKeyPair = () => {
|
||||||
const pair = nacl.box.keyPair();
|
const pair = nacl.box.keyPair();
|
||||||
|
4
backend/src/lib/error-codes/database.ts
Normal file
4
backend/src/lib/error-codes/database.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum DatabaseErrorCode {
|
||||||
|
ForeignKeyViolation = "23503",
|
||||||
|
UniqueViolation = "23505"
|
||||||
|
}
|
1
backend/src/lib/error-codes/index.ts
Normal file
1
backend/src/lib/error-codes/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./database";
|
@ -7,6 +7,7 @@ import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
|||||||
|
|
||||||
export * from "./connection";
|
export * from "./connection";
|
||||||
export * from "./join";
|
export * from "./join";
|
||||||
|
export * from "./prependTableNameToFindFilter";
|
||||||
export * from "./select";
|
export * from "./select";
|
||||||
|
|
||||||
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
||||||
|
13
backend/src/lib/knex/prependTableNameToFindFilter.ts
Normal file
13
backend/src/lib/knex/prependTableNameToFindFilter.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { buildFindFilter } from "@app/lib/knex/index";
|
||||||
|
|
||||||
|
type TFindFilterParameters = Parameters<typeof buildFindFilter<object>>[0];
|
||||||
|
|
||||||
|
export const prependTableNameToFindFilter = (tableName: TableName, filterObj: object): TFindFilterParameters =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(filterObj).map(([key, value]) =>
|
||||||
|
key.startsWith("$")
|
||||||
|
? [key, prependTableNameToFindFilter(tableName, value as object)]
|
||||||
|
: [`${tableName}.${key}`, value]
|
||||||
|
)
|
||||||
|
);
|
@ -1,3 +1,4 @@
|
|||||||
export { isDisposableEmail } from "./validate-email";
|
export { isDisposableEmail } from "./validate-email";
|
||||||
export { isValidFolderName, isValidSecretPath } from "./validate-folder-name";
|
export { isValidFolderName, isValidSecretPath } from "./validate-folder-name";
|
||||||
export { blockLocalAndPrivateIpAddresses } from "./validate-url";
|
export { blockLocalAndPrivateIpAddresses } from "./validate-url";
|
||||||
|
export { isUuidV4 } from "./validate-uuid";
|
||||||
|
3
backend/src/lib/validator/validate-uuid.ts
Normal file
3
backend/src/lib/validator/validate-uuid.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const isUuidV4 = (uuid: string) => z.string().uuid().safeParse(uuid).success;
|
@ -15,6 +15,12 @@ import {
|
|||||||
TIntegrationSyncPayload,
|
TIntegrationSyncPayload,
|
||||||
TSyncSecretsDTO
|
TSyncSecretsDTO
|
||||||
} from "@app/services/secret/secret-types";
|
} from "@app/services/secret/secret-types";
|
||||||
|
import {
|
||||||
|
TQueueSecretSyncImportSecretsByIdDTO,
|
||||||
|
TQueueSecretSyncRemoveSecretsByIdDTO,
|
||||||
|
TQueueSecretSyncSyncSecretsByIdDTO,
|
||||||
|
TQueueSendSecretSyncActionFailedNotificationsDTO
|
||||||
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
export enum QueueName {
|
export enum QueueName {
|
||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
@ -36,7 +42,8 @@ export enum QueueName {
|
|||||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
AccessTokenStatusUpdate = "access-token-status-update",
|
AccessTokenStatusUpdate = "access-token-status-update",
|
||||||
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source",
|
||||||
|
AppConnectionSecretSync = "app-connection-secret-sync"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueJobs {
|
export enum QueueJobs {
|
||||||
@ -61,7 +68,11 @@ export enum QueueJobs {
|
|||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
||||||
ServiceTokenStatusUpdate = "service-token-status-update",
|
ServiceTokenStatusUpdate = "service-token-status-update",
|
||||||
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source",
|
||||||
|
SecretSyncSyncSecrets = "secret-sync-sync-secrets",
|
||||||
|
SecretSyncImportSecrets = "secret-sync-import-secrets",
|
||||||
|
SecretSyncRemoveSecrets = "secret-sync-remove-secrets",
|
||||||
|
SecretSyncSendActionFailedNotifications = "secret-sync-send-action-failed-notifications"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@ -184,6 +195,23 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
[QueueName.AppConnectionSecretSync]:
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncSyncSecrets;
|
||||||
|
payload: TQueueSecretSyncSyncSecretsByIdDTO;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncImportSecrets;
|
||||||
|
payload: TQueueSecretSyncImportSecretsByIdDTO;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncRemoveSecrets;
|
||||||
|
payload: TQueueSecretSyncRemoveSecretsByIdDTO;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncSendActionFailedNotifications;
|
||||||
|
payload: TQueueSendSecretSyncActionFailedNotificationsDTO;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
@ -27,10 +27,10 @@ import { globalRateLimiterCfg } from "./config/rateLimiter";
|
|||||||
import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas";
|
import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas";
|
||||||
import { apiMetrics } from "./plugins/api-metrics";
|
import { apiMetrics } from "./plugins/api-metrics";
|
||||||
import { fastifyErrHandler } from "./plugins/error-handler";
|
import { fastifyErrHandler } from "./plugins/error-handler";
|
||||||
import { registerExternalNextjs } from "./plugins/external-nextjs";
|
|
||||||
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
|
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
|
||||||
import { fastifyIp } from "./plugins/ip";
|
import { fastifyIp } from "./plugins/ip";
|
||||||
import { maintenanceMode } from "./plugins/maintenanceMode";
|
import { maintenanceMode } from "./plugins/maintenanceMode";
|
||||||
|
import { registerServeUI } from "./plugins/serve-ui";
|
||||||
import { fastifySwagger } from "./plugins/swagger";
|
import { fastifySwagger } from "./plugins/swagger";
|
||||||
import { registerRoutes } from "./routes";
|
import { registerRoutes } from "./routes";
|
||||||
|
|
||||||
@ -87,7 +87,16 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
|
|||||||
|
|
||||||
await server.register<FastifyCorsOptions>(cors, {
|
await server.register<FastifyCorsOptions>(cors, {
|
||||||
credentials: true,
|
credentials: true,
|
||||||
origin: appCfg.SITE_URL || true
|
...(appCfg.CORS_ALLOWED_ORIGINS?.length
|
||||||
|
? {
|
||||||
|
origin: [...appCfg.CORS_ALLOWED_ORIGINS, ...(appCfg.SITE_URL ? [appCfg.SITE_URL] : [])]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
origin: appCfg.SITE_URL || true
|
||||||
|
}),
|
||||||
|
...(appCfg.CORS_ALLOWED_HEADERS?.length && {
|
||||||
|
allowedHeaders: appCfg.CORS_ALLOWED_HEADERS
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.register(addErrorsToResponseSchemas);
|
await server.register(addErrorsToResponseSchemas);
|
||||||
@ -120,13 +129,10 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
|
|||||||
|
|
||||||
await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule });
|
await server.register(registerRoutes, { smtp, queue, db, auditLogDb, keyStore, hsmModule });
|
||||||
|
|
||||||
if (appCfg.isProductionMode) {
|
await server.register(registerServeUI, {
|
||||||
await server.register(registerExternalNextjs, {
|
standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED,
|
||||||
standaloneMode: appCfg.STANDALONE_MODE || IS_PACKAGED,
|
dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../")
|
||||||
dir: path.join(__dirname, IS_PACKAGED ? "../../../" : "../../"),
|
});
|
||||||
port: appCfg.PORT
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await server.ready();
|
await server.ready();
|
||||||
server.swagger();
|
server.swagger();
|
||||||
|
@ -32,13 +32,21 @@ export const getUserAgentType = (userAgent: string | undefined) => {
|
|||||||
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
|
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
|
||||||
server.decorateRequest("auditLogInfo", null);
|
server.decorateRequest("auditLogInfo", null);
|
||||||
server.addHook("onRequest", async (req) => {
|
server.addHook("onRequest", async (req) => {
|
||||||
if (!req.auth) return;
|
|
||||||
const userAgent = req.headers["user-agent"] ?? "";
|
const userAgent = req.headers["user-agent"] ?? "";
|
||||||
const payload = {
|
const payload = {
|
||||||
ipAddress: req.realIp,
|
ipAddress: req.realIp,
|
||||||
userAgent,
|
userAgent,
|
||||||
userAgentType: getUserAgentType(userAgent)
|
userAgentType: getUserAgentType(userAgent)
|
||||||
} as typeof req.auditLogInfo;
|
} as typeof req.auditLogInfo;
|
||||||
|
|
||||||
|
if (!req.auth) {
|
||||||
|
payload.actor = {
|
||||||
|
type: ActorType.UNKNOWN_USER,
|
||||||
|
metadata: {}
|
||||||
|
};
|
||||||
|
req.auditLogInfo = payload;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (req.auth.actor === ActorType.USER) {
|
if (req.auth.actor === ActorType.USER) {
|
||||||
payload.actor = {
|
payload.actor = {
|
||||||
type: ActorType.USER,
|
type: ActorType.USER,
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
// this plugins allows to run infisical in standalone mode
|
|
||||||
// standalone mode = infisical backend and nextjs frontend in one server
|
|
||||||
// this way users don't need to deploy two things
|
|
||||||
import path from "node:path";
|
|
||||||
|
|
||||||
import { IS_PACKAGED } from "@app/lib/config/env";
|
|
||||||
|
|
||||||
// to enabled this u need to set standalone mode to true
|
|
||||||
export const registerExternalNextjs = async (
|
|
||||||
server: FastifyZodProvider,
|
|
||||||
{
|
|
||||||
standaloneMode,
|
|
||||||
dir,
|
|
||||||
port
|
|
||||||
}: {
|
|
||||||
standaloneMode?: boolean;
|
|
||||||
dir: string;
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
if (standaloneMode) {
|
|
||||||
const frontendName = IS_PACKAGED ? "frontend" : "frontend-build";
|
|
||||||
const nextJsBuildPath = path.join(dir, frontendName);
|
|
||||||
|
|
||||||
const { default: conf } = (await import(
|
|
||||||
path.join(dir, `${frontendName}/.next/required-server-files.json`),
|
|
||||||
// @ts-expect-error type
|
|
||||||
{
|
|
||||||
assert: { type: "json" }
|
|
||||||
}
|
|
||||||
)) as { default: { config: string } };
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
let NextServer: any;
|
|
||||||
|
|
||||||
if (!IS_PACKAGED) {
|
|
||||||
/* eslint-disable */
|
|
||||||
const { default: nextServer } = (
|
|
||||||
await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`))
|
|
||||||
).default;
|
|
||||||
|
|
||||||
NextServer = nextServer;
|
|
||||||
} else {
|
|
||||||
/* eslint-disable */
|
|
||||||
const nextServer = await import(path.join(dir, `${frontendName}/node_modules/next/dist/server/next-server.js`));
|
|
||||||
|
|
||||||
NextServer = nextServer.default;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextApp = new NextServer({
|
|
||||||
dev: false,
|
|
||||||
dir: nextJsBuildPath,
|
|
||||||
port,
|
|
||||||
conf: conf.config,
|
|
||||||
hostname: "local",
|
|
||||||
customServer: false
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: ["GET", "PUT", "PATCH", "POST", "DELETE"],
|
|
||||||
url: "/*",
|
|
||||||
schema: {
|
|
||||||
hide: true
|
|
||||||
},
|
|
||||||
handler: (req, res) =>
|
|
||||||
nextApp
|
|
||||||
.getRequestHandler()(req.raw, res.raw)
|
|
||||||
.then(() => {
|
|
||||||
res.hijack();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
server.addHook("onClose", () => nextApp.close());
|
|
||||||
await nextApp.prepare();
|
|
||||||
/* eslint-enable */
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { EmitterWebhookEventName } from "@octokit/webhooks/dist-types/types";
|
||||||
import { PushEvent } from "@octokit/webhooks-types";
|
import { PushEvent } from "@octokit/webhooks-types";
|
||||||
import { Probot } from "probot";
|
import { Probot } from "probot";
|
||||||
import SmeeClient from "smee-client";
|
import SmeeClient from "smee-client";
|
||||||
@ -54,14 +55,14 @@ export const registerSecretScannerGhApp = async (server: FastifyZodProvider) =>
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
const eventName = req.headers["x-github-event"];
|
const eventName = req.headers["x-github-event"] as EmitterWebhookEventName;
|
||||||
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
|
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
|
||||||
const id = req.headers["x-github-delivery"] as string;
|
const id = req.headers["x-github-delivery"] as string;
|
||||||
|
|
||||||
await probot.webhooks.verifyAndReceive({
|
await probot.webhooks.verifyAndReceive({
|
||||||
id,
|
id,
|
||||||
// @ts-expect-error type
|
|
||||||
name: eventName,
|
name: eventName,
|
||||||
payload: req.body as string,
|
payload: JSON.stringify(req.body),
|
||||||
signature: signatureSHA256
|
signature: signatureSHA256
|
||||||
});
|
});
|
||||||
void res.send("ok");
|
void res.send("ok");
|
||||||
|
64
backend/src/server/plugins/serve-ui.ts
Normal file
64
backend/src/server/plugins/serve-ui.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import staticServe from "@fastify/static";
|
||||||
|
|
||||||
|
import { getConfig, IS_PACKAGED } from "@app/lib/config/env";
|
||||||
|
|
||||||
|
// to enabled this u need to set standalone mode to true
|
||||||
|
export const registerServeUI = async (
|
||||||
|
server: FastifyZodProvider,
|
||||||
|
{
|
||||||
|
standaloneMode,
|
||||||
|
dir
|
||||||
|
}: {
|
||||||
|
standaloneMode?: boolean;
|
||||||
|
dir: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
// use this only for frontend runtime static non-sensitive configuration in standalone mode
|
||||||
|
// that app needs before loading like posthog dsn key
|
||||||
|
// for most of the other usecase use server config
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/runtime-ui-env.js",
|
||||||
|
schema: {
|
||||||
|
hide: true
|
||||||
|
},
|
||||||
|
handler: (_req, res) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
void res.type("application/javascript");
|
||||||
|
const config = {
|
||||||
|
CAPTCHA_SITE_KEY: appCfg.CAPTCHA_SITE_KEY,
|
||||||
|
POSTHOG_API_KEY: appCfg.POSTHOG_PROJECT_API_KEY,
|
||||||
|
INTERCOM_ID: appCfg.INTERCOM_ID,
|
||||||
|
TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED
|
||||||
|
};
|
||||||
|
const js = `window.__INFISICAL_RUNTIME_ENV__ = Object.freeze(${JSON.stringify(config)});`;
|
||||||
|
void res.send(js);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (standaloneMode) {
|
||||||
|
const frontendName = IS_PACKAGED ? "frontend" : "frontend-build";
|
||||||
|
const frontendPath = path.join(dir, frontendName);
|
||||||
|
await server.register(staticServe, {
|
||||||
|
root: frontendPath,
|
||||||
|
wildcard: false
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/*",
|
||||||
|
schema: {
|
||||||
|
hide: true
|
||||||
|
},
|
||||||
|
handler: (request, reply) => {
|
||||||
|
if (request.url.startsWith("/api")) {
|
||||||
|
reply.callNotFound();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void reply.sendFile("index.html");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user