mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-15 09:42:14 +00:00
Compare commits
568 Commits
misc/resol
...
doc/made-e
Author | SHA1 | Date | |
---|---|---|---|
53075d503a | |||
e73d3f87f3 | |||
b53607f8e4 | |||
8f79d3210a | |||
68b1984a76 | |||
ba45e83880 | |||
f0938330a7 | |||
e1bb0ac3ad | |||
f54d930de2 | |||
288f47f4bd | |||
b090ebfd41 | |||
67773bff5e | |||
8ef1cfda04 | |||
2a79d5ba36 | |||
0cb95f36ff | |||
4a1dfda41f | |||
c238b7b6ae | |||
83d314ba32 | |||
b94a0ffa6c | |||
b60e404243 | |||
10120e1825 | |||
31e66c18e7 | |||
fb06f5a3bc | |||
1515dd8a71 | |||
da18a12648 | |||
49a0d3cec6 | |||
e821a11271 | |||
af4428acec | |||
61370cc6b2 | |||
cf3b2ebbca | |||
e970cc0f47 | |||
bd5cd03aeb | |||
c46e4d7fc1 | |||
1f3896231a | |||
4323f6fa8f | |||
65db91d491 | |||
ae5b57f69f | |||
b717de4f78 | |||
1216d218c1 | |||
209004ec6d | |||
c865d12849 | |||
c921c28185 | |||
3647943c80 | |||
4bf5381060 | |||
a10c358f83 | |||
d3c63b5699 | |||
c64334462f | |||
c497e19b99 | |||
2aeae616de | |||
e0e21530e2 | |||
7b4b802a9b | |||
95cf3cf6cc | |||
d021b414cf | |||
bed75c36dd | |||
04cb499f0f | |||
189a610f52 | |||
00039ba0e4 | |||
abdcb95a8f | |||
47ea4ae9a6 | |||
903b2c3dc6 | |||
c795b3b3a0 | |||
0d8ff1828e | |||
30d6af7760 | |||
44b42359da | |||
38373722e3 | |||
7ec68ca9a1 | |||
a49d5b121b | |||
901ff7a605 | |||
ba4aa15c92 | |||
a00103aa1e | |||
0c17cc3577 | |||
51d84a47b9 | |||
d529670a52 | |||
ed0463e3e4 | |||
20db0a255c | |||
6fe1d77375 | |||
f90855e7a5 | |||
97f5c33aea | |||
34c2200269 | |||
69925721cc | |||
0961d2f1c6 | |||
b9bd518aa6 | |||
692c9b5d9c | |||
32046ca880 | |||
590dbbcb04 | |||
27d2af4979 | |||
a1e6c6f7d5 | |||
cc94a3366a | |||
6a6c084b8a | |||
7baa3b4cbe | |||
6cab7504fc | |||
ca3d8c5594 | |||
28a2a6c41a | |||
05efd95472 | |||
fa31f87479 | |||
b176f13392 | |||
f4384bb01e | |||
856c2423be | |||
4570de09ae | |||
4feff5b4ca | |||
6081e2927e | |||
0b42f29916 | |||
b60d0992f4 | |||
a8a68f600c | |||
742f5f6621 | |||
f993e4aa5c | |||
bb6416acb7 | |||
f3cd7efe0e | |||
2b16c19b70 | |||
943b540383 | |||
e180021aa6 | |||
8e08c443ad | |||
dae26daeeb | |||
170f8d9add | |||
8d41ef198a | |||
69d60a227a | |||
c8eefcfbf9 | |||
53cec754cc | |||
5db3e177eb | |||
3fcc3ccff4 | |||
df07d7b6d7 | |||
28a655bef1 | |||
5f2cd04f46 | |||
897ce1f267 | |||
6afc17b84b | |||
9017a5e838 | |||
cb8e4d884e | |||
16807c3dd6 | |||
61791e385c | |||
bbd7bfb0f5 | |||
4de8c48b2c | |||
a4bbe2c612 | |||
541a2e7d05 | |||
ea4e51d826 | |||
3bc920c593 | |||
f4244c6d4d | |||
e1b9965f01 | |||
705b4f7513 | |||
df38c761ad | |||
32a84471f2 | |||
fc4a20caf2 | |||
ea14df2cbd | |||
6bd6cac366 | |||
45294253aa | |||
635fbdc80b | |||
d20c48b7cf | |||
1fc18fe23b | |||
99403e122b | |||
5176e70437 | |||
82b2b0af97 | |||
e313c866a2 | |||
2d81606049 | |||
718f4ef129 | |||
a42f3b3763 | |||
f7d882a6fc | |||
385afdfcf8 | |||
281d703cc3 | |||
6f56ed5474 | |||
809e4eeba1 | |||
254446c895 | |||
bb52e2beb4 | |||
2739b08e59 | |||
ba5e877a3b | |||
d2752216f6 | |||
d91fb0db02 | |||
556e4d62c4 | |||
4892eea009 | |||
09c6fcb73b | |||
79181a1e3d | |||
bb934ef7b1 | |||
cd9316537d | |||
942e5f2f65 | |||
353d231a4e | |||
68e05b7198 | |||
4f998e3940 | |||
1248840dc8 | |||
64c8125e4b | |||
1690a9429c | |||
c109fbab3e | |||
15fb01089b | |||
6f4be3e25a | |||
8d33647739 | |||
d1c142e5b1 | |||
bb1cad0c5b | |||
2a1cfe15b4 | |||
881d70bc64 | |||
14c1b4f07b | |||
3028bdd424 | |||
902a0b0ed4 | |||
ba92192537 | |||
26ed8df73c | |||
c1decab912 | |||
216c073290 | |||
8626bce632 | |||
c5a2b0321f | |||
5af53d3398 | |||
8da8c6a66c | |||
88a4390ea0 | |||
c70d0a577c | |||
1070954bdd | |||
587a4a1120 | |||
cc689d3178 | |||
e6848828f2 | |||
c8b93e4467 | |||
0bca24bb00 | |||
c563ada50f | |||
26d1616e22 | |||
5fd071d1de | |||
a6ac78356b | |||
e4a2137991 | |||
9721d7a15e | |||
93db5c4555 | |||
ad4393fdef | |||
cd06e4e7f3 | |||
711a4179ce | |||
b4a2a477d3 | |||
8e53a1b171 | |||
71af463ad8 | |||
7abd18b11c | |||
1aee50a751 | |||
0f23b7e1d3 | |||
e9b37a1f98 | |||
33193a47ae | |||
43fded2350 | |||
7b6f4d810d | |||
1ad286ca87 | |||
be7c11a3f5 | |||
b97bbe5beb | |||
cf5260b383 | |||
13e0dd8e0f | |||
7f9150e60e | |||
995f0360fb | |||
ecab69a7ab | |||
cca36ab106 | |||
76311a1b5f | |||
55a6740714 | |||
a0490d0fde | |||
78e41a51c0 | |||
8414f04e94 | |||
79e414ea9f | |||
83772c1770 | |||
09928efba3 | |||
48eb4e772f | |||
7467a05fc4 | |||
afba636850 | |||
96cc315762 | |||
e95d7e55c1 | |||
520c068ac4 | |||
cf330777ed | |||
c1eae42b26 | |||
9f0d7c6d11 | |||
683e3dd7be | |||
46ca3856b3 | |||
891cb06de0 | |||
aff7481fbc | |||
e7c1a4d4a0 | |||
27f9628dc5 | |||
1866ce4240 | |||
e6b6de5e8e | |||
02e8f20cbf | |||
9184ec0765 | |||
1d55c7bcb0 | |||
96cffd6196 | |||
5bb2866b28 | |||
7a7841e487 | |||
b0819ee592 | |||
b4689bed17 | |||
bfd24ea938 | |||
cea1a5e7ea | |||
8d32ca2fb6 | |||
d468067d43 | |||
3a640d6cf8 | |||
8fc85105a9 | |||
48bd354bae | |||
6e1dc7375c | |||
164627139e | |||
f7c962425c | |||
d92979d50e | |||
021dbf3558 | |||
29060ffc9e | |||
d9c7724857 | |||
9063787772 | |||
c821bc0e14 | |||
83eed831da | |||
5c8d6157d7 | |||
5d78b6941d | |||
1d09d4cdfd | |||
9877444117 | |||
6f2ae344a7 | |||
549d388f59 | |||
e2caa98c74 | |||
6bb41913bf | |||
844a4ebc02 | |||
b37f780c4c | |||
6e7997b1bd | |||
e210a6a24f | |||
b950bf0cf7 | |||
a53d0b2334 | |||
ab88e6c414 | |||
49eb6d6474 | |||
05d7e26f8b | |||
6a156371c0 | |||
8435b20178 | |||
7d7fcd0db6 | |||
b5182550da | |||
3e0ae5765f | |||
f7ef86eb11 | |||
acf9a488ac | |||
4a06e3e712 | |||
b7b0e60b1d | |||
d4747abba8 | |||
641860cdb8 | |||
36ac1f47ca | |||
643d13b0ec | |||
ef2816b2ee | |||
9e314d7a09 | |||
8eab27d752 | |||
b563c4030b | |||
761a0f121c | |||
70400ef369 | |||
9aecfe77ad | |||
cedeb1ce27 | |||
0e75a8f6d7 | |||
a5b030c4a7 | |||
4009580cf2 | |||
64869ea8e0 | |||
ffc1b1ec1c | |||
880a689376 | |||
3709f31b5a | |||
6b6fd9735c | |||
a57d1f1c9a | |||
6c06de6da4 | |||
0c9e979fb8 | |||
32fc254ae1 | |||
69d813887b | |||
80be054425 | |||
4d032cfbfa | |||
d41011e056 | |||
d918f3ecdf | |||
7e5c3e8163 | |||
cb347aa16a | |||
88a7cc3068 | |||
4ddfb05134 | |||
7bb0ec0111 | |||
31af4a4602 | |||
dd46a21035 | |||
26a5d74b14 | |||
7e9389cb26 | |||
eda57881ec | |||
5eafdba6c8 | |||
9c4bb79472 | |||
937b0c0a7c | |||
cb132f4c65 | |||
4caa77e28a | |||
547be80dcf | |||
2cbae96c9a | |||
553d51e5b3 | |||
16e0a441ae | |||
d6c0941fa9 | |||
7cbd254f06 | |||
4b83b92725 | |||
fe72f034c1 | |||
dbe771dba0 | |||
273fd6c98f | |||
d5f4ce4376 | |||
6803553b21 | |||
18aac6508b | |||
1c8299054a | |||
85653a90d5 | |||
98b6373d6a | |||
1d97921c7c | |||
0d4164ea81 | |||
79bd8613d3 | |||
8deea21a83 | |||
3b3c2be933 | |||
c041e44399 | |||
c1aeb04174 | |||
3f3c0aab0f | |||
b740e8c900 | |||
4416b11094 | |||
d8169a866d | |||
7239158e7f | |||
879ef2c178 | |||
8777cfe680 | |||
2b630f75aa | |||
91cee20cc8 | |||
4249ec6030 | |||
e7a95e6af2 | |||
a9f04a3c1f | |||
3d380710ee | |||
2177ec6bcc | |||
fefe2d1de1 | |||
3f3e41282d | |||
c14f94177a | |||
ceb741955d | |||
f5bc4e1b5f | |||
06900b9c99 | |||
d71cb96adf | |||
61ebec25b3 | |||
57320c51fb | |||
4aa9cd0f72 | |||
ea39ef9269 | |||
15749a1f52 | |||
9e9aff129e | |||
4ac487c974 | |||
2e50072caa | |||
2bd170df7d | |||
938a7b7e72 | |||
af864b456b | |||
a30e3874cd | |||
de886f8dd0 | |||
b3db29ac37 | |||
070eb2aacd | |||
e619cfa313 | |||
c3038e3ca1 | |||
ce1db38afd | |||
0fa6b7a08a | |||
29c5bf5491 | |||
4d711ae149 | |||
ff0e7feeee | |||
9dd675ff98 | |||
8fd3e50d04 | |||
391ed0723e | |||
84af8e708e | |||
b39b5bd1a1 | |||
b3d9d91b52 | |||
5ad4061881 | |||
f29862eaf2 | |||
7cb174b644 | |||
bf00d16c80 | |||
e30a0fe8be | |||
6e6f0252ae | |||
2348df7a4d | |||
962cf67dfb | |||
32627c20c4 | |||
c50f8fd78c | |||
1cb4dc9e84 | |||
977ce09245 | |||
08d7dead8c | |||
a30e06e392 | |||
23f3f09cb6 | |||
5cd0f665fa | |||
443e76c1df | |||
4ea22b6761 | |||
ae7e0d0963 | |||
ed6c6d54c0 | |||
428ff5186f | |||
d07b0d20d6 | |||
8e373fe9bf | |||
28087cdcc4 | |||
dcef49950d | |||
1e5d567ef7 | |||
d09c320150 | |||
229599b8de | |||
02eea4d886 | |||
d12144a7e7 | |||
5fa69235d1 | |||
7dd9337b1c | |||
f9eaee4dbc | |||
cd3a64f3e7 | |||
121254f98d | |||
1591c1dbac | |||
3c59d288c4 | |||
632b775d7f | |||
d66da3d770 | |||
da43f405c4 | |||
d5c0abbc3b | |||
7a642e7634 | |||
de686acc23 | |||
b359f4278e | |||
29d76c1deb | |||
6ba1012f5b | |||
4abb3ef348 | |||
73e764474d | |||
7eb5689b4c | |||
5d945f432d | |||
1066710c4f | |||
b64d4e57c4 | |||
bd860e6c5a | |||
37137b8c68 | |||
8b10cf863d | |||
eb45bed7d9 | |||
1ee65205a0 | |||
f41272d4df | |||
8bf4df9f27 | |||
037a8f2ebb | |||
14bc436283 | |||
a108c7dde1 | |||
54ccd73d2a | |||
729ca7b6d6 | |||
754db67f11 | |||
f97756a07b | |||
22df51ab8e | |||
bff8f55ea2 | |||
2f17f5e7df | |||
72d2247bf2 | |||
4ecd4c0337 | |||
538613dd40 | |||
4c5c24f689 | |||
dead16a98a | |||
224368b172 | |||
3731459e99 | |||
dc055c11ab | |||
22878a035b | |||
2f2c9d4508 | |||
774017adbe | |||
f9d1d9c89f | |||
eb82fc0d9a | |||
e45585a909 | |||
6f0484f074 | |||
4ba529f22d | |||
5360fb033a | |||
27e14bcafe | |||
bc5003ae4c | |||
f544b39597 | |||
8381f52f1e | |||
aa96a833d7 | |||
53c64b759c | |||
74f2224c6b | |||
ecb5342a55 | |||
bcb657b81e | |||
ebe6b08cab | |||
43b14d0091 | |||
7127f6d1e1 | |||
20387cff35 | |||
997d7f22fc | |||
e1ecad2331 | |||
ce26a06129 | |||
7622cac07e | |||
a101602e0a | |||
ca63a7baa7 | |||
ff4f15c437 | |||
d6c2715852 | |||
fc386c0cbc | |||
263a88379f | |||
4b718b679a | |||
498b1109c9 | |||
b70bf4cadb | |||
d301f74feb | |||
454826fbb6 | |||
f464d7a096 | |||
cae9ace1ca | |||
8a5a295a01 | |||
95a4661787 | |||
7e9c846ba3 | |||
aed310b9ee | |||
c331af5345 | |||
d4dd684f32 | |||
1f6c33bdb8 | |||
a538e37a62 | |||
f3f87cfd84 | |||
2c57bd94fb | |||
869fcd6541 | |||
7b3e116bf8 | |||
0a95f6dc1d | |||
d19c856e9b | |||
ada0033bd0 | |||
6818c8730f | |||
8542ec8c3e | |||
c141b916d3 | |||
b09dddec1c | |||
1ae375188b | |||
22b954b657 | |||
1d6d424c91 | |||
c39ea130b1 | |||
5514508482 | |||
5921dcaa51 | |||
b2c62c4193 |
13
.github/workflows/build-binaries.yml
vendored
13
.github/workflows/build-binaries.yml
vendored
@ -14,7 +14,6 @@ defaults:
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x64, arm64]
|
||||
@ -24,6 +23,7 @@ jobs:
|
||||
target: node20-linux
|
||||
- os: win
|
||||
target: node20-win
|
||||
runs-on: ${{ (matrix.arch == 'arm64' && matrix.os == 'linux') && 'ubuntu24-arm64' || 'ubuntu-latest' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@ -49,9 +49,9 @@ jobs:
|
||||
- name: Package into node binary
|
||||
run: |
|
||||
if [ "${{ matrix.os }}" != "linux" ]; then
|
||||
pkg --no-bytecode --public-packages "*" --public --compress Brotli --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core-${{ matrix.os }}-${{ matrix.arch }} .
|
||||
else
|
||||
pkg --no-bytecode --public-packages "*" --public --compress Brotli --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
||||
pkg --no-bytecode --public-packages "*" --public --target ${{ matrix.target }}-${{ matrix.arch }} --output ./binary/infisical-core .
|
||||
fi
|
||||
|
||||
# Set up .deb package structure (Debian/Ubuntu only)
|
||||
@ -84,7 +84,12 @@ jobs:
|
||||
mv infisical-core.deb ./binary/infisical-core-${{matrix.arch}}.deb
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
- run: pip install --upgrade cloudsmith-cli
|
||||
with:
|
||||
python-version: "3.x" # Specify the Python version you need
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade cloudsmith-cli
|
||||
|
||||
# Publish .deb file to Cloudsmith (Debian/Ubuntu only)
|
||||
- name: Publish to Cloudsmith (Debian/Ubuntu)
|
||||
|
@ -22,14 +22,14 @@ jobs:
|
||||
# uncomment this when testing locally using nektos/act
|
||||
- uses: KengoTODA/actions-setup-docker-compose@v1
|
||||
if: ${{ env.ACT }}
|
||||
name: Install `docker-compose` for local simulations
|
||||
name: Install `docker compose` for local simulations
|
||||
with:
|
||||
version: "2.14.2"
|
||||
- name: 📦Build the latest image
|
||||
run: docker build --tag infisical-api .
|
||||
working-directory: backend
|
||||
- name: Start postgres and redis
|
||||
run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis
|
||||
run: touch .env && docker compose -f docker-compose.dev.yml up -d db redis
|
||||
- name: Start the server
|
||||
run: |
|
||||
echo "SECRET_SCANNING_GIT_APP_ID=793712" >> .env
|
||||
@ -72,6 +72,6 @@ jobs:
|
||||
run: oasdiff breaking https://app.infisical.com/api/docs/json http://localhost:4000/api/docs/json --fail-on ERR
|
||||
- name: cleanup
|
||||
run: |
|
||||
docker-compose -f "docker-compose.dev.yml" down
|
||||
docker compose -f "docker-compose.dev.yml" down
|
||||
docker stop infisical-api
|
||||
docker remove infisical-api
|
||||
|
6
.github/workflows/run-backend-tests.yml
vendored
6
.github/workflows/run-backend-tests.yml
vendored
@ -20,7 +20,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
- uses: KengoTODA/actions-setup-docker-compose@v1
|
||||
if: ${{ env.ACT }}
|
||||
name: Install `docker-compose` for local simulations
|
||||
name: Install `docker compose` for local simulations
|
||||
with:
|
||||
version: "2.14.2"
|
||||
- name: 🔧 Setup Node 20
|
||||
@ -33,7 +33,7 @@ jobs:
|
||||
run: npm install
|
||||
working-directory: backend
|
||||
- name: Start postgres and redis
|
||||
run: touch .env && docker-compose -f docker-compose.dev.yml up -d db redis
|
||||
run: touch .env && docker compose -f docker-compose.dev.yml up -d db redis
|
||||
- name: Start integration test
|
||||
run: npm run test:e2e
|
||||
working-directory: backend
|
||||
@ -44,4 +44,4 @@ jobs:
|
||||
ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218
|
||||
- name: cleanup
|
||||
run: |
|
||||
docker-compose -f "docker-compose.dev.yml" down
|
||||
docker compose -f "docker-compose.dev.yml" down
|
2
.github/workflows/run-cli-tests.yml
vendored
2
.github/workflows/run-cli-tests.yml
vendored
@ -50,6 +50,6 @@ jobs:
|
||||
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
||||
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
||||
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
||||
INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
||||
# INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
|
||||
|
||||
run: go test -v -count=1 ./test
|
||||
|
13
Makefile
13
Makefile
@ -15,3 +15,16 @@ up-prod:
|
||||
|
||||
down:
|
||||
docker compose -f docker-compose.dev.yml down
|
||||
|
||||
reviewable-ui:
|
||||
cd frontend && \
|
||||
npm run lint:fix && \
|
||||
npm run type:check
|
||||
|
||||
reviewable-api:
|
||||
cd backend && \
|
||||
npm run lint:fix && \
|
||||
npm run type:check
|
||||
|
||||
reviewable: reviewable-ui reviewable-api
|
||||
|
||||
|
4617
backend/package-lock.json
generated
4617
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -34,14 +34,14 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"dev": "tsx watch --clear-screen=false ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine",
|
||||
"dev:docker": "nodemon",
|
||||
"build": "tsup",
|
||||
"build": "tsup --sourcemap",
|
||||
"build:frontend": "npm run build --prefix ../frontend",
|
||||
"start": "node dist/main.mjs",
|
||||
"start": "node --enable-source-maps dist/main.mjs",
|
||||
"type:check": "tsc --noEmit",
|
||||
"lint:fix": "eslint --fix --ext js,ts ./src",
|
||||
"lint": "eslint 'src/**/*.ts'",
|
||||
"test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1",
|
||||
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=<value>",
|
||||
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
||||
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
||||
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
||||
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
||||
@ -78,6 +78,7 @@
|
||||
"@types/picomatch": "^2.3.3",
|
||||
"@types/prompt-sync": "^4.2.3",
|
||||
"@types/resolve": "^1.20.6",
|
||||
"@types/safe-regex": "^1.1.6",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||
"@typescript-eslint/parser": "^6.20.0",
|
||||
@ -121,10 +122,11 @@
|
||||
"@fastify/swagger": "^8.14.0",
|
||||
"@fastify/swagger-ui": "^2.1.0",
|
||||
"@node-saml/passport-saml": "^4.0.4",
|
||||
"@octokit/plugin-retry": "^5.0.5",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@octokit/webhooks-types": "^7.3.1",
|
||||
"@peculiar/asn1-schema": "^2.3.8",
|
||||
"@peculiar/x509": "^1.10.0",
|
||||
"@peculiar/x509": "^1.12.1",
|
||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
"@team-plain/typescript-sdk": "^4.6.1",
|
||||
@ -171,6 +173,7 @@
|
||||
"pino": "^8.16.2",
|
||||
"posthog-node": "^3.6.2",
|
||||
"probot": "^13.0.0",
|
||||
"safe-regex": "^2.1.1",
|
||||
"smee-client": "^2.0.0",
|
||||
"tedious": "^18.2.1",
|
||||
"tweetnacl": "^1.0.3",
|
||||
|
@ -7,14 +7,33 @@ const prompt = promptSync({
|
||||
sigint: true
|
||||
});
|
||||
|
||||
type ComponentType = 1 | 2 | 3;
|
||||
|
||||
console.log(`
|
||||
Component List
|
||||
--------------
|
||||
0. Exit
|
||||
1. Service component
|
||||
2. DAL component
|
||||
3. Router component
|
||||
`);
|
||||
const componentType = parseInt(prompt("Select a component: "), 10);
|
||||
|
||||
function getComponentType(): ComponentType {
|
||||
while (true) {
|
||||
const input = prompt("Select a component (0-3): ");
|
||||
const componentType = parseInt(input, 10);
|
||||
|
||||
if (componentType === 0) {
|
||||
console.log("Exiting the program. Goodbye!");
|
||||
process.exit(0);
|
||||
} else if (componentType === 1 || componentType === 2 || componentType === 3) {
|
||||
return componentType;
|
||||
} else {
|
||||
console.log("Invalid input. Please enter 0, 1, 2, or 3.");
|
||||
}
|
||||
}
|
||||
}
|
||||
const componentType = getComponentType();
|
||||
|
||||
if (componentType === 1) {
|
||||
const componentName = prompt("Enter service name: ");
|
||||
|
10
backend/src/@types/fastify.d.ts
vendored
10
backend/src/@types/fastify.d.ts
vendored
@ -18,6 +18,7 @@ import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-ser
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
||||
import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
|
||||
import { RateLimitConfiguration } from "@app/ee/services/rate-limit/rate-limit-types";
|
||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
@ -35,6 +36,7 @@ import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
||||
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||
@ -50,6 +52,9 @@ import { TIntegrationServiceFactory } from "@app/services/integration/integratio
|
||||
import { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
|
||||
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
||||
import { TOrgServiceFactory } from "@app/services/org/org-service";
|
||||
import { TOrgAdminServiceFactory } from "@app/services/org-admin/org-admin-service";
|
||||
import { TPkiAlertServiceFactory } from "@app/services/pki-alert/pki-alert-service";
|
||||
import { TPkiCollectionServiceFactory } from "@app/services/pki-collection/pki-collection-service";
|
||||
import { TProjectServiceFactory } from "@app/services/project/project-service";
|
||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
||||
@ -88,6 +93,7 @@ declare module "fastify" {
|
||||
id: string;
|
||||
orgId: string;
|
||||
};
|
||||
rateLimits: RateLimitConfiguration;
|
||||
// passport data
|
||||
passportUser: {
|
||||
isUserCompleted: string;
|
||||
@ -113,6 +119,7 @@ declare module "fastify" {
|
||||
group: TGroupServiceFactory;
|
||||
groupProject: TGroupProjectServiceFactory;
|
||||
apiKey: TApiKeyServiceFactory;
|
||||
pkiAlert: TPkiAlertServiceFactory;
|
||||
project: TProjectServiceFactory;
|
||||
projectMembership: TProjectMembershipServiceFactory;
|
||||
projectEnv: TProjectEnvServiceFactory;
|
||||
@ -150,8 +157,10 @@ declare module "fastify" {
|
||||
auditLog: TAuditLogServiceFactory;
|
||||
auditLogStream: TAuditLogStreamServiceFactory;
|
||||
certificate: TCertificateServiceFactory;
|
||||
certificateTemplate: TCertificateTemplateServiceFactory;
|
||||
certificateAuthority: TCertificateAuthorityServiceFactory;
|
||||
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
||||
pkiCollection: TPkiCollectionServiceFactory;
|
||||
secretScanning: TSecretScanningServiceFactory;
|
||||
license: TLicenseServiceFactory;
|
||||
trustedIp: TTrustedIpServiceFactory;
|
||||
@ -165,6 +174,7 @@ declare module "fastify" {
|
||||
rateLimit: TRateLimitServiceFactory;
|
||||
userEngagement: TUserEngagementServiceFactory;
|
||||
externalKms: TExternalKmsServiceFactory;
|
||||
orgAdmin: TOrgAdminServiceFactory;
|
||||
};
|
||||
// this is exclusive use for middlewares in which we need to inject data
|
||||
// everywhere else access using service layer
|
||||
|
28
backend/src/@types/knex.d.ts
vendored
28
backend/src/@types/knex.d.ts
vendored
@ -53,6 +53,9 @@ import {
|
||||
TCertificateSecretsUpdate,
|
||||
TCertificatesInsert,
|
||||
TCertificatesUpdate,
|
||||
TCertificateTemplates,
|
||||
TCertificateTemplatesInsert,
|
||||
TCertificateTemplatesUpdate,
|
||||
TDynamicSecretLeases,
|
||||
TDynamicSecretLeasesInsert,
|
||||
TDynamicSecretLeasesUpdate,
|
||||
@ -161,6 +164,15 @@ import {
|
||||
TOrgRoles,
|
||||
TOrgRolesInsert,
|
||||
TOrgRolesUpdate,
|
||||
TPkiAlerts,
|
||||
TPkiAlertsInsert,
|
||||
TPkiAlertsUpdate,
|
||||
TPkiCollectionItems,
|
||||
TPkiCollectionItemsInsert,
|
||||
TPkiCollectionItemsUpdate,
|
||||
TPkiCollections,
|
||||
TPkiCollectionsInsert,
|
||||
TPkiCollectionsUpdate,
|
||||
TProjectBots,
|
||||
TProjectBotsInsert,
|
||||
TProjectBotsUpdate,
|
||||
@ -355,6 +367,11 @@ declare module "knex/types/tables" {
|
||||
TCertificateAuthorityCrlUpdate
|
||||
>;
|
||||
[TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
|
||||
[TableName.CertificateTemplate]: KnexOriginal.CompositeTableType<
|
||||
TCertificateTemplates,
|
||||
TCertificateTemplatesInsert,
|
||||
TCertificateTemplatesUpdate
|
||||
>;
|
||||
[TableName.CertificateBody]: KnexOriginal.CompositeTableType<
|
||||
TCertificateBodies,
|
||||
TCertificateBodiesInsert,
|
||||
@ -365,6 +382,17 @@ declare module "knex/types/tables" {
|
||||
TCertificateSecretsInsert,
|
||||
TCertificateSecretsUpdate
|
||||
>;
|
||||
[TableName.PkiAlert]: KnexOriginal.CompositeTableType<TPkiAlerts, TPkiAlertsInsert, TPkiAlertsUpdate>;
|
||||
[TableName.PkiCollection]: KnexOriginal.CompositeTableType<
|
||||
TPkiCollections,
|
||||
TPkiCollectionsInsert,
|
||||
TPkiCollectionsUpdate
|
||||
>;
|
||||
[TableName.PkiCollectionItem]: KnexOriginal.CompositeTableType<
|
||||
TPkiCollectionItems,
|
||||
TPkiCollectionItemsInsert,
|
||||
TPkiCollectionItemsUpdate
|
||||
>;
|
||||
[TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
|
||||
TUserGroupMembership,
|
||||
TUserGroupMembershipInsert,
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "enforcementLevel");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.string("enforcementLevel", 10).notNullable().defaultTo(EnforcementLevel.Hard);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalPolicy, "enforcementLevel");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalPolicy, (table) => {
|
||||
table.dropColumn("enforcementLevel");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "enforcementLevel");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.string("enforcementLevel", 10).notNullable().defaultTo(EnforcementLevel.Hard);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicy, "enforcementLevel");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.AccessApprovalPolicy, (table) => {
|
||||
table.dropColumn("enforcementLevel");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SecretSharingAccessType } from "@app/lib/types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.SecretSharing, (table) => {
|
||||
table.string("accessType").notNullable().defaultTo(SecretSharingAccessType.Anyone);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretSharing, "accessType");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.SecretSharing, (table) => {
|
||||
table.dropColumn("accessType");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalRequest, "bypassReason");
|
||||
if (!hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalRequest, (table) => {
|
||||
table.string("bypassReason").nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.SecretApprovalRequest, "bypassReason");
|
||||
if (hasColumn) {
|
||||
await knex.schema.table(TableName.SecretApprovalRequest, (table) => {
|
||||
table.dropColumn("bypassReason");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,294 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
// ---------- ACCESS APPROVAL POLICY APPROVER ------------
|
||||
const hasApproverUserId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverUserId");
|
||||
const hasApproverId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverId");
|
||||
|
||||
if (!hasApproverUserId) {
|
||||
// add the new fields
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (tb) => {
|
||||
// if (hasApproverId) tb.setNullable("approverId");
|
||||
tb.uuid("approverUserId");
|
||||
tb.foreign("approverUserId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||
});
|
||||
|
||||
// convert project membership id => user id
|
||||
await knex(TableName.AccessApprovalPolicyApprover).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
approverUserId: knex(TableName.ProjectMembership)
|
||||
.select("userId")
|
||||
.where("id", knex.raw("??", [`${TableName.AccessApprovalPolicyApprover}.approverId`]))
|
||||
});
|
||||
// drop the old field
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (tb) => {
|
||||
if (hasApproverId) tb.dropColumn("approverId");
|
||||
tb.uuid("approverUserId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- ACCESS APPROVAL REQUEST ------------
|
||||
const hasAccessApprovalRequestTable = await knex.schema.hasTable(TableName.AccessApprovalRequest);
|
||||
const hasRequestedByUserId = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedByUserId");
|
||||
const hasRequestedBy = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedBy");
|
||||
|
||||
if (hasAccessApprovalRequestTable) {
|
||||
// new fields
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
|
||||
if (!hasRequestedByUserId) {
|
||||
tb.uuid("requestedByUserId");
|
||||
tb.foreign("requestedByUserId").references("id").inTable(TableName.Users).onDelete("SET NULL");
|
||||
}
|
||||
});
|
||||
|
||||
// copy the assigned project membership => user id to new fields
|
||||
await knex(TableName.AccessApprovalRequest).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
requestedByUserId: knex(TableName.ProjectMembership)
|
||||
.select("userId")
|
||||
.where("id", knex.raw("??", [`${TableName.AccessApprovalRequest}.requestedBy`]))
|
||||
});
|
||||
// drop old fields
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
|
||||
if (hasRequestedBy) {
|
||||
// DROP AT A LATER TIME
|
||||
// tb.dropColumn("requestedBy");
|
||||
|
||||
// ADD ALLOW NULLABLE FOR NOW
|
||||
tb.uuid("requestedBy").nullable().alter();
|
||||
}
|
||||
tb.uuid("requestedByUserId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- ACCESS APPROVAL REQUEST REVIEWER ------------
|
||||
const hasMemberId = await knex.schema.hasColumn(TableName.AccessApprovalRequestReviewer, "member");
|
||||
const hasReviewerUserId = await knex.schema.hasColumn(TableName.AccessApprovalRequestReviewer, "reviewerUserId");
|
||||
if (!hasReviewerUserId) {
|
||||
// new fields
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequestReviewer, (tb) => {
|
||||
// if (hasMemberId) tb.setNullable("member");
|
||||
tb.uuid("reviewerUserId");
|
||||
tb.foreign("reviewerUserId").references("id").inTable(TableName.Users).onDelete("SET NULL");
|
||||
});
|
||||
// copy project membership => user id to new fields
|
||||
await knex(TableName.AccessApprovalRequestReviewer).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
reviewerUserId: knex(TableName.ProjectMembership)
|
||||
.select("userId")
|
||||
.where("id", knex.raw("??", [`${TableName.AccessApprovalRequestReviewer}.member`]))
|
||||
});
|
||||
// drop table
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequestReviewer, (tb) => {
|
||||
if (hasMemberId) {
|
||||
// DROP AT A LATER TIME
|
||||
// tb.dropColumn("member");
|
||||
|
||||
// ADD ALLOW NULLABLE FOR NOW
|
||||
tb.uuid("member").nullable().alter();
|
||||
}
|
||||
tb.uuid("reviewerUserId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- PROJECT USER ADDITIONAL PRIVILEGE ------------
|
||||
const projectUserAdditionalPrivilegeHasProjectMembershipId = await knex.schema.hasColumn(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
"projectMembershipId"
|
||||
);
|
||||
|
||||
const projectUserAdditionalPrivilegeHasUserId = await knex.schema.hasColumn(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
"userId"
|
||||
);
|
||||
|
||||
if (!projectUserAdditionalPrivilegeHasUserId) {
|
||||
await knex.schema.alterTable(TableName.ProjectUserAdditionalPrivilege, (tb) => {
|
||||
tb.uuid("userId");
|
||||
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||
|
||||
tb.string("projectId");
|
||||
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||
});
|
||||
|
||||
await knex(TableName.ProjectUserAdditionalPrivilege)
|
||||
.update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
userId: knex(TableName.ProjectMembership)
|
||||
.select("userId")
|
||||
.where("id", knex.raw("??", [`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`])),
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
projectId: knex(TableName.ProjectMembership)
|
||||
.select("projectId")
|
||||
.where("id", knex.raw("??", [`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`]))
|
||||
})
|
||||
.whereNotNull("projectMembershipId");
|
||||
|
||||
await knex.schema.alterTable(TableName.ProjectUserAdditionalPrivilege, (tb) => {
|
||||
tb.uuid("userId").notNullable().alter();
|
||||
tb.string("projectId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
if (projectUserAdditionalPrivilegeHasProjectMembershipId) {
|
||||
await knex.schema.alterTable(TableName.ProjectUserAdditionalPrivilege, (tb) => {
|
||||
// DROP AT A LATER TIME
|
||||
// tb.dropColumn("projectMembershipId");
|
||||
|
||||
// ADD ALLOW NULLABLE FOR NOW
|
||||
tb.uuid("projectMembershipId").nullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
// We remove project user additional privileges first, because it may delete records in the database where the project membership is not found.
|
||||
// The project membership won't be found on records created by group members. In those cades we just delete the record and continue.
|
||||
// When the additionl privilege record is deleted, it will cascade delete the access request created by the group member.
|
||||
|
||||
// ---------- PROJECT USER ADDITIONAL PRIVILEGE ------------
|
||||
const hasUserId = await knex.schema.hasColumn(TableName.ProjectUserAdditionalPrivilege, "userId");
|
||||
const hasProjectMembershipId = await knex.schema.hasColumn(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
"projectMembershipId"
|
||||
);
|
||||
|
||||
// If it doesn't have the userId field, then the up migration has not run
|
||||
if (!hasUserId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await knex.schema.alterTable(TableName.ProjectUserAdditionalPrivilege, (tb) => {
|
||||
if (!hasProjectMembershipId) {
|
||||
tb.uuid("projectMembershipId");
|
||||
tb.foreign("projectMembershipId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasProjectMembershipId) {
|
||||
// First, update records where a matching project membership exists
|
||||
await knex(TableName.ProjectUserAdditionalPrivilege).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
projectMembershipId: knex(TableName.ProjectMembership)
|
||||
.select("id")
|
||||
.where("userId", knex.raw("??", [`${TableName.ProjectUserAdditionalPrivilege}.userId`]))
|
||||
});
|
||||
|
||||
await knex(TableName.AccessApprovalRequest).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
projectMembershipId: knex(TableName.ProjectMembership)
|
||||
.select("id")
|
||||
.where("userId", knex.raw("??", [`${TableName.SecretApprovalRequest}.userId`]))
|
||||
});
|
||||
|
||||
await knex.schema.alterTable(TableName.ProjectUserAdditionalPrivilege, (tb) => {
|
||||
tb.dropColumn("userId");
|
||||
tb.dropColumn("projectId");
|
||||
|
||||
tb.uuid("projectMembershipId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
// Then, delete records where no matching project membership was found
|
||||
await knex(TableName.ProjectUserAdditionalPrivilege).whereNull("projectMembershipId").delete();
|
||||
await knex(TableName.AccessApprovalRequest).whereNull("requestedBy").delete();
|
||||
|
||||
// ---------- ACCESS APPROVAL POLICY APPROVER ------------
|
||||
const hasApproverUserId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverUserId");
|
||||
const hasApproverId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverId");
|
||||
|
||||
if (hasApproverUserId) {
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (tb) => {
|
||||
if (!hasApproverId) {
|
||||
tb.uuid("approverId");
|
||||
tb.foreign("approverId").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasApproverId) {
|
||||
await knex(TableName.AccessApprovalPolicyApprover).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
approverId: knex(TableName.ProjectMembership)
|
||||
.select("id")
|
||||
.where("userId", knex.raw("??", [`${TableName.AccessApprovalPolicyApprover}.approverUserId`]))
|
||||
});
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (tb) => {
|
||||
tb.dropColumn("approverUserId");
|
||||
|
||||
tb.uuid("approverId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- ACCESS APPROVAL REQUEST ------------
|
||||
const hasAccessApprovalRequestTable = await knex.schema.hasTable(TableName.AccessApprovalRequest);
|
||||
const hasRequestedByUserId = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedByUserId");
|
||||
const hasRequestedBy = await knex.schema.hasColumn(TableName.AccessApprovalRequest, "requestedBy");
|
||||
|
||||
if (hasAccessApprovalRequestTable) {
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
|
||||
if (!hasRequestedBy) {
|
||||
tb.uuid("requestedBy");
|
||||
tb.foreign("requestedBy").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||
}
|
||||
});
|
||||
|
||||
// Try to find a project membership based on the AccessApprovalRequest.requestedByUserId and AccessApprovalRequest.policyId(reference to AccessApprovalRequestPolicy).envId(reference to Environment).projectId(reference to Project)
|
||||
// If a project membership is found, set the AccessApprovalRequest.requestedBy to the project membership id
|
||||
// If a project membership is not found, remove the AccessApprovalRequest record
|
||||
|
||||
await knex(TableName.AccessApprovalRequest).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
requestedBy: knex(TableName.ProjectMembership)
|
||||
.select("id")
|
||||
.where("userId", knex.raw("??", [`${TableName.AccessApprovalRequest}.requestedByUserId`]))
|
||||
});
|
||||
|
||||
// Then, delete records where no matching project membership was found
|
||||
await knex(TableName.AccessApprovalRequest).whereNull("requestedBy").delete();
|
||||
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequest, (tb) => {
|
||||
if (hasRequestedByUserId) {
|
||||
tb.dropColumn("requestedByUserId");
|
||||
}
|
||||
if (hasRequestedBy) tb.uuid("requestedBy").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- ACCESS APPROVAL REQUEST REVIEWER ------------
|
||||
const hasMemberId = await knex.schema.hasColumn(TableName.AccessApprovalRequestReviewer, "member");
|
||||
const hasReviewerUserId = await knex.schema.hasColumn(TableName.AccessApprovalRequestReviewer, "reviewerUserId");
|
||||
|
||||
if (hasReviewerUserId) {
|
||||
if (!hasMemberId) {
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequestReviewer, (tb) => {
|
||||
tb.uuid("member");
|
||||
tb.foreign("member").references("id").inTable(TableName.ProjectMembership).onDelete("CASCADE");
|
||||
});
|
||||
}
|
||||
await knex(TableName.AccessApprovalRequestReviewer).update({
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore because generate schema happens after this
|
||||
member: knex(TableName.ProjectMembership)
|
||||
.select("id")
|
||||
.where("userId", knex.raw("??", [`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`]))
|
||||
});
|
||||
await knex.schema.alterTable(TableName.AccessApprovalRequestReviewer, (tb) => {
|
||||
tb.dropColumn("reviewerUserId");
|
||||
|
||||
tb.uuid("member").notNullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||
const doesNameExist = await knex.schema.hasColumn(TableName.SecretSharing, "name");
|
||||
if (!doesNameExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.string("name").nullable();
|
||||
});
|
||||
}
|
||||
|
||||
const doesLastViewedAtExist = await knex.schema.hasColumn(TableName.SecretSharing, "lastViewedAt");
|
||||
if (!doesLastViewedAtExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.timestamp("lastViewedAt").nullable();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||
const doesNameExist = await knex.schema.hasColumn(TableName.SecretSharing, "name");
|
||||
if (doesNameExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.dropColumn("name");
|
||||
});
|
||||
}
|
||||
|
||||
const doesLastViewedAtExist = await knex.schema.hasColumn(TableName.SecretSharing, "lastViewedAt");
|
||||
if (doesLastViewedAtExist) {
|
||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||
t.dropColumn("lastViewedAt");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SecretType, TableName } from "../schemas";
|
||||
@ -22,6 +23,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
t.uuid("folderId").notNullable();
|
||||
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("CASCADE");
|
||||
t.timestamps(true, true, true);
|
||||
t.index(["folderId", "userId"]);
|
||||
});
|
||||
}
|
||||
await createOnUpdateTrigger(knex, TableName.SecretV2);
|
||||
@ -105,12 +107,12 @@ export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.SnapshotSecretV2))) {
|
||||
await knex.schema.createTable(TableName.SnapshotSecretV2, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.uuid("envId").notNullable();
|
||||
t.uuid("envId").index().notNullable();
|
||||
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
||||
// not a relation kept like that to keep it when rolled back
|
||||
t.uuid("secretVersionId").notNullable();
|
||||
t.uuid("secretVersionId").index().notNullable();
|
||||
t.foreign("secretVersionId").references("id").inTable(TableName.SecretVersionV2).onDelete("CASCADE");
|
||||
t.uuid("snapshotId").notNullable();
|
||||
t.uuid("snapshotId").index().notNullable();
|
||||
t.foreign("snapshotId").references("id").inTable(TableName.Snapshot).onDelete("CASCADE");
|
||||
t.timestamps(true, true, true);
|
||||
});
|
117
backend/src/db/migrations/20240802181855_ca-cert-version.ts
Normal file
117
backend/src/db/migrations/20240802181855_ca-cert-version.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||
const hasActiveCaCertIdColumn = await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertId");
|
||||
if (!hasActiveCaCertIdColumn) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||
t.uuid("activeCaCertId").nullable();
|
||||
t.foreign("activeCaCertId").references("id").inTable(TableName.CertificateAuthorityCert);
|
||||
});
|
||||
|
||||
await knex.raw(`
|
||||
UPDATE "${TableName.CertificateAuthority}" ca
|
||||
SET "activeCaCertId" = cac.id
|
||||
FROM "${TableName.CertificateAuthorityCert}" cac
|
||||
WHERE ca.id = cac."caId"
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
if (await knex.schema.hasTable(TableName.CertificateAuthorityCert)) {
|
||||
const hasVersionColumn = await knex.schema.hasColumn(TableName.CertificateAuthorityCert, "version");
|
||||
if (!hasVersionColumn) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
||||
t.integer("version").nullable();
|
||||
t.dropUnique(["caId"]);
|
||||
});
|
||||
|
||||
await knex(TableName.CertificateAuthorityCert).update({ version: 1 }).whereNull("version");
|
||||
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
||||
t.integer("version").notNullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
const hasCaSecretIdColumn = await knex.schema.hasColumn(TableName.CertificateAuthorityCert, "caSecretId");
|
||||
if (!hasCaSecretIdColumn) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
||||
t.uuid("caSecretId").nullable();
|
||||
t.foreign("caSecretId").references("id").inTable(TableName.CertificateAuthoritySecret).onDelete("CASCADE");
|
||||
});
|
||||
|
||||
await knex.raw(`
|
||||
UPDATE "${TableName.CertificateAuthorityCert}" cert
|
||||
SET "caSecretId" = (
|
||||
SELECT sec.id
|
||||
FROM "${TableName.CertificateAuthoritySecret}" sec
|
||||
WHERE sec."caId" = cert."caId"
|
||||
)
|
||||
`);
|
||||
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
||||
t.uuid("caSecretId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (await knex.schema.hasTable(TableName.CertificateAuthoritySecret)) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthoritySecret, (t) => {
|
||||
t.dropUnique(["caId"]);
|
||||
});
|
||||
}
|
||||
|
||||
if (await knex.schema.hasTable(TableName.Certificate)) {
|
||||
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||
t.uuid("caCertId").nullable();
|
||||
t.foreign("caCertId").references("id").inTable(TableName.CertificateAuthorityCert);
|
||||
});
|
||||
|
||||
await knex.raw(`
|
||||
UPDATE "${TableName.Certificate}" cert
|
||||
SET "caCertId" = (
|
||||
SELECT caCert.id
|
||||
FROM "${TableName.CertificateAuthorityCert}" caCert
|
||||
WHERE caCert."caId" = cert."caId"
|
||||
)
|
||||
`);
|
||||
|
||||
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||
t.uuid("caCertId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||
if (await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertId")) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||
t.dropColumn("activeCaCertId");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (await knex.schema.hasTable(TableName.CertificateAuthorityCert)) {
|
||||
if (await knex.schema.hasColumn(TableName.CertificateAuthorityCert, "version")) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
||||
t.dropColumn("version");
|
||||
});
|
||||
}
|
||||
|
||||
if (await knex.schema.hasColumn(TableName.CertificateAuthorityCert, "caSecretId")) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCert, (t) => {
|
||||
t.dropColumn("caSecretId");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (await knex.schema.hasTable(TableName.Certificate)) {
|
||||
if (await knex.schema.hasColumn(TableName.Certificate, "caCertId")) {
|
||||
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||
t.dropColumn("caCertId");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasCreationLimitCol = await knex.schema.hasColumn(TableName.RateLimit, "creationLimit");
|
||||
await knex.schema.alterTable(TableName.RateLimit, (t) => {
|
||||
if (hasCreationLimitCol) {
|
||||
t.dropColumn("creationLimit");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasCreationLimitCol = await knex.schema.hasColumn(TableName.RateLimit, "creationLimit");
|
||||
await knex.schema.alterTable(TableName.RateLimit, (t) => {
|
||||
if (!hasCreationLimitCol) {
|
||||
t.integer("creationLimit").defaultTo(30).notNullable();
|
||||
}
|
||||
});
|
||||
}
|
21
backend/src/db/migrations/20240806185442_drop-tag-name.ts
Normal file
21
backend/src/db/migrations/20240806185442_drop-tag-name.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name");
|
||||
if (hasNameField) {
|
||||
await knex.schema.alterTable(TableName.SecretTag, (t) => {
|
||||
t.dropColumn("name");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name");
|
||||
if (!hasNameField) {
|
||||
await knex.schema.alterTable(TableName.SecretTag, (t) => {
|
||||
t.string("name");
|
||||
});
|
||||
}
|
||||
}
|
62
backend/src/db/migrations/20240818024923_cert-alerting.ts
Normal file
62
backend/src/db/migrations/20240818024923_cert-alerting.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (!(await knex.schema.hasTable(TableName.PkiCollection))) {
|
||||
await knex.schema.createTable(TableName.PkiCollection, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
t.string("projectId").notNullable();
|
||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||
t.string("name").notNullable();
|
||||
t.string("description").notNullable();
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.PkiCollection);
|
||||
|
||||
if (!(await knex.schema.hasTable(TableName.PkiCollectionItem))) {
|
||||
await knex.schema.createTable(TableName.PkiCollectionItem, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
t.uuid("pkiCollectionId").notNullable();
|
||||
t.foreign("pkiCollectionId").references("id").inTable(TableName.PkiCollection).onDelete("CASCADE");
|
||||
t.uuid("caId").nullable();
|
||||
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
||||
t.uuid("certId").nullable();
|
||||
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.PkiCollectionItem);
|
||||
|
||||
if (!(await knex.schema.hasTable(TableName.PkiAlert))) {
|
||||
await knex.schema.createTable(TableName.PkiAlert, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
t.string("projectId").notNullable();
|
||||
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||
t.uuid("pkiCollectionId").notNullable();
|
||||
t.foreign("pkiCollectionId").references("id").inTable(TableName.PkiCollection).onDelete("CASCADE");
|
||||
t.string("name").notNullable();
|
||||
t.integer("alertBeforeDays").notNullable();
|
||||
t.string("recipientEmails").notNullable();
|
||||
t.unique(["name", "projectId"]);
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.PkiAlert);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.PkiAlert);
|
||||
await dropOnUpdateTrigger(knex, TableName.PkiAlert);
|
||||
|
||||
await knex.schema.dropTableIfExists(TableName.PkiCollectionItem);
|
||||
await dropOnUpdateTrigger(knex, TableName.PkiCollectionItem);
|
||||
|
||||
await knex.schema.dropTableIfExists(TableName.PkiCollection);
|
||||
await dropOnUpdateTrigger(knex, TableName.PkiCollection);
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasCertificateTemplateTable = await knex.schema.hasTable(TableName.CertificateTemplate);
|
||||
if (!hasCertificateTemplateTable) {
|
||||
await knex.schema.createTable(TableName.CertificateTemplate, (tb) => {
|
||||
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
tb.uuid("caId").notNullable();
|
||||
tb.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
||||
tb.uuid("pkiCollectionId");
|
||||
tb.foreign("pkiCollectionId").references("id").inTable(TableName.PkiCollection).onDelete("SET NULL");
|
||||
tb.string("name").notNullable();
|
||||
tb.string("commonName").notNullable();
|
||||
tb.string("subjectAlternativeName").notNullable();
|
||||
tb.string("ttl").notNullable();
|
||||
tb.timestamps(true, true, true);
|
||||
});
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.CertificateTemplate);
|
||||
}
|
||||
|
||||
const doesCertificateTableHaveTemplateId = await knex.schema.hasColumn(
|
||||
TableName.Certificate,
|
||||
"certificateTemplateId"
|
||||
);
|
||||
|
||||
if (!doesCertificateTableHaveTemplateId) {
|
||||
await knex.schema.alterTable(TableName.Certificate, (tb) => {
|
||||
tb.uuid("certificateTemplateId");
|
||||
tb.foreign("certificateTemplateId").references("id").inTable(TableName.CertificateTemplate).onDelete("SET NULL");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const doesCertificateTableHaveTemplateId = await knex.schema.hasColumn(
|
||||
TableName.Certificate,
|
||||
"certificateTemplateId"
|
||||
);
|
||||
|
||||
if (doesCertificateTableHaveTemplateId) {
|
||||
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||
t.dropColumn("certificateTemplateId");
|
||||
});
|
||||
}
|
||||
|
||||
const hasCertificateTemplateTable = await knex.schema.hasTable(TableName.CertificateTemplate);
|
||||
if (hasCertificateTemplateTable) {
|
||||
await knex.schema.dropTable(TableName.CertificateTemplate);
|
||||
await dropOnUpdateTrigger(knex, TableName.CertificateTemplate);
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.CertificateAuthorityCrl)) {
|
||||
const hasCaSecretIdColumn = await knex.schema.hasColumn(TableName.CertificateAuthorityCrl, "caSecretId");
|
||||
if (!hasCaSecretIdColumn) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCrl, (t) => {
|
||||
t.uuid("caSecretId").nullable();
|
||||
t.foreign("caSecretId").references("id").inTable(TableName.CertificateAuthoritySecret).onDelete("CASCADE");
|
||||
});
|
||||
|
||||
await knex.raw(`
|
||||
UPDATE "${TableName.CertificateAuthorityCrl}" crl
|
||||
SET "caSecretId" = (
|
||||
SELECT sec.id
|
||||
FROM "${TableName.CertificateAuthoritySecret}" sec
|
||||
WHERE sec."caId" = crl."caId"
|
||||
)
|
||||
`);
|
||||
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCrl, (t) => {
|
||||
t.uuid("caSecretId").notNullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasTable(TableName.CertificateAuthorityCrl)) {
|
||||
await knex.schema.alterTable(TableName.CertificateAuthorityCrl, (t) => {
|
||||
t.dropColumn("caSecretId");
|
||||
});
|
||||
}
|
||||
}
|
105
backend/src/db/migrations/utils/kms.ts
Normal file
105
backend/src/db/migrations/utils/kms.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import slugify from "@sindresorhus/slugify";
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { randomSecureBytes } from "@app/lib/crypto";
|
||||
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
const getInstanceRootKey = async (knex: Knex) => {
|
||||
const encryptionKey = process.env.ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY;
|
||||
// if root key its base64 encoded
|
||||
const isBase64 = !process.env.ENCRYPTION_KEY;
|
||||
if (!encryptionKey) throw new Error("ENCRYPTION_KEY variable needed for migration");
|
||||
const encryptionKeyBuffer = Buffer.from(encryptionKey, isBase64 ? "base64" : "utf8");
|
||||
|
||||
const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000";
|
||||
const kmsRootConfig = await knex(TableName.KmsServerRootConfig).where({ id: KMS_ROOT_CONFIG_UUID }).first();
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
if (kmsRootConfig) {
|
||||
const decryptedRootKey = cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer);
|
||||
// set the flag so that other instancen nodes can start
|
||||
return decryptedRootKey;
|
||||
}
|
||||
|
||||
const newRootKey = randomSecureBytes(32);
|
||||
const encryptedRootKey = cipher.encrypt(newRootKey, encryptionKeyBuffer);
|
||||
await knex(TableName.KmsServerRootConfig).insert({
|
||||
encryptedRootKey,
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore id is kept as fixed for idempotence and to avoid race condition
|
||||
id: KMS_ROOT_CONFIG_UUID
|
||||
});
|
||||
return encryptedRootKey;
|
||||
};
|
||||
|
||||
export const getSecretManagerDataKey = async (knex: Knex, projectId: string) => {
|
||||
const KMS_VERSION = "v01";
|
||||
const KMS_VERSION_BLOB_LENGTH = 3;
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const project = await knex(TableName.Project).where({ id: projectId }).first();
|
||||
if (!project) throw new Error("Missing project id");
|
||||
|
||||
const ROOT_ENCRYPTION_KEY = await getInstanceRootKey(knex);
|
||||
|
||||
let secretManagerKmsKey;
|
||||
const projectSecretManagerKmsId = project?.kmsSecretManagerKeyId;
|
||||
if (projectSecretManagerKmsId) {
|
||||
const kmsDoc = await knex(TableName.KmsKey)
|
||||
.leftJoin(TableName.InternalKms, `${TableName.KmsKey}.id`, `${TableName.InternalKms}.kmsKeyId`)
|
||||
.where({ [`${TableName.KmsKey}.id` as "id"]: projectSecretManagerKmsId })
|
||||
.first();
|
||||
if (!kmsDoc) throw new Error("missing kms");
|
||||
secretManagerKmsKey = cipher.decrypt(kmsDoc.encryptedKey, ROOT_ENCRYPTION_KEY);
|
||||
} else {
|
||||
const [kmsDoc] = await knex(TableName.KmsKey)
|
||||
.insert({
|
||||
slug: slugify(alphaNumericNanoId(8).toLowerCase()),
|
||||
orgId: project.orgId,
|
||||
isReserved: false
|
||||
})
|
||||
.returning("*");
|
||||
|
||||
secretManagerKmsKey = randomSecureBytes(32);
|
||||
const encryptedKeyMaterial = cipher.encrypt(secretManagerKmsKey, ROOT_ENCRYPTION_KEY);
|
||||
await knex(TableName.InternalKms).insert({
|
||||
version: 1,
|
||||
encryptedKey: encryptedKeyMaterial,
|
||||
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
|
||||
kmsKeyId: kmsDoc.id
|
||||
});
|
||||
}
|
||||
|
||||
const encryptedSecretManagerDataKey = project?.kmsSecretManagerEncryptedDataKey;
|
||||
let dataKey: Buffer;
|
||||
if (!encryptedSecretManagerDataKey) {
|
||||
dataKey = randomSecureBytes();
|
||||
// the below versioning we do it automatically in kms service
|
||||
const unversionedDataKey = cipher.encrypt(dataKey, secretManagerKmsKey);
|
||||
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
|
||||
await knex(TableName.Project)
|
||||
.where({ id: projectId })
|
||||
.update({
|
||||
kmsSecretManagerEncryptedDataKey: Buffer.concat([unversionedDataKey, versionBlob])
|
||||
});
|
||||
} else {
|
||||
const cipherTextBlob = encryptedSecretManagerDataKey.subarray(0, -KMS_VERSION_BLOB_LENGTH);
|
||||
dataKey = cipher.decrypt(cipherTextBlob, secretManagerKmsKey);
|
||||
}
|
||||
|
||||
return {
|
||||
encryptor: ({ plainText }: { plainText: Buffer }) => {
|
||||
const encryptedPlainTextBlob = cipher.encrypt(plainText, dataKey);
|
||||
|
||||
// Buffer#1 encrypted text + Buffer#2 version number
|
||||
const versionBlob = Buffer.from(KMS_VERSION, "utf8"); // length is 3
|
||||
const cipherTextBlob = Buffer.concat([encryptedPlainTextBlob, versionBlob]);
|
||||
return { cipherTextBlob };
|
||||
},
|
||||
decryptor: ({ cipherTextBlob: versionedCipherTextBlob }: { cipherTextBlob: Buffer }) => {
|
||||
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
|
||||
const decryptedBlob = cipher.decrypt(cipherTextBlob, dataKey);
|
||||
return decryptedBlob;
|
||||
}
|
||||
};
|
||||
};
|
@ -9,10 +9,10 @@ import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const AccessApprovalPoliciesApproversSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
approverId: z.string().uuid(),
|
||||
policyId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
approverUserId: z.string().uuid()
|
||||
});
|
||||
|
||||
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
|
||||
|
@ -14,7 +14,8 @@ export const AccessApprovalPoliciesSchema = z.object({
|
||||
secretPath: z.string().nullable().optional(),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
enforcementLevel: z.string().default("hard")
|
||||
});
|
||||
|
||||
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
||||
|
@ -9,11 +9,12 @@ import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const AccessApprovalRequestsReviewersSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
member: z.string().uuid(),
|
||||
member: z.string().uuid().nullable().optional(),
|
||||
status: z.string(),
|
||||
requestId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
reviewerUserId: z.string().uuid()
|
||||
});
|
||||
|
||||
export type TAccessApprovalRequestsReviewers = z.infer<typeof AccessApprovalRequestsReviewersSchema>;
|
||||
|
@ -11,12 +11,13 @@ export const AccessApprovalRequestsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
policyId: z.string().uuid(),
|
||||
privilegeId: z.string().uuid().nullable().optional(),
|
||||
requestedBy: z.string().uuid(),
|
||||
requestedBy: z.string().uuid().nullable().optional(),
|
||||
isTemporary: z.boolean(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
permissions: z.unknown(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
requestedByUserId: z.string().uuid()
|
||||
});
|
||||
|
||||
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;
|
||||
|
@ -27,7 +27,8 @@ export const CertificateAuthoritiesSchema = z.object({
|
||||
maxPathLength: z.number().nullable().optional(),
|
||||
keyAlgorithm: z.string(),
|
||||
notBefore: z.date().nullable().optional(),
|
||||
notAfter: z.date().nullable().optional()
|
||||
notAfter: z.date().nullable().optional(),
|
||||
activeCaCertId: z.string().uuid().nullable().optional()
|
||||
});
|
||||
|
||||
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
||||
|
@ -15,7 +15,9 @@ export const CertificateAuthorityCertsSchema = z.object({
|
||||
updatedAt: z.date(),
|
||||
caId: z.string().uuid(),
|
||||
encryptedCertificate: zodBuffer,
|
||||
encryptedCertificateChain: zodBuffer
|
||||
encryptedCertificateChain: zodBuffer,
|
||||
version: z.number(),
|
||||
caSecretId: z.string().uuid()
|
||||
});
|
||||
|
||||
export type TCertificateAuthorityCerts = z.infer<typeof CertificateAuthorityCertsSchema>;
|
||||
|
@ -14,7 +14,8 @@ export const CertificateAuthorityCrlSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
caId: z.string().uuid(),
|
||||
encryptedCrl: zodBuffer
|
||||
encryptedCrl: zodBuffer,
|
||||
caSecretId: z.string().uuid()
|
||||
});
|
||||
|
||||
export type TCertificateAuthorityCrl = z.infer<typeof CertificateAuthorityCrlSchema>;
|
||||
|
24
backend/src/db/schemas/certificate-templates.ts
Normal file
24
backend/src/db/schemas/certificate-templates.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 CertificateTemplatesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
caId: z.string().uuid(),
|
||||
pkiCollectionId: z.string().uuid().nullable().optional(),
|
||||
name: z.string(),
|
||||
commonName: z.string(),
|
||||
subjectAlternativeName: z.string(),
|
||||
ttl: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
});
|
||||
|
||||
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
||||
export type TCertificateTemplatesInsert = Omit<z.input<typeof CertificateTemplatesSchema>, TImmutableDBKeys>;
|
||||
export type TCertificateTemplatesUpdate = Partial<Omit<z.input<typeof CertificateTemplatesSchema>, TImmutableDBKeys>>;
|
@ -20,7 +20,9 @@ export const CertificatesSchema = z.object({
|
||||
notAfter: z.date(),
|
||||
revokedAt: z.date().nullable().optional(),
|
||||
revocationReason: z.number().nullable().optional(),
|
||||
altNames: z.string().default("").nullable().optional()
|
||||
altNames: z.string().default("").nullable().optional(),
|
||||
caCertId: z.string().uuid(),
|
||||
certificateTemplateId: z.string().uuid().nullable().optional()
|
||||
});
|
||||
|
||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||
|
@ -14,6 +14,7 @@ export * from "./certificate-authority-crl";
|
||||
export * from "./certificate-authority-secret";
|
||||
export * from "./certificate-bodies";
|
||||
export * from "./certificate-secrets";
|
||||
export * from "./certificate-templates";
|
||||
export * from "./certificates";
|
||||
export * from "./dynamic-secret-leases";
|
||||
export * from "./dynamic-secrets";
|
||||
@ -52,6 +53,9 @@ export * from "./org-bots";
|
||||
export * from "./org-memberships";
|
||||
export * from "./org-roles";
|
||||
export * from "./organizations";
|
||||
export * from "./pki-alerts";
|
||||
export * from "./pki-collection-items";
|
||||
export * from "./pki-collections";
|
||||
export * from "./project-bots";
|
||||
export * from "./project-environments";
|
||||
export * from "./project-keys";
|
||||
|
@ -35,7 +35,6 @@ export const IntegrationAuthsSchema = z.object({
|
||||
awsAssumeIamRoleArnCipherText: z.string().nullable().optional(),
|
||||
awsAssumeIamRoleArnIV: z.string().nullable().optional(),
|
||||
awsAssumeIamRoleArnTag: z.string().nullable().optional(),
|
||||
encryptedAwsIamAssumRole: zodBuffer.nullable().optional(),
|
||||
encryptedAccess: zodBuffer.nullable().optional(),
|
||||
encryptedAccessId: zodBuffer.nullable().optional(),
|
||||
encryptedRefresh: zodBuffer.nullable().optional(),
|
||||
|
@ -9,6 +9,10 @@ export enum TableName {
|
||||
Certificate = "certificates",
|
||||
CertificateBody = "certificate_bodies",
|
||||
CertificateSecret = "certificate_secrets",
|
||||
CertificateTemplate = "certificate_templates",
|
||||
PkiAlert = "pki_alerts",
|
||||
PkiCollection = "pki_collections",
|
||||
PkiCollectionItem = "pki_collection_items",
|
||||
Groups = "groups",
|
||||
GroupProjectMembership = "group_project_memberships",
|
||||
GroupProjectMembershipRole = "group_project_membership_roles",
|
||||
|
23
backend/src/db/schemas/pki-alerts.ts
Normal file
23
backend/src/db/schemas/pki-alerts.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// 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 PkiAlertsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
projectId: z.string(),
|
||||
pkiCollectionId: z.string().uuid(),
|
||||
name: z.string(),
|
||||
alertBeforeDays: z.number(),
|
||||
recipientEmails: z.string()
|
||||
});
|
||||
|
||||
export type TPkiAlerts = z.infer<typeof PkiAlertsSchema>;
|
||||
export type TPkiAlertsInsert = Omit<z.input<typeof PkiAlertsSchema>, TImmutableDBKeys>;
|
||||
export type TPkiAlertsUpdate = Partial<Omit<z.input<typeof PkiAlertsSchema>, TImmutableDBKeys>>;
|
21
backend/src/db/schemas/pki-collection-items.ts
Normal file
21
backend/src/db/schemas/pki-collection-items.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// 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 PkiCollectionItemsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
pkiCollectionId: z.string().uuid(),
|
||||
caId: z.string().uuid().nullable().optional(),
|
||||
certId: z.string().uuid().nullable().optional()
|
||||
});
|
||||
|
||||
export type TPkiCollectionItems = z.infer<typeof PkiCollectionItemsSchema>;
|
||||
export type TPkiCollectionItemsInsert = Omit<z.input<typeof PkiCollectionItemsSchema>, TImmutableDBKeys>;
|
||||
export type TPkiCollectionItemsUpdate = Partial<Omit<z.input<typeof PkiCollectionItemsSchema>, TImmutableDBKeys>>;
|
21
backend/src/db/schemas/pki-collections.ts
Normal file
21
backend/src/db/schemas/pki-collections.ts
Normal file
@ -0,0 +1,21 @@
|
||||
// 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 PkiCollectionsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
projectId: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string()
|
||||
});
|
||||
|
||||
export type TPkiCollections = z.infer<typeof PkiCollectionsSchema>;
|
||||
export type TPkiCollectionsInsert = Omit<z.input<typeof PkiCollectionsSchema>, TImmutableDBKeys>;
|
||||
export type TPkiCollectionsUpdate = Partial<Omit<z.input<typeof PkiCollectionsSchema>, TImmutableDBKeys>>;
|
@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
|
||||
export const ProjectUserAdditionalPrivilegeSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
slug: z.string(),
|
||||
projectMembershipId: z.string().uuid(),
|
||||
projectMembershipId: z.string().uuid().nullable().optional(),
|
||||
isTemporary: z.boolean().default(false),
|
||||
temporaryMode: z.string().nullable().optional(),
|
||||
temporaryRange: z.string().nullable().optional(),
|
||||
@ -18,7 +18,9 @@ export const ProjectUserAdditionalPrivilegeSchema = z.object({
|
||||
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||
permissions: z.unknown(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
userId: z.string().uuid(),
|
||||
projectId: z.string()
|
||||
});
|
||||
|
||||
export type TProjectUserAdditionalPrivilege = z.infer<typeof ProjectUserAdditionalPrivilegeSchema>;
|
||||
|
@ -15,7 +15,6 @@ export const RateLimitSchema = z.object({
|
||||
authRateLimit: z.number().default(60),
|
||||
inviteUserRateLimit: z.number().default(30),
|
||||
mfaRateLimit: z.number().default(20),
|
||||
creationLimit: z.number().default(30),
|
||||
publicEndpointLimit: z.number().default(30),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
|
@ -14,7 +14,8 @@ export const SecretApprovalPoliciesSchema = z.object({
|
||||
approvals: z.number().default(1),
|
||||
envId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
enforcementLevel: z.string().default("hard")
|
||||
});
|
||||
|
||||
export type TSecretApprovalPolicies = z.infer<typeof SecretApprovalPoliciesSchema>;
|
||||
|
@ -19,7 +19,8 @@ export const SecretApprovalRequestsSchema = z.object({
|
||||
updatedAt: z.date(),
|
||||
isReplicated: z.boolean().nullable().optional(),
|
||||
committerUserId: z.string().uuid(),
|
||||
statusChangedByUserId: z.string().uuid().nullable().optional()
|
||||
statusChangedByUserId: z.string().uuid().nullable().optional(),
|
||||
bypassReason: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export type TSecretApprovalRequests = z.infer<typeof SecretApprovalRequestsSchema>;
|
||||
|
@ -18,7 +18,10 @@ export const SecretSharingSchema = z.object({
|
||||
orgId: z.string().uuid().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
expiresAfterViews: z.number().nullable().optional()
|
||||
expiresAfterViews: z.number().nullable().optional(),
|
||||
accessType: z.string().default("anyone"),
|
||||
name: z.string().nullable().optional(),
|
||||
lastViewedAt: z.date().nullable().optional()
|
||||
});
|
||||
|
||||
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
||||
|
@ -9,7 +9,6 @@ import { TImmutableDBKeys } from "./models";
|
||||
|
||||
export const SecretTagsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
name: z.string(),
|
||||
slug: z.string(),
|
||||
color: z.string().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
@ -16,10 +17,11 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
name: z.string().optional(),
|
||||
secretPath: z.string().trim().default("/"),
|
||||
environment: z.string(),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1)
|
||||
approverUserIds: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||
path: ["approvals"],
|
||||
message: "The number of approvals should be lower than the number of approvers."
|
||||
}),
|
||||
@ -38,7 +40,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
actorOrgId: req.permission.orgId,
|
||||
...req.body,
|
||||
projectSlug: req.body.projectSlug,
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
||||
enforcementLevel: req.body.enforcementLevel
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
@ -53,7 +56,16 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
approvals: sapPubSchema.extend({ approvers: z.string().array(), secretPath: z.string().optional() }).array()
|
||||
approvals: sapPubSchema
|
||||
.extend({
|
||||
userApprovers: z
|
||||
.object({
|
||||
userId: z.string()
|
||||
})
|
||||
.array(),
|
||||
secretPath: z.string().optional().nullable()
|
||||
})
|
||||
.array()
|
||||
})
|
||||
}
|
||||
},
|
||||
@ -66,6 +78,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectSlug: req.query.projectSlug
|
||||
});
|
||||
|
||||
return { approvals };
|
||||
}
|
||||
});
|
||||
@ -114,10 +127,11 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.trim()
|
||||
.optional()
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1)
|
||||
approverUserIds: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||
path: ["approvals"],
|
||||
message: "The number of approvals should be lower than the number of approvers."
|
||||
}),
|
||||
|
@ -1,10 +1,19 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema } from "@app/db/schemas";
|
||||
import { AccessApprovalRequestsReviewersSchema, AccessApprovalRequestsSchema, UsersSchema } from "@app/db/schemas";
|
||||
import { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
const approvalRequestUser = z.object({ userId: z.string() }).merge(
|
||||
UsersSchema.pick({
|
||||
email: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
username: true
|
||||
})
|
||||
);
|
||||
|
||||
export const registerAccessApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
url: "/",
|
||||
@ -99,14 +108,16 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
||||
approvals: z.number(),
|
||||
approvers: z.string().array(),
|
||||
secretPath: z.string().nullish(),
|
||||
envId: z.string()
|
||||
envId: z.string(),
|
||||
enforcementLevel: z.string()
|
||||
}),
|
||||
reviewers: z
|
||||
.object({
|
||||
member: z.string(),
|
||||
userId: z.string(),
|
||||
status: z.string()
|
||||
})
|
||||
.array()
|
||||
.array(),
|
||||
requestedByUser: approvalRequestUser
|
||||
}).array()
|
||||
})
|
||||
}
|
||||
|
@ -1,86 +1,31 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { z } from "zod";
|
||||
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
|
||||
import { CA_CRLS } from "@app/lib/api-docs";
|
||||
import { readLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerCaCrlRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:caId/crl",
|
||||
url: "/:crlId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Get CRL of the CA",
|
||||
description: "Get CRL in DER format",
|
||||
params: z.object({
|
||||
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CRL.caId)
|
||||
crlId: z.string().trim().describe(CA_CRLS.GET.crlId)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
crl: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CRL.crl)
|
||||
})
|
||||
200: z.instanceof(Buffer)
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { crl, ca } = await server.services.certificateAuthorityCrl.getCaCrl({
|
||||
caId: req.params.caId,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
handler: async (req, res) => {
|
||||
const { crl } = await server.services.certificateAuthorityCrl.getCrlById(req.params.crlId);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId: ca.projectId,
|
||||
event: {
|
||||
type: EventType.GET_CA_CRL,
|
||||
metadata: {
|
||||
caId: ca.id,
|
||||
dn: ca.dn
|
||||
}
|
||||
}
|
||||
});
|
||||
res.header("Content-Type", "application/pkix-crl");
|
||||
|
||||
return {
|
||||
crl
|
||||
};
|
||||
return Buffer.from(crl);
|
||||
}
|
||||
});
|
||||
|
||||
// server.route({
|
||||
// method: "GET",
|
||||
// url: "/:caId/crl/rotate",
|
||||
// config: {
|
||||
// rateLimit: writeLimit
|
||||
// },
|
||||
// onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
// schema: {
|
||||
// description: "Rotate CRL of the CA",
|
||||
// params: z.object({
|
||||
// caId: z.string().trim()
|
||||
// }),
|
||||
// response: {
|
||||
// 200: z.object({
|
||||
// message: z.string()
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
// handler: async (req) => {
|
||||
// await server.services.certificateAuthority.rotateCaCrl({
|
||||
// caId: req.params.caId,
|
||||
// actor: req.permission.type,
|
||||
// actorId: req.permission.id,
|
||||
// actorAuthMethod: req.permission.authMethod,
|
||||
// actorOrgId: req.permission.orgId
|
||||
// });
|
||||
// return {
|
||||
// message: "Successfully rotated CA CRL"
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
@ -131,7 +131,7 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
|
||||
.default("/")
|
||||
.transform(removeTrailingSlash)
|
||||
.describe(DYNAMIC_SECRET_LEASES.RENEW.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.ttl)
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.RENEW.environmentSlug)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -61,7 +61,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
|
||||
await server.register(
|
||||
async (pkiRouter) => {
|
||||
await pkiRouter.register(registerCaCrlRouter, { prefix: "/ca" });
|
||||
await pkiRouter.register(registerCaCrlRouter, { prefix: "/crl" });
|
||||
},
|
||||
{ prefix: "/pki" }
|
||||
);
|
||||
|
@ -52,6 +52,36 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:organizationId/roles/:roleId",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
roleId: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
role: OrgRolesSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const role = await server.services.orgRole.getRole(
|
||||
req.permission.id,
|
||||
req.params.organizationId,
|
||||
req.params.roleId,
|
||||
req.permission.authMethod,
|
||||
req.permission.orgId
|
||||
);
|
||||
return { role };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/:organizationId/roles/:roleId",
|
||||
@ -69,7 +99,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
.trim()
|
||||
.optional()
|
||||
.refine(
|
||||
(val) => typeof val === "undefined" || Object.keys(OrgMembershipRole).includes(val),
|
||||
(val) => typeof val !== "undefined" && !Object.keys(OrgMembershipRole).includes(val),
|
||||
"Please choose a different slug, the slug you have entered is reserved."
|
||||
)
|
||||
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
||||
@ -77,7 +107,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
||||
}),
|
||||
name: z.string().trim().optional(),
|
||||
description: z.string().trim().optional(),
|
||||
permissions: z.any().array()
|
||||
permissions: z.any().array().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -101,7 +101,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
message: "Slug must be a valid"
|
||||
}),
|
||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions)
|
||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -120,7 +120,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||
roleId: req.params.roleId,
|
||||
data: {
|
||||
...req.body,
|
||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||
permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined
|
||||
}
|
||||
});
|
||||
return { role };
|
||||
|
@ -349,4 +349,35 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
return backup;
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:workspaceId/migrate-v3",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const migration = await server.services.secret.startSecretV2Migration({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.params.workspaceId
|
||||
});
|
||||
|
||||
return migration;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -58,7 +58,6 @@ export const registerRateLimitRouter = async (server: FastifyZodProvider) => {
|
||||
authRateLimit: z.number(),
|
||||
inviteUserRateLimit: z.number(),
|
||||
mfaRateLimit: z.number(),
|
||||
creationLimit: z.number(),
|
||||
publicEndpointLimit: z.number()
|
||||
}),
|
||||
response: {
|
||||
|
@ -350,7 +350,12 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
schemas: z.array(z.string()),
|
||||
id: z.string().trim(),
|
||||
displayName: z.string().trim(),
|
||||
members: z.array(z.any()).length(0),
|
||||
members: z.array(
|
||||
z.object({
|
||||
value: z.string(),
|
||||
display: z.string()
|
||||
})
|
||||
),
|
||||
meta: z.object({
|
||||
resourceType: z.string().trim()
|
||||
})
|
||||
@ -423,7 +428,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
displayName: z.string().trim(),
|
||||
members: z.array(
|
||||
z.object({
|
||||
value: z.string(), // infisical orgMembershipId
|
||||
value: z.string(),
|
||||
display: z.string()
|
||||
})
|
||||
)
|
||||
|
@ -2,6 +2,7 @@ import { nanoid } from "nanoid";
|
||||
import { z } from "zod";
|
||||
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
@ -24,11 +25,13 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.default("/")
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||
approverUserIds: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1)
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
path: ["approvals"],
|
||||
message: "The number of approvals should be lower than the number of approvers."
|
||||
}),
|
||||
@ -47,7 +50,8 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.body.workspaceId,
|
||||
...req.body,
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`
|
||||
name: req.body.name ?? `${req.body.environment}-${nanoid(3)}`,
|
||||
enforcementLevel: req.body.enforcementLevel
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
@ -66,15 +70,17 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
body: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
approverUserIds: z.string().array().min(1),
|
||||
approvers: z.string().array().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
secretPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val ? removeTrailingSlash(val) : val))
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).optional()
|
||||
})
|
||||
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||
.refine((data) => data.approvals <= data.approvers.length, {
|
||||
path: ["approvals"],
|
||||
message: "The number of approvals should be lower than the number of approvers."
|
||||
}),
|
||||
|
@ -47,7 +47,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
name: z.string(),
|
||||
approvals: z.number(),
|
||||
approvers: z.string().array(),
|
||||
secretPath: z.string().optional().nullable()
|
||||
secretPath: z.string().optional().nullable(),
|
||||
enforcementLevel: z.string()
|
||||
}),
|
||||
committerUser: approvalRequestUser,
|
||||
commits: z.object({ op: z.string(), secretId: z.string().nullable().optional() }).array(),
|
||||
@ -114,6 +115,9 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
params: z.object({
|
||||
id: z.string()
|
||||
}),
|
||||
body: z.object({
|
||||
bypassReason: z.string().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
approval: SecretApprovalRequestsSchema
|
||||
@ -127,7 +131,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
approvalId: req.params.id
|
||||
approvalId: req.params.id,
|
||||
bypassReason: req.body.bypassReason
|
||||
});
|
||||
return { approval };
|
||||
}
|
||||
@ -246,7 +251,8 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
name: z.string(),
|
||||
approvals: z.number(),
|
||||
approvers: approvalRequestUser.array(),
|
||||
secretPath: z.string().optional().nullable()
|
||||
secretPath: z.string().optional().nullable(),
|
||||
enforcementLevel: z.string()
|
||||
}),
|
||||
environment: z.string(),
|
||||
statusChangedByUser: approvalRequestUser.optional(),
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName, TAccessApprovalPolicies } from "@app/db/schemas";
|
||||
import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, mergeOneToManyRelation, ormify, selectAllTableCols, TFindFilter } from "@app/lib/knex";
|
||||
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||
|
||||
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
|
||||
|
||||
@ -15,12 +15,12 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
// eslint-disable-next-line
|
||||
.where(buildFindFilter(filter))
|
||||
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||
.join(
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalPolicyApprover,
|
||||
`${TableName.AccessApprovalPolicy}.id`,
|
||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||
)
|
||||
.select(tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
||||
@ -35,18 +35,30 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
const doc = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), {
|
||||
[`${TableName.AccessApprovalPolicy}.id` as "id"]: id
|
||||
});
|
||||
const formatedDoc = mergeOneToManyRelation(
|
||||
doc,
|
||||
"id",
|
||||
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
|
||||
...el,
|
||||
envId,
|
||||
environment: { id: envId, name, slug }
|
||||
const formattedDoc = sqlNestRelationships({
|
||||
data: doc,
|
||||
key: "id",
|
||||
parentMapper: (data) => ({
|
||||
environment: {
|
||||
id: data.envId,
|
||||
name: data.envName,
|
||||
slug: data.envSlug
|
||||
},
|
||||
projectId: data.projectId,
|
||||
...AccessApprovalPoliciesSchema.parse(data)
|
||||
}),
|
||||
({ approverId }) => approverId,
|
||||
"approvers"
|
||||
);
|
||||
return formatedDoc?.[0];
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "approverUserId",
|
||||
label: "userApprovers" as const,
|
||||
mapper: ({ approverUserId }) => ({
|
||||
userId: approverUserId
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return formattedDoc?.[0];
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindById" });
|
||||
}
|
||||
@ -55,18 +67,32 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => {
|
||||
try {
|
||||
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
|
||||
const formatedDoc = mergeOneToManyRelation(
|
||||
docs,
|
||||
"id",
|
||||
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
|
||||
...el,
|
||||
envId,
|
||||
environment: { id: envId, name, slug }
|
||||
|
||||
const formattedDocs = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (data) => ({
|
||||
environment: {
|
||||
id: data.envId,
|
||||
name: data.envName,
|
||||
slug: data.envSlug
|
||||
},
|
||||
projectId: data.projectId,
|
||||
...AccessApprovalPoliciesSchema.parse(data)
|
||||
// secretPath: data.secretPath || undefined,
|
||||
}),
|
||||
({ approverId }) => approverId,
|
||||
"approvers"
|
||||
);
|
||||
return formatedDoc.map((policy) => ({ ...policy, secretPath: policy.secretPath || undefined }));
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "approverUserId",
|
||||
label: "userApprovers" as const,
|
||||
mapper: ({ approverUserId }) => ({
|
||||
userId: approverUserId
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return formattedDocs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find" });
|
||||
}
|
||||
|
@ -34,8 +34,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
accessApprovalPolicyApproverDAL,
|
||||
permissionService,
|
||||
projectEnvDAL,
|
||||
projectDAL,
|
||||
projectMembershipDAL
|
||||
projectDAL
|
||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||
const createAccessApprovalPolicy = async ({
|
||||
name,
|
||||
@ -45,14 +44,15 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
secretPath,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
approvers,
|
||||
approverUserIds,
|
||||
projectSlug,
|
||||
environment
|
||||
environment,
|
||||
enforcementLevel
|
||||
}: TCreateAccessApprovalPolicy) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
if (approvals > approvers.length)
|
||||
if (approvals > approverUserIds.length)
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@ -69,15 +69,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||
|
||||
const secretApprovers = await projectMembershipDAL.find({
|
||||
projectId: project.id,
|
||||
$in: { id: approvers }
|
||||
});
|
||||
|
||||
if (secretApprovers.length !== approvers.length) {
|
||||
throw new BadRequestError({ message: "Approver not found in project" });
|
||||
}
|
||||
|
||||
await verifyApprovers({
|
||||
projectId: project.id,
|
||||
orgId: actorOrgId,
|
||||
@ -85,7 +76,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
secretPath,
|
||||
actorAuthMethod,
|
||||
permissionService,
|
||||
userIds: secretApprovers.map((approver) => approver.userId)
|
||||
userIds: approverUserIds
|
||||
});
|
||||
|
||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||
@ -94,13 +85,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
envId: env.id,
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
await accessApprovalPolicyApproverDAL.insertMany(
|
||||
secretApprovers.map(({ id }) => ({
|
||||
approverId: id,
|
||||
approverUserIds.map((userId) => ({
|
||||
approverUserId: userId,
|
||||
policyId: doc.id
|
||||
})),
|
||||
tx
|
||||
@ -136,14 +128,15 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
|
||||
const updateAccessApprovalPolicy = async ({
|
||||
policyId,
|
||||
approvers,
|
||||
approverUserIds,
|
||||
secretPath,
|
||||
name,
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals
|
||||
approvals,
|
||||
enforcementLevel
|
||||
}: TUpdateAccessApprovalPolicy) => {
|
||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
||||
@ -163,20 +156,12 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
{
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
if (approvers) {
|
||||
// Find the workspace project memberships of the users passed in the approvers array
|
||||
const secretApprovers = await projectMembershipDAL.find(
|
||||
{
|
||||
projectId: accessApprovalPolicy.projectId,
|
||||
$in: { id: approvers }
|
||||
},
|
||||
{ tx }
|
||||
);
|
||||
|
||||
if (approverUserIds) {
|
||||
await verifyApprovers({
|
||||
projectId: accessApprovalPolicy.projectId,
|
||||
orgId: actorOrgId,
|
||||
@ -184,15 +169,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
secretPath: doc.secretPath!,
|
||||
actorAuthMethod,
|
||||
permissionService,
|
||||
userIds: secretApprovers.map((approver) => approver.userId)
|
||||
userIds: approverUserIds
|
||||
});
|
||||
|
||||
if (secretApprovers.length !== approvers.length)
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
||||
await accessApprovalPolicyApproverDAL.insertMany(
|
||||
secretApprovers.map(({ id }) => ({
|
||||
approverId: id,
|
||||
approverUserIds.map((userId) => ({
|
||||
approverUserId: userId,
|
||||
policyId: doc.id
|
||||
})),
|
||||
tx
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
|
||||
import { ActorAuthMethod } from "@app/services/auth/auth-type";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
@ -17,17 +17,19 @@ export type TCreateAccessApprovalPolicy = {
|
||||
approvals: number;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
approvers: string[];
|
||||
approverUserIds: string[];
|
||||
projectSlug: string;
|
||||
name: string;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateAccessApprovalPolicy = {
|
||||
policyId: string;
|
||||
approvals?: number;
|
||||
approvers?: string[];
|
||||
approverUserIds?: string[];
|
||||
secretPath?: string;
|
||||
name?: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteAccessApprovalPolicy = {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests } from "@app/db/schemas";
|
||||
import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests, TUsers } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||
|
||||
@ -40,6 +40,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||
)
|
||||
|
||||
.join<TUsers>(
|
||||
db(TableName.Users).as("requestedByUser"),
|
||||
`${TableName.AccessApprovalRequest}.requestedByUserId`,
|
||||
`requestedByUser.id`
|
||||
)
|
||||
|
||||
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||
|
||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||
@ -48,10 +54,11 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
db.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
|
||||
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
|
||||
)
|
||||
|
||||
.select(db.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
|
||||
.select(
|
||||
db.ref("projectId").withSchema(TableName.Environment),
|
||||
@ -60,15 +67,20 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
|
||||
.select(
|
||||
db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"),
|
||||
db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
|
||||
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
|
||||
)
|
||||
|
||||
// TODO: ADD SUPPORT FOR GROUPS!!!!
|
||||
.select(
|
||||
db
|
||||
.ref("projectMembershipId")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("privilegeMembershipId"),
|
||||
db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
|
||||
db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
|
||||
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
|
||||
db.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
|
||||
|
||||
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeUserId"),
|
||||
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeMembershipId"),
|
||||
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
|
||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
|
||||
@ -98,11 +110,21 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
name: doc.policyName,
|
||||
approvals: doc.policyApprovals,
|
||||
secretPath: doc.policySecretPath,
|
||||
enforcementLevel: doc.policyEnforcementLevel,
|
||||
envId: doc.policyEnvId
|
||||
},
|
||||
requestedByUser: {
|
||||
userId: doc.requestedByUserId,
|
||||
email: doc.requestedByUserEmail,
|
||||
firstName: doc.requestedByUserFirstName,
|
||||
lastName: doc.requestedByUserLastName,
|
||||
username: doc.requestedByUserUsername
|
||||
},
|
||||
privilege: doc.privilegeId
|
||||
? {
|
||||
membershipId: doc.privilegeMembershipId,
|
||||
userId: doc.privilegeUserId,
|
||||
projectId: doc.projectId,
|
||||
isTemporary: doc.privilegeIsTemporary,
|
||||
temporaryMode: doc.privilegeTemporaryMode,
|
||||
temporaryRange: doc.privilegeTemporaryRange,
|
||||
@ -116,11 +138,11 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "reviewerMemberId",
|
||||
key: "reviewerUserId",
|
||||
label: "reviewers" as const,
|
||||
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
|
||||
mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined)
|
||||
},
|
||||
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
|
||||
{ key: "approverUserId", label: "approvers" as const, mapper: ({ approverUserId }) => approverUserId }
|
||||
]
|
||||
});
|
||||
|
||||
@ -144,29 +166,65 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`${TableName.AccessApprovalPolicy}.id`
|
||||
)
|
||||
|
||||
.join<TUsers>(
|
||||
db(TableName.Users).as("requestedByUser"),
|
||||
`${TableName.AccessApprovalRequest}.requestedByUserId`,
|
||||
`requestedByUser.id`
|
||||
)
|
||||
|
||||
.join(
|
||||
TableName.AccessApprovalPolicyApprover,
|
||||
`${TableName.AccessApprovalPolicy}.id`,
|
||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||
)
|
||||
|
||||
.join<TUsers>(
|
||||
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
|
||||
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
||||
"accessApprovalPolicyApproverUser.id"
|
||||
)
|
||||
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalRequestReviewer,
|
||||
`${TableName.AccessApprovalRequest}.id`,
|
||||
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
||||
)
|
||||
|
||||
.leftJoin<TUsers>(
|
||||
db(TableName.Users).as("accessApprovalReviewerUser"),
|
||||
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
|
||||
`accessApprovalReviewerUser.id`
|
||||
)
|
||||
|
||||
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||
.select(
|
||||
tx.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"),
|
||||
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
|
||||
tx.ref("email").withSchema("accessApprovalPolicyApproverUser").as("approverEmail"),
|
||||
tx.ref("username").withSchema("accessApprovalPolicyApproverUser").as("approverUsername"),
|
||||
tx.ref("firstName").withSchema("accessApprovalPolicyApproverUser").as("approverFirstName"),
|
||||
tx.ref("lastName").withSchema("accessApprovalPolicyApproverUser").as("approverLastName"),
|
||||
tx.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
|
||||
tx.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
|
||||
tx.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
|
||||
tx.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
|
||||
|
||||
tx.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer),
|
||||
|
||||
tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"),
|
||||
|
||||
tx.ref("email").withSchema("accessApprovalReviewerUser").as("reviewerEmail"),
|
||||
tx.ref("username").withSchema("accessApprovalReviewerUser").as("reviewerUsername"),
|
||||
tx.ref("firstName").withSchema("accessApprovalReviewerUser").as("reviewerFirstName"),
|
||||
tx.ref("lastName").withSchema("accessApprovalReviewerUser").as("reviewerLastName"),
|
||||
|
||||
tx.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
|
||||
tx.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
|
||||
tx.ref("projectId").withSchema(TableName.Environment),
|
||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
|
||||
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals")
|
||||
);
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
@ -184,16 +242,47 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
approvals: el.policyApprovals,
|
||||
secretPath: el.policySecretPath
|
||||
secretPath: el.policySecretPath,
|
||||
enforcementLevel: el.policyEnforcementLevel
|
||||
},
|
||||
requestedByUser: {
|
||||
userId: el.requestedByUserId,
|
||||
email: el.requestedByUserEmail,
|
||||
firstName: el.requestedByUserFirstName,
|
||||
lastName: el.requestedByUserLastName,
|
||||
username: el.requestedByUserUsername
|
||||
}
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "reviewerMemberId",
|
||||
key: "reviewerUserId",
|
||||
label: "reviewers" as const,
|
||||
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
|
||||
mapper: ({
|
||||
reviewerUserId: userId,
|
||||
reviewerStatus: status,
|
||||
reviewerEmail: email,
|
||||
reviewerLastName: lastName,
|
||||
reviewerUsername: username,
|
||||
reviewerFirstName: firstName
|
||||
}) => (userId ? { userId, status, email, firstName, lastName, username } : undefined)
|
||||
},
|
||||
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId }
|
||||
{
|
||||
key: "approverUserId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({
|
||||
approverUserId,
|
||||
approverEmail: email,
|
||||
approverUsername: username,
|
||||
approverLastName: lastName,
|
||||
approverFirstName: firstName
|
||||
}) => ({
|
||||
userId: approverUserId,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
username
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
if (!formatedDoc?.[0]) return;
|
||||
@ -231,7 +320,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
.where(`${TableName.Environment}.projectId`, projectId)
|
||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
|
||||
.select(db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"));
|
||||
.select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"));
|
||||
|
||||
const formattedRequests = sqlNestRelationships({
|
||||
data: accessRequests,
|
||||
@ -241,9 +330,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "reviewerMemberId",
|
||||
key: "reviewerUserId",
|
||||
label: "reviewers" as const,
|
||||
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
|
||||
mapper: ({ reviewerUserId: reviewer, reviewerStatus: status }) =>
|
||||
reviewer ? { reviewer, status } : undefined
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -52,7 +52,10 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
||||
>;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
userDAL: Pick<TUserDALFactory, "findUserByProjectMembershipId" | "findUsersByProjectMembershipIds">;
|
||||
userDAL: Pick<
|
||||
TUserDALFactory,
|
||||
"findUserByProjectMembershipId" | "findUsersByProjectMembershipIds" | "find" | "findById"
|
||||
>;
|
||||
};
|
||||
|
||||
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
||||
@ -94,7 +97,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
);
|
||||
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
|
||||
|
||||
const requestedByUser = await userDAL.findUserByProjectMembershipId(membership.id);
|
||||
const requestedByUser = await userDAL.findById(actorId);
|
||||
if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" });
|
||||
|
||||
await projectDAL.checkProjectUpgradeStatus(project.id);
|
||||
@ -114,13 +117,15 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
policyId: policy.id
|
||||
});
|
||||
|
||||
const approverUsers = await userDAL.findUsersByProjectMembershipIds(
|
||||
approvers.map((approver) => approver.approverId)
|
||||
);
|
||||
const approverUsers = await userDAL.find({
|
||||
$in: {
|
||||
id: approvers.map((approver) => approver.approverUserId)
|
||||
}
|
||||
});
|
||||
|
||||
const duplicateRequests = await accessApprovalRequestDAL.find({
|
||||
policyId: policy.id,
|
||||
requestedBy: membership.id,
|
||||
requestedByUserId: actorId,
|
||||
permissions: JSON.stringify(requestedPermissions),
|
||||
isTemporary
|
||||
});
|
||||
@ -153,7 +158,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
const approvalRequest = await accessApprovalRequestDAL.create(
|
||||
{
|
||||
policyId: policy.id,
|
||||
requestedBy: membership.id,
|
||||
requestedByUserId: actorId,
|
||||
temporaryRange: temporaryRange || null,
|
||||
permissions: JSON.stringify(requestedPermissions),
|
||||
isTemporary
|
||||
@ -212,7 +217,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
|
||||
|
||||
if (authorProjectMembershipId) {
|
||||
requests = requests.filter((request) => request.requestedBy === authorProjectMembershipId);
|
||||
requests = requests.filter((request) => request.requestedByUserId === actorId);
|
||||
}
|
||||
|
||||
if (envSlug) {
|
||||
@ -246,8 +251,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
|
||||
if (
|
||||
!hasRole(ProjectMembershipRole.Admin) &&
|
||||
accessApprovalRequest.requestedBy !== membership.id && // The request wasn't made by the current user
|
||||
!policy.approvers.find((approverId) => approverId === membership.id) // The request isn't performed by an assigned approver
|
||||
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
|
||||
!policy.approvers.find((approver) => approver.userId === actorId) // The request isn't performed by an assigned approver
|
||||
) {
|
||||
throw new UnauthorizedError({ message: "You are not authorized to approve this request" });
|
||||
}
|
||||
@ -273,7 +278,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
const review = await accessApprovalRequestReviewerDAL.findOne(
|
||||
{
|
||||
requestId: accessApprovalRequest.id,
|
||||
member: membership.id
|
||||
reviewerUserId: actorId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@ -282,7 +287,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
{
|
||||
status,
|
||||
requestId: accessApprovalRequest.id,
|
||||
member: membership.id
|
||||
reviewerUserId: actorId
|
||||
},
|
||||
tx
|
||||
);
|
||||
@ -303,7 +308,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
// Permanent access
|
||||
const privilege = await additionalPrivilegeDAL.create(
|
||||
{
|
||||
projectMembershipId: accessApprovalRequest.requestedBy,
|
||||
userId: accessApprovalRequest.requestedByUserId,
|
||||
projectId: accessApprovalRequest.projectId,
|
||||
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||
permissions: JSON.stringify(accessApprovalRequest.permissions)
|
||||
},
|
||||
@ -317,7 +323,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
|
||||
const privilege = await additionalPrivilegeDAL.create(
|
||||
{
|
||||
projectMembershipId: accessApprovalRequest.requestedBy,
|
||||
userId: accessApprovalRequest.requestedByUserId,
|
||||
projectId: accessApprovalRequest.projectId,
|
||||
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||
permissions: JSON.stringify(accessApprovalRequest.permissions),
|
||||
isTemporary: true,
|
||||
|
@ -75,15 +75,16 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
.del()
|
||||
.returning("id");
|
||||
numberOfRetryOnFailure = 0; // reset
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100); // time to breathe for db
|
||||
});
|
||||
} catch (error) {
|
||||
numberOfRetryOnFailure += 1;
|
||||
logger.error(error, "Failed to delete audit log on pruning");
|
||||
} finally {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 10); // time to breathe for db
|
||||
});
|
||||
}
|
||||
} while (deletedAuditLogIds.length > 0 && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE);
|
||||
} while (deletedAuditLogIds.length > 0 || numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE);
|
||||
};
|
||||
|
||||
return { ...auditLogOrm, pruneAuditLog, find };
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
@ -61,6 +62,10 @@ export const auditLogServiceFactory = ({
|
||||
};
|
||||
|
||||
const createAuditLog = async (data: TCreateAuditLogDTO) => {
|
||||
const appCfg = getConfig();
|
||||
if (appCfg.DISABLE_AUDIT_LOG_GENERATION) {
|
||||
return;
|
||||
}
|
||||
// add all cases in which project id or org id cannot be added
|
||||
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" });
|
||||
|
@ -2,6 +2,7 @@ import { TProjectPermission } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
||||
|
||||
export type TListProjectAuditLogDTO = {
|
||||
auditLogActor?: string;
|
||||
@ -106,6 +107,7 @@ export enum EventType {
|
||||
CREATE_ENVIRONMENT = "create-environment",
|
||||
UPDATE_ENVIRONMENT = "update-environment",
|
||||
DELETE_ENVIRONMENT = "delete-environment",
|
||||
GET_ENVIRONMENT = "get-environment",
|
||||
ADD_WORKSPACE_MEMBER = "add-workspace-member",
|
||||
ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members",
|
||||
REMOVE_WORKSPACE_MEMBER = "remove-workspace-member",
|
||||
@ -129,23 +131,42 @@ export enum EventType {
|
||||
GET_CA = "get-certificate-authority",
|
||||
UPDATE_CA = "update-certificate-authority",
|
||||
DELETE_CA = "delete-certificate-authority",
|
||||
RENEW_CA = "renew-certificate-authority",
|
||||
GET_CA_CSR = "get-certificate-authority-csr",
|
||||
GET_CA_CERTS = "get-certificate-authority-certs",
|
||||
GET_CA_CERT = "get-certificate-authority-cert",
|
||||
SIGN_INTERMEDIATE = "sign-intermediate",
|
||||
IMPORT_CA_CERT = "import-certificate-authority-cert",
|
||||
GET_CA_CRL = "get-certificate-authority-crl",
|
||||
GET_CA_CRLS = "get-certificate-authority-crls",
|
||||
ISSUE_CERT = "issue-cert",
|
||||
SIGN_CERT = "sign-cert",
|
||||
GET_CERT = "get-cert",
|
||||
DELETE_CERT = "delete-cert",
|
||||
REVOKE_CERT = "revoke-cert",
|
||||
GET_CERT_BODY = "get-cert-body",
|
||||
CREATE_PKI_ALERT = "create-pki-alert",
|
||||
GET_PKI_ALERT = "get-pki-alert",
|
||||
UPDATE_PKI_ALERT = "update-pki-alert",
|
||||
DELETE_PKI_ALERT = "delete-pki-alert",
|
||||
CREATE_PKI_COLLECTION = "create-pki-collection",
|
||||
GET_PKI_COLLECTION = "get-pki-collection",
|
||||
UPDATE_PKI_COLLECTION = "update-pki-collection",
|
||||
DELETE_PKI_COLLECTION = "delete-pki-collection",
|
||||
GET_PKI_COLLECTION_ITEMS = "get-pki-collection-items",
|
||||
ADD_PKI_COLLECTION_ITEM = "add-pki-collection-item",
|
||||
DELETE_PKI_COLLECTION_ITEM = "delete-pki-collection-item",
|
||||
CREATE_KMS = "create-kms",
|
||||
UPDATE_KMS = "update-kms",
|
||||
DELETE_KMS = "delete-kms",
|
||||
GET_KMS = "get-kms",
|
||||
UPDATE_PROJECT_KMS = "update-project-kms",
|
||||
GET_PROJECT_KMS_BACKUP = "get-project-kms-backup",
|
||||
LOAD_PROJECT_KMS_BACKUP = "load-project-kms-backup"
|
||||
LOAD_PROJECT_KMS_BACKUP = "load-project-kms-backup",
|
||||
ORG_ADMIN_ACCESS_PROJECT = "org-admin-accessed-project",
|
||||
CREATE_CERTIFICATE_TEMPLATE = "create-certificate-template",
|
||||
UPDATE_CERTIFICATE_TEMPLATE = "update-certificate-template",
|
||||
DELETE_CERTIFICATE_TEMPLATE = "delete-certificate-template",
|
||||
GET_CERTIFICATE_TEMPLATE = "get-certificate-template"
|
||||
}
|
||||
|
||||
interface UserActorMetadata {
|
||||
@ -335,6 +356,7 @@ interface DeleteIntegrationEvent {
|
||||
targetServiceId?: string;
|
||||
path?: string;
|
||||
region?: string;
|
||||
shouldDeleteIntegrationSecrets?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@ -838,6 +860,13 @@ interface CreateEnvironmentEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetEnvironmentEvent {
|
||||
type: EventType.GET_ENVIRONMENT;
|
||||
metadata: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateEnvironmentEvent {
|
||||
type: EventType.UPDATE_ENVIRONMENT;
|
||||
metadata: {
|
||||
@ -1085,6 +1114,14 @@ interface DeleteCa {
|
||||
};
|
||||
}
|
||||
|
||||
interface RenewCa {
|
||||
type: EventType.RENEW_CA;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCaCsr {
|
||||
type: EventType.GET_CA_CSR;
|
||||
metadata: {
|
||||
@ -1093,6 +1130,14 @@ interface GetCaCsr {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCaCerts {
|
||||
type: EventType.GET_CA_CERTS;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCaCert {
|
||||
type: EventType.GET_CA_CERT;
|
||||
metadata: {
|
||||
@ -1118,8 +1163,8 @@ interface ImportCaCert {
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCaCrl {
|
||||
type: EventType.GET_CA_CRL;
|
||||
interface GetCaCrls {
|
||||
type: EventType.GET_CA_CRLS;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
@ -1135,6 +1180,15 @@ interface IssueCert {
|
||||
};
|
||||
}
|
||||
|
||||
interface SignCert {
|
||||
type: EventType.SIGN_CERT;
|
||||
metadata: {
|
||||
caId: string;
|
||||
dn: string;
|
||||
serialNumber: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCert {
|
||||
type: EventType.GET_CERT;
|
||||
metadata: {
|
||||
@ -1171,6 +1225,95 @@ interface GetCertBody {
|
||||
};
|
||||
}
|
||||
|
||||
interface CreatePkiAlert {
|
||||
type: EventType.CREATE_PKI_ALERT;
|
||||
metadata: {
|
||||
pkiAlertId: string;
|
||||
pkiCollectionId: string;
|
||||
name: string;
|
||||
alertBeforeDays: number;
|
||||
recipientEmails: string;
|
||||
};
|
||||
}
|
||||
interface GetPkiAlert {
|
||||
type: EventType.GET_PKI_ALERT;
|
||||
metadata: {
|
||||
pkiAlertId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdatePkiAlert {
|
||||
type: EventType.UPDATE_PKI_ALERT;
|
||||
metadata: {
|
||||
pkiAlertId: string;
|
||||
pkiCollectionId?: string;
|
||||
name?: string;
|
||||
alertBeforeDays?: number;
|
||||
recipientEmails?: string;
|
||||
};
|
||||
}
|
||||
interface DeletePkiAlert {
|
||||
type: EventType.DELETE_PKI_ALERT;
|
||||
metadata: {
|
||||
pkiAlertId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreatePkiCollection {
|
||||
type: EventType.CREATE_PKI_COLLECTION;
|
||||
metadata: {
|
||||
pkiCollectionId: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetPkiCollection {
|
||||
type: EventType.GET_PKI_COLLECTION;
|
||||
metadata: {
|
||||
pkiCollectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdatePkiCollection {
|
||||
type: EventType.UPDATE_PKI_COLLECTION;
|
||||
metadata: {
|
||||
pkiCollectionId: string;
|
||||
name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeletePkiCollection {
|
||||
type: EventType.DELETE_PKI_COLLECTION;
|
||||
metadata: {
|
||||
pkiCollectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetPkiCollectionItems {
|
||||
type: EventType.GET_PKI_COLLECTION_ITEMS;
|
||||
metadata: {
|
||||
pkiCollectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AddPkiCollectionItem {
|
||||
type: EventType.ADD_PKI_COLLECTION_ITEM;
|
||||
metadata: {
|
||||
pkiCollectionItemId: string;
|
||||
pkiCollectionId: string;
|
||||
type: PkiItemType;
|
||||
itemId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeletePkiCollectionItem {
|
||||
type: EventType.DELETE_PKI_COLLECTION_ITEM;
|
||||
metadata: {
|
||||
pkiCollectionItemId: string;
|
||||
pkiCollectionId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateKmsEvent {
|
||||
type: EventType.CREATE_KMS;
|
||||
metadata: {
|
||||
@ -1227,6 +1370,56 @@ interface LoadProjectKmsBackupEvent {
|
||||
metadata: Record<string, string>; // no metadata yet
|
||||
}
|
||||
|
||||
interface CreateCertificateTemplate {
|
||||
type: EventType.CREATE_CERTIFICATE_TEMPLATE;
|
||||
metadata: {
|
||||
certificateTemplateId: string;
|
||||
caId: string;
|
||||
pkiCollectionId?: string;
|
||||
name: string;
|
||||
commonName: string;
|
||||
subjectAlternativeName: string;
|
||||
ttl: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetCertificateTemplate {
|
||||
type: EventType.GET_CERTIFICATE_TEMPLATE;
|
||||
metadata: {
|
||||
certificateTemplateId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateCertificateTemplate {
|
||||
type: EventType.UPDATE_CERTIFICATE_TEMPLATE;
|
||||
metadata: {
|
||||
certificateTemplateId: string;
|
||||
caId: string;
|
||||
pkiCollectionId?: string;
|
||||
name: string;
|
||||
commonName: string;
|
||||
subjectAlternativeName: string;
|
||||
ttl: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteCertificateTemplate {
|
||||
type: EventType.DELETE_CERTIFICATE_TEMPLATE;
|
||||
metadata: {
|
||||
certificateTemplateId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface OrgAdminAccessProjectEvent {
|
||||
type: EventType.ORG_ADMIN_ACCESS_PROJECT;
|
||||
metadata: {
|
||||
userId: string;
|
||||
username: string;
|
||||
email: string;
|
||||
projectId: string;
|
||||
}; // no metadata yet
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| GetSecretsEvent
|
||||
| GetSecretEvent
|
||||
@ -1293,6 +1486,7 @@ export type Event =
|
||||
| UpdateIdentityOidcAuthEvent
|
||||
| GetIdentityOidcAuthEvent
|
||||
| CreateEnvironmentEvent
|
||||
| GetEnvironmentEvent
|
||||
| UpdateEnvironmentEvent
|
||||
| DeleteEnvironmentEvent
|
||||
| AddWorkspaceMemberEvent
|
||||
@ -1318,20 +1512,39 @@ export type Event =
|
||||
| GetCa
|
||||
| UpdateCa
|
||||
| DeleteCa
|
||||
| RenewCa
|
||||
| GetCaCsr
|
||||
| GetCaCerts
|
||||
| GetCaCert
|
||||
| SignIntermediate
|
||||
| ImportCaCert
|
||||
| GetCaCrl
|
||||
| GetCaCrls
|
||||
| IssueCert
|
||||
| SignCert
|
||||
| GetCert
|
||||
| DeleteCert
|
||||
| RevokeCert
|
||||
| GetCertBody
|
||||
| CreatePkiAlert
|
||||
| GetPkiAlert
|
||||
| UpdatePkiAlert
|
||||
| DeletePkiAlert
|
||||
| CreatePkiCollection
|
||||
| GetPkiCollection
|
||||
| UpdatePkiCollection
|
||||
| DeletePkiCollection
|
||||
| GetPkiCollectionItems
|
||||
| AddPkiCollectionItem
|
||||
| DeletePkiCollectionItem
|
||||
| CreateKmsEvent
|
||||
| UpdateKmsEvent
|
||||
| DeleteKmsEvent
|
||||
| GetKmsEvent
|
||||
| UpdateProjectKmsEvent
|
||||
| GetProjectKmsBackupEvent
|
||||
| LoadProjectKmsBackupEvent;
|
||||
| LoadProjectKmsBackupEvent
|
||||
| OrgAdminAccessProjectEvent
|
||||
| CreateCertificateTemplate
|
||||
| UpdateCertificateTemplate
|
||||
| GetCertificateTemplate
|
||||
| DeleteCertificateTemplate;
|
||||
|
@ -2,24 +2,24 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||
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 { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||
|
||||
import { TGetCrl } from "./certificate-authority-crl-types";
|
||||
import { TGetCaCrlsDTO, TGetCrlById } from "./certificate-authority-crl-types";
|
||||
|
||||
type TCertificateAuthorityCrlServiceFactoryDep = {
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "find" | "findById">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
||||
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
// licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TCertificateAuthorityCrlServiceFactory = ReturnType<typeof certificateAuthorityCrlServiceFactory>;
|
||||
@ -29,13 +29,42 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
certificateAuthorityCrlDAL,
|
||||
projectDAL,
|
||||
kmsService,
|
||||
permissionService,
|
||||
licenseService
|
||||
permissionService // licenseService
|
||||
}: TCertificateAuthorityCrlServiceFactoryDep) => {
|
||||
/**
|
||||
* Return the Certificate Revocation List (CRL) for CA with id [caId]
|
||||
* Return CRL with id [crlId]
|
||||
*/
|
||||
const getCaCrl = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCrl) => {
|
||||
const getCrlById = async (crlId: TGetCrlById) => {
|
||||
const caCrl = await certificateAuthorityCrlDAL.findById(crlId);
|
||||
if (!caCrl) throw new NotFoundError({ message: "CRL not found" });
|
||||
|
||||
const ca = await certificateAuthorityDAL.findById(caCrl.caId);
|
||||
|
||||
const keyId = await getProjectKmsCertificateKeyId({
|
||||
projectId: ca.projectId,
|
||||
projectDAL,
|
||||
kmsService
|
||||
});
|
||||
|
||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||
kmsId: keyId
|
||||
});
|
||||
|
||||
const decryptedCrl = await kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
|
||||
|
||||
const crl = new x509.X509Crl(decryptedCrl);
|
||||
|
||||
return {
|
||||
ca,
|
||||
caCrl,
|
||||
crl: crl.rawData
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of CRL ids for CA with id [caId]
|
||||
*/
|
||||
const getCaCrls = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => {
|
||||
const ca = await certificateAuthorityDAL.findById(caId);
|
||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||
|
||||
@ -52,15 +81,14 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
ProjectPermissionSub.CertificateAuthorities
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan.caCrl)
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to get CA certificate revocation list (CRL) due to plan restriction. Upgrade plan to get the CA CRL."
|
||||
});
|
||||
// const plan = await licenseService.getPlan(actorOrgId);
|
||||
// if (!plan.caCrl)
|
||||
// throw new BadRequestError({
|
||||
// message:
|
||||
// "Failed to get CA certificate revocation lists (CRLs) due to plan restriction. Upgrade plan to get the CA CRL."
|
||||
// });
|
||||
|
||||
const caCrl = await certificateAuthorityCrlDAL.findOne({ caId: ca.id });
|
||||
if (!caCrl) throw new BadRequestError({ message: "CRL not found" });
|
||||
const caCrls = await certificateAuthorityCrlDAL.find({ caId: ca.id }, { sort: [["createdAt", "desc"]] });
|
||||
|
||||
const keyId = await getProjectKmsCertificateKeyId({
|
||||
projectId: ca.projectId,
|
||||
@ -72,15 +100,23 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
kmsId: keyId
|
||||
});
|
||||
|
||||
const decryptedCrl = await kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
|
||||
const crl = new x509.X509Crl(decryptedCrl);
|
||||
const decryptedCrls = await Promise.all(
|
||||
caCrls.map(async (caCrl) => {
|
||||
const decryptedCrl = await kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
|
||||
const crl = new x509.X509Crl(decryptedCrl);
|
||||
|
||||
const base64crl = crl.toString("base64");
|
||||
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
|
||||
const base64crl = crl.toString("base64");
|
||||
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
|
||||
return {
|
||||
id: caCrl.id,
|
||||
crl: crlPem
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
crl: crlPem,
|
||||
ca
|
||||
ca,
|
||||
crls: decryptedCrls
|
||||
};
|
||||
};
|
||||
|
||||
@ -166,7 +202,8 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
// };
|
||||
|
||||
return {
|
||||
getCaCrl
|
||||
getCrlById,
|
||||
getCaCrls
|
||||
// rotateCaCrl
|
||||
};
|
||||
};
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export type TGetCrl = {
|
||||
export type TGetCrlById = string;
|
||||
|
||||
export type TGetCaCrlsDTO = {
|
||||
caId: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
@ -12,10 +12,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
||||
|
||||
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
|
||||
.count("*")
|
||||
.where({ dynamicSecretId })
|
||||
.first();
|
||||
const doc = await (tx || db)(TableName.DynamicSecretLease).count("*").where({ dynamicSecretId }).first();
|
||||
return parseInt(doc || "0", 10);
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
|
||||
@ -24,7 +21,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
|
||||
const doc = await (tx || db)(TableName.DynamicSecretLease)
|
||||
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
|
||||
.first()
|
||||
.join(
|
||||
|
@ -336,31 +336,36 @@ export const removeUsersFromGroupByUserIds = async ({
|
||||
)
|
||||
);
|
||||
|
||||
// TODO: this part can be optimized
|
||||
for await (const userId of userIds) {
|
||||
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(userId, group.id, projectIds, tx);
|
||||
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
||||
const promises: Array<Promise<void>> = [];
|
||||
for (const userId of userIds) {
|
||||
promises.push(
|
||||
(async () => {
|
||||
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(userId, group.id, projectIds, tx);
|
||||
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
||||
|
||||
if (projectsToDeleteKeyFor.length) {
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
receiverId: userId,
|
||||
$in: {
|
||||
projectId: projectsToDeleteKeyFor
|
||||
}
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
if (projectsToDeleteKeyFor.length) {
|
||||
await projectKeyDAL.delete(
|
||||
{
|
||||
receiverId: userId,
|
||||
$in: {
|
||||
projectId: projectsToDeleteKeyFor
|
||||
}
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
await userGroupMembershipDAL.delete(
|
||||
{
|
||||
groupId: group.id,
|
||||
userId
|
||||
},
|
||||
tx
|
||||
await userGroupMembershipDAL.delete(
|
||||
{
|
||||
groupId: group.id,
|
||||
userId
|
||||
},
|
||||
tx
|
||||
);
|
||||
})()
|
||||
);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
if (membersToRemoveFromGroupPending.length) {
|
||||
|
@ -162,17 +162,50 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findUserGroupMembershipsInOrg = async (userId: string, orgId: string) => {
|
||||
const findGroupMembershipsByUserIdInOrg = async (userId: string, orgId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.UserGroupMembership)
|
||||
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
|
||||
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
|
||||
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.UserGroupMembership}.userId`, userId)
|
||||
.where(`${TableName.Groups}.orgId`, orgId);
|
||||
.where(`${TableName.Groups}.orgId`, orgId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
||||
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
|
||||
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
||||
db.ref("lastName").withSchema(TableName.Users).as("lastName")
|
||||
);
|
||||
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "findTest" });
|
||||
throw new DatabaseError({ error, name: "Find group memberships by user id in org" });
|
||||
}
|
||||
};
|
||||
|
||||
const findGroupMembershipsByGroupIdInOrg = async (groupId: string, orgId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.UserGroupMembership)
|
||||
.join(TableName.Groups, `${TableName.UserGroupMembership}.groupId`, `${TableName.Groups}.id`)
|
||||
.join(TableName.OrgMembership, `${TableName.UserGroupMembership}.userId`, `${TableName.OrgMembership}.userId`)
|
||||
.join(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.where(`${TableName.Groups}.id`, groupId)
|
||||
.where(`${TableName.Groups}.orgId`, orgId)
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("groupId").withSchema(TableName.UserGroupMembership),
|
||||
db.ref("name").withSchema(TableName.Groups).as("groupName"),
|
||||
db.ref("id").withSchema(TableName.OrgMembership).as("orgMembershipId"),
|
||||
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
||||
db.ref("lastName").withSchema(TableName.Users).as("lastName")
|
||||
);
|
||||
return docs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find group memberships by group id in org" });
|
||||
}
|
||||
};
|
||||
|
||||
@ -182,6 +215,7 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
||||
findUserGroupMembershipsInProject,
|
||||
findGroupMembersNotInProject,
|
||||
deletePendingUserGroupMembershipsByUserIds,
|
||||
findUserGroupMembershipsInOrg
|
||||
findGroupMembershipsByUserIdInOrg,
|
||||
findGroupMembershipsByGroupIdInOrg
|
||||
};
|
||||
};
|
||||
|
@ -40,7 +40,12 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
secretRotation: true,
|
||||
caCrl: false,
|
||||
instanceUserManagement: false,
|
||||
externalKms: false
|
||||
externalKms: false,
|
||||
rateLimits: {
|
||||
readLimit: 60,
|
||||
writeLimit: 200,
|
||||
secretsLimit: 40
|
||||
}
|
||||
});
|
||||
|
||||
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||
|
@ -58,6 +58,11 @@ export type TFeatureSet = {
|
||||
caCrl: false;
|
||||
instanceUserManagement: false;
|
||||
externalKms: false;
|
||||
rateLimits: {
|
||||
readLimit: number;
|
||||
writeLimit: number;
|
||||
secretsLimit: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type TOrgPlansTableDTO = {
|
||||
|
@ -9,6 +9,10 @@ export enum OrgPermissionActions {
|
||||
Delete = "delete"
|
||||
}
|
||||
|
||||
export enum OrgPermissionAdminConsoleAction {
|
||||
AccessAllProjects = "access-all-projects"
|
||||
}
|
||||
|
||||
export enum OrgPermissionSubjects {
|
||||
Workspace = "workspace",
|
||||
Role = "role",
|
||||
@ -22,7 +26,8 @@ export enum OrgPermissionSubjects {
|
||||
Billing = "billing",
|
||||
SecretScanning = "secret-scanning",
|
||||
Identity = "identity",
|
||||
Kms = "kms"
|
||||
Kms = "kms",
|
||||
AdminConsole = "organization-admin-console"
|
||||
}
|
||||
|
||||
export type OrgPermissionSet =
|
||||
@ -39,7 +44,8 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms];
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
||||
|
||||
const buildAdminPermission = () => {
|
||||
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||
@ -107,6 +113,8 @@ const buildAdminPermission = () => {
|
||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms);
|
||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
|
||||
|
||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||
|
||||
return build({ conditionsMatcher });
|
||||
};
|
||||
|
||||
@ -118,7 +126,6 @@ const buildMemberPermission = () => {
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||
|
@ -66,6 +66,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||
`${TableName.GroupProjectMembership}.id`
|
||||
)
|
||||
|
||||
.leftJoin(
|
||||
TableName.ProjectRoles,
|
||||
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||
@ -73,6 +74,12 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||
|
||||
.leftJoin(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
`${TableName.GroupProjectMembership}.projectId`,
|
||||
`${TableName.Project}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.GroupProjectMembershipRole))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
||||
@ -81,9 +88,30 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
db.ref("projectId").withSchema(TableName.GroupProjectMembership),
|
||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||
db.ref("orgId").withSchema(TableName.Project),
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
||||
)
|
||||
.select("permissions");
|
||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||
|
||||
db.ref("permissions").withSchema(TableName.ProjectRoles).as("permissions"),
|
||||
// db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("apPermissions")
|
||||
// Additional Privileges
|
||||
db.ref("id").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApId"),
|
||||
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApPermissions"),
|
||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
|
||||
|
||||
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApProjectId"),
|
||||
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApUserId"),
|
||||
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userApTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("userApTemporaryAccessEndTime")
|
||||
);
|
||||
// .select(`${TableName.ProjectRoles}.permissions`);
|
||||
|
||||
const docs = await db(TableName.ProjectMembership)
|
||||
.join(
|
||||
@ -98,12 +126,13 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`,
|
||||
`${TableName.ProjectMembership}.id`
|
||||
`${TableName.ProjectUserAdditionalPrivilege}.projectId`,
|
||||
`${TableName.ProjectMembership}.projectId`
|
||||
)
|
||||
|
||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||
.where("userId", userId)
|
||||
.where(`${TableName.ProjectMembership}.userId`, userId)
|
||||
.where(`${TableName.ProjectMembership}.projectId`, projectId)
|
||||
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
|
||||
.select(
|
||||
@ -120,6 +149,10 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
|
||||
|
||||
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApProjectId"),
|
||||
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApUserId"),
|
||||
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
@ -198,6 +231,31 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
permissions: z.unknown(),
|
||||
customRoleSlug: z.string().optional().nullable()
|
||||
}).parse(data)
|
||||
},
|
||||
{
|
||||
key: "userApId",
|
||||
label: "additionalPrivileges" as const,
|
||||
mapper: ({
|
||||
userApId,
|
||||
userApProjectId,
|
||||
userApUserId,
|
||||
userApPermissions,
|
||||
userApIsTemporary,
|
||||
userApTemporaryMode,
|
||||
userApTemporaryRange,
|
||||
userApTemporaryAccessEndTime,
|
||||
userApTemporaryAccessStartTime
|
||||
}) => ({
|
||||
id: userApId,
|
||||
userId: userApUserId,
|
||||
projectId: userApProjectId,
|
||||
permissions: userApPermissions,
|
||||
temporaryRange: userApTemporaryRange,
|
||||
temporaryMode: userApTemporaryMode,
|
||||
temporaryAccessEndTime: userApTemporaryAccessEndTime,
|
||||
temporaryAccessStartTime: userApTemporaryAccessStartTime,
|
||||
isTemporary: userApIsTemporary
|
||||
})
|
||||
}
|
||||
]
|
||||
})
|
||||
@ -218,15 +276,24 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
) ?? [];
|
||||
|
||||
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
);
|
||||
const activeAdditionalPrivileges =
|
||||
permission?.[0]?.additionalPrivileges?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime }) =>
|
||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||
) ?? [];
|
||||
|
||||
const activeGroupAdditionalPrivileges =
|
||||
groupPermission?.[0]?.additionalPrivileges?.filter(
|
||||
({ isTemporary, temporaryAccessEndTime, userId: apUserId, projectId: apProjectId }) =>
|
||||
apProjectId === projectId &&
|
||||
apUserId === userId &&
|
||||
(!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime))
|
||||
) ?? [];
|
||||
|
||||
return {
|
||||
...(permission[0] || groupPermission[0]),
|
||||
roles: [...activeRoles, ...activeGroupRoles],
|
||||
additionalPrivileges: activeAdditionalPrivileges
|
||||
additionalPrivileges: [...activeAdditionalPrivileges, ...activeGroupAdditionalPrivileges]
|
||||
};
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
||||
|
@ -23,12 +23,16 @@ export enum ProjectPermissionSub {
|
||||
IpAllowList = "ip-allowlist",
|
||||
Project = "workspace",
|
||||
Secrets = "secrets",
|
||||
SecretFolders = "secret-folders",
|
||||
SecretRollback = "secret-rollback",
|
||||
SecretApproval = "secret-approval",
|
||||
SecretRotation = "secret-rotation",
|
||||
Identity = "identity",
|
||||
CertificateAuthorities = "certificate-authorities",
|
||||
Certificates = "certificates",
|
||||
CertificateTemplates = "certificate-templates",
|
||||
PkiAlerts = "pki-alerts",
|
||||
PkiCollections = "pki-collections",
|
||||
Kms = "kms"
|
||||
}
|
||||
|
||||
@ -42,6 +46,10 @@ export type ProjectPermissionSet =
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub.SecretFolders | (ForcedSubject<ProjectPermissionSub.SecretFolders> & SubjectFields)
|
||||
]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
||||
@ -58,6 +66,9 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||
@ -156,6 +167,21 @@ const buildAdminPermissionRules = () => {
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections);
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections);
|
||||
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
||||
|
||||
@ -232,6 +258,11 @@ const buildMemberPermissionRules = () => {
|
||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
||||
|
||||
return rules;
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
|
||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
@ -53,12 +53,17 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||
|
||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({ slug, projectMembershipId });
|
||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||
slug,
|
||||
projectId: projectMembership.projectId,
|
||||
userId: projectMembership.userId
|
||||
});
|
||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||
|
||||
if (!dto.isTemporary) {
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||
projectMembershipId,
|
||||
userId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId,
|
||||
slug,
|
||||
permissions: customPermission
|
||||
});
|
||||
@ -67,7 +72,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
|
||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||
projectMembershipId,
|
||||
projectId: projectMembership.projectId,
|
||||
userId: projectMembership.userId,
|
||||
slug,
|
||||
permissions: customPermission,
|
||||
isTemporary: true,
|
||||
@ -90,7 +96,11 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||
const projectMembership = await projectMembershipDAL.findOne({
|
||||
userId: userPrivilege.userId,
|
||||
projectId: userPrivilege.projectId
|
||||
});
|
||||
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@ -105,7 +115,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
if (dto?.slug) {
|
||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||
slug: dto.slug,
|
||||
projectMembershipId: projectMembership.id
|
||||
userId: projectMembership.id,
|
||||
projectId: projectMembership.projectId
|
||||
});
|
||||
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
||||
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||
@ -138,7 +149,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||
const projectMembership = await projectMembershipDAL.findOne({
|
||||
userId: userPrivilege.userId,
|
||||
projectId: userPrivilege.projectId
|
||||
});
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@ -164,7 +178,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
||||
|
||||
const projectMembership = await projectMembershipDAL.findById(userPrivilege.projectMembershipId);
|
||||
const projectMembership = await projectMembershipDAL.findOne({
|
||||
userId: userPrivilege.userId,
|
||||
projectId: userPrivilege.projectId
|
||||
});
|
||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@ -198,7 +215,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||
|
||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find({ projectMembershipId });
|
||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find({
|
||||
userId: projectMembership.userId,
|
||||
projectId: projectMembership.projectId
|
||||
});
|
||||
return userPrivileges;
|
||||
};
|
||||
|
||||
|
@ -4,17 +4,16 @@ import { logger } from "@app/lib/logger";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TRateLimitDALFactory } from "./rate-limit-dal";
|
||||
import { TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types";
|
||||
import { RateLimitConfiguration, TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types";
|
||||
|
||||
let rateLimitMaxConfiguration = {
|
||||
let rateLimitMaxConfiguration: RateLimitConfiguration = {
|
||||
readLimit: 60,
|
||||
publicEndpointLimit: 30,
|
||||
writeLimit: 200,
|
||||
secretsLimit: 60,
|
||||
authRateLimit: 60,
|
||||
inviteUserRateLimit: 30,
|
||||
mfaRateLimit: 20,
|
||||
creationLimit: 30
|
||||
mfaRateLimit: 20
|
||||
};
|
||||
|
||||
Object.freeze(rateLimitMaxConfiguration);
|
||||
@ -67,8 +66,7 @@ export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateL
|
||||
secretsLimit: rateLimit.secretsRateLimit,
|
||||
authRateLimit: rateLimit.authRateLimit,
|
||||
inviteUserRateLimit: rateLimit.inviteUserRateLimit,
|
||||
mfaRateLimit: rateLimit.mfaRateLimit,
|
||||
creationLimit: rateLimit.creationLimit
|
||||
mfaRateLimit: rateLimit.mfaRateLimit
|
||||
};
|
||||
|
||||
logger.info(`syncRateLimitConfiguration: rate limit configuration: %o`, newRateLimitMaxConfiguration);
|
||||
|
@ -5,7 +5,6 @@ export type TRateLimitUpdateDTO = {
|
||||
authRateLimit: number;
|
||||
inviteUserRateLimit: number;
|
||||
mfaRateLimit: number;
|
||||
creationLimit: number;
|
||||
publicEndpointLimit: number;
|
||||
};
|
||||
|
||||
@ -14,3 +13,13 @@ export type TRateLimit = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
} & TRateLimitUpdateDTO;
|
||||
|
||||
export type RateLimitConfiguration = {
|
||||
readLimit: number;
|
||||
publicEndpointLimit: number;
|
||||
writeLimit: number;
|
||||
secretsLimit: number;
|
||||
authRateLimit: number;
|
||||
inviteUserRateLimit: number;
|
||||
mfaRateLimit: number;
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-grou
|
||||
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { TOrgPermission } from "@app/lib/types";
|
||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||
@ -30,6 +31,7 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||
import {
|
||||
buildScimGroup,
|
||||
buildScimGroupList,
|
||||
@ -51,6 +53,7 @@ import {
|
||||
TListScimUsers,
|
||||
TListScimUsersDTO,
|
||||
TReplaceScimUserDTO,
|
||||
TScimGroup,
|
||||
TScimTokenJwtPayload,
|
||||
TUpdateScimGroupNamePatchDTO,
|
||||
TUpdateScimGroupNamePutDTO,
|
||||
@ -83,13 +86,15 @@ type TScimServiceFactoryDep = {
|
||||
| "insertMany"
|
||||
| "filterProjectsByUserMembership"
|
||||
| "delete"
|
||||
| "findUserGroupMembershipsInOrg"
|
||||
| "findGroupMembershipsByUserIdInOrg"
|
||||
| "findGroupMembershipsByGroupIdInOrg"
|
||||
>;
|
||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||
};
|
||||
|
||||
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
|
||||
@ -109,6 +114,7 @@ export const scimServiceFactory = ({
|
||||
projectKeyDAL,
|
||||
projectBotDAL,
|
||||
permissionService,
|
||||
projectUserAdditionalPrivilegeDAL,
|
||||
smtpService
|
||||
}: TScimServiceFactoryDep) => {
|
||||
const createScimToken = async ({
|
||||
@ -252,7 +258,10 @@ export const scimServiceFactory = ({
|
||||
status: 403
|
||||
});
|
||||
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
|
||||
membership.userId,
|
||||
orgId
|
||||
);
|
||||
|
||||
return buildScimUser({
|
||||
orgMembershipId: membership.id,
|
||||
@ -263,7 +272,7 @@ export const scimServiceFactory = ({
|
||||
active: membership.isActive,
|
||||
groups: groupMembershipsInOrg.map((group) => ({
|
||||
value: group.groupId,
|
||||
display: group.name
|
||||
display: group.groupName
|
||||
}))
|
||||
});
|
||||
};
|
||||
@ -509,7 +518,10 @@ export const scimServiceFactory = ({
|
||||
isActive: active
|
||||
});
|
||||
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findUserGroupMembershipsInOrg(membership.userId, orgId);
|
||||
const groupMembershipsInOrg = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(
|
||||
membership.userId,
|
||||
orgId
|
||||
);
|
||||
|
||||
return buildScimUser({
|
||||
orgMembershipId: membership.id,
|
||||
@ -520,7 +532,7 @@ export const scimServiceFactory = ({
|
||||
active,
|
||||
groups: groupMembershipsInOrg.map((group) => ({
|
||||
value: group.groupId,
|
||||
display: group.name
|
||||
display: group.groupName
|
||||
}))
|
||||
});
|
||||
};
|
||||
@ -549,6 +561,7 @@ export const scimServiceFactory = ({
|
||||
orgId: membership.orgId,
|
||||
orgDAL,
|
||||
projectMembershipDAL,
|
||||
projectUserAdditionalPrivilegeDAL,
|
||||
projectKeyDAL,
|
||||
userAliasDAL,
|
||||
licenseService
|
||||
@ -589,13 +602,20 @@ export const scimServiceFactory = ({
|
||||
}
|
||||
);
|
||||
|
||||
const scimGroups = groups.map((group) =>
|
||||
buildScimGroup({
|
||||
const scimGroups: TScimGroup[] = [];
|
||||
|
||||
for await (const group of groups) {
|
||||
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
||||
const scimGroup = buildScimGroup({
|
||||
groupId: group.id,
|
||||
name: group.name,
|
||||
members: [] // does this need to be populated?
|
||||
})
|
||||
);
|
||||
members: members.map((member) => ({
|
||||
value: member.orgMembershipId,
|
||||
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
||||
}))
|
||||
});
|
||||
scimGroups.push(scimGroup);
|
||||
}
|
||||
|
||||
return buildScimGroupList({
|
||||
scimGroups,
|
||||
@ -872,23 +892,27 @@ export const scimServiceFactory = ({
|
||||
break;
|
||||
}
|
||||
case "add": {
|
||||
const orgMemberships = await orgMembershipDAL.find({
|
||||
$in: {
|
||||
id: operation.value.map((member) => member.value)
|
||||
}
|
||||
});
|
||||
try {
|
||||
const orgMemberships = await orgMembershipDAL.find({
|
||||
$in: {
|
||||
id: operation.value.map((member) => member.value)
|
||||
}
|
||||
});
|
||||
|
||||
await addUsersToGroupByUserIds({
|
||||
group,
|
||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL
|
||||
});
|
||||
await addUsersToGroupByUserIds({
|
||||
group,
|
||||
userIds: orgMemberships.map((membership) => membership.userId as string),
|
||||
userDAL,
|
||||
userGroupMembershipDAL,
|
||||
orgDAL,
|
||||
groupProjectDAL,
|
||||
projectKeyDAL,
|
||||
projectDAL,
|
||||
projectBotDAL
|
||||
});
|
||||
} catch {
|
||||
logger.info("Repeat SCIM user-group add operation");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@ -916,10 +940,15 @@ export const scimServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
const members = await userGroupMembershipDAL.findGroupMembershipsByGroupIdInOrg(group.id, orgId);
|
||||
|
||||
return buildScimGroup({
|
||||
groupId: group.id,
|
||||
name: group.name,
|
||||
members: []
|
||||
members: members.map((member) => ({
|
||||
value: member.orgMembershipId,
|
||||
display: `${member.firstName ?? ""} ${member.lastName ?? ""}`
|
||||
}))
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||
import {
|
||||
@ -28,6 +29,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||
@ -36,7 +38,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
secretApprovalPolicyDAL,
|
||||
permissionService,
|
||||
secretApprovalPolicyApproverDAL,
|
||||
projectEnvDAL
|
||||
projectEnvDAL,
|
||||
licenseService
|
||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||
const createSecretApprovalPolicy = async ({
|
||||
name,
|
||||
@ -45,12 +48,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
approverUserIds,
|
||||
approvers,
|
||||
projectId,
|
||||
secretPath,
|
||||
environment
|
||||
environment,
|
||||
enforcementLevel
|
||||
}: TCreateSapDTO) => {
|
||||
if (approvals > approverUserIds.length)
|
||||
if (approvals > approvers.length)
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@ -64,6 +68,15 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
ProjectPermissionActions.Create,
|
||||
ProjectPermissionSub.SecretApproval
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan.secretApproval) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to create secret approval policy due to plan restriction. Upgrade plan to create secret approval policy."
|
||||
});
|
||||
}
|
||||
|
||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||
|
||||
@ -73,12 +86,13 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
envId: env.id,
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
await secretApprovalPolicyApproverDAL.insertMany(
|
||||
approverUserIds.map((approverUserId) => ({
|
||||
approvers.map((approverUserId) => ({
|
||||
approverUserId,
|
||||
policyId: doc.id
|
||||
})),
|
||||
@ -90,7 +104,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
};
|
||||
|
||||
const updateSecretApprovalPolicy = async ({
|
||||
approverUserIds,
|
||||
approvers,
|
||||
secretPath,
|
||||
name,
|
||||
actorId,
|
||||
@ -98,7 +112,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
secretPolicyId
|
||||
secretPolicyId,
|
||||
enforcementLevel
|
||||
}: TUpdateSapDTO) => {
|
||||
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||
if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
||||
@ -112,20 +127,29 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
);
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan.secretApproval) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
||||
});
|
||||
}
|
||||
|
||||
const updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||
const doc = await secretApprovalPolicyDAL.updateById(
|
||||
secretApprovalPolicy.id,
|
||||
{
|
||||
approvals,
|
||||
secretPath,
|
||||
name
|
||||
name,
|
||||
enforcementLevel
|
||||
},
|
||||
tx
|
||||
);
|
||||
if (approverUserIds) {
|
||||
if (approvers) {
|
||||
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
||||
await secretApprovalPolicyApproverDAL.insertMany(
|
||||
approverUserIds.map((approverUserId) => ({
|
||||
approvers.map((approverUserId) => ({
|
||||
approverUserId,
|
||||
policyId: doc.id
|
||||
})),
|
||||
@ -163,6 +187,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
||||
ProjectPermissionSub.SecretApproval
|
||||
);
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan.secretApproval) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to update secret approval policy due to plan restriction. Upgrade plan to update secret approval policy."
|
||||
});
|
||||
}
|
||||
|
||||
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
||||
return sapPolicy;
|
||||
};
|
||||
|
@ -1,20 +1,22 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export type TCreateSapDTO = {
|
||||
approvals: number;
|
||||
secretPath?: string | null;
|
||||
environment: string;
|
||||
approverUserIds: string[];
|
||||
approvers: string[];
|
||||
projectId: string;
|
||||
name: string;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateSapDTO = {
|
||||
secretPolicyId: string;
|
||||
approvals?: number;
|
||||
secretPath?: string | null;
|
||||
approverUserIds: string[];
|
||||
approvers: string[];
|
||||
name?: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteSapDTO = {
|
||||
|
@ -94,6 +94,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
tx.ref("projectId").withSchema(TableName.Environment),
|
||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||
tx.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||
tx.ref("envId").withSchema(TableName.SecretApprovalPolicy).as("policyEnvId"),
|
||||
tx.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||
tx.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals")
|
||||
);
|
||||
|
||||
@ -128,7 +130,9 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
approvals: el.policyApprovals,
|
||||
secretPath: el.policySecretPath
|
||||
secretPath: el.policySecretPath,
|
||||
enforcementLevel: el.policyEnforcementLevel,
|
||||
envId: el.policyEnvId
|
||||
}
|
||||
}),
|
||||
childrenMapper: [
|
||||
@ -282,6 +286,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
`DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank`
|
||||
),
|
||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||
@ -308,7 +313,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
approvals: el.policyApprovals,
|
||||
secretPath: el.policySecretPath
|
||||
secretPath: el.policySecretPath,
|
||||
enforcementLevel: el.policyEnforcementLevel
|
||||
},
|
||||
committerUser: {
|
||||
userId: el.committerUserId,
|
||||
@ -416,6 +422,7 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
),
|
||||
db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath"),
|
||||
db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals"),
|
||||
db.ref("enforcementLevel").withSchema(TableName.SecretApprovalPolicy).as("policyEnforcementLevel"),
|
||||
db.ref("approverUserId").withSchema(TableName.SecretApprovalPolicyApprover),
|
||||
db.ref("email").withSchema("committerUser").as("committerUserEmail"),
|
||||
db.ref("username").withSchema("committerUser").as("committerUserUsername"),
|
||||
@ -441,7 +448,8 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
id: el.policyId,
|
||||
name: el.policyName,
|
||||
approvals: el.policyApprovals,
|
||||
secretPath: el.policySecretPath
|
||||
secretPath: el.policySecretPath,
|
||||
enforcementLevel: el.policyEnforcementLevel
|
||||
},
|
||||
committerUser: {
|
||||
userId: el.committerUserId,
|
||||
@ -483,11 +491,26 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteByProjectId = async (projectId: string, tx?: Knex) => {
|
||||
try {
|
||||
const query = await (tx || db)(TableName.SecretApprovalRequest)
|
||||
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
|
||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||
.where({ projectId })
|
||||
.delete();
|
||||
|
||||
return query;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "DeleteByProjectId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretApprovalRequestOrm,
|
||||
findById,
|
||||
findProjectRequestCount,
|
||||
findByProjectId,
|
||||
findByProjectIdBridgeSecretV2
|
||||
findByProjectIdBridgeSecretV2,
|
||||
deleteByProjectId
|
||||
};
|
||||
};
|
||||
|
@ -81,15 +81,13 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||
.select({
|
||||
secVerTagId: "secVerTag.id",
|
||||
secVerTagColor: "secVerTag.color",
|
||||
secVerTagSlug: "secVerTag.slug",
|
||||
secVerTagName: "secVerTag.name"
|
||||
secVerTagSlug: "secVerTag.slug"
|
||||
})
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"),
|
||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||
)
|
||||
.select(
|
||||
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
||||
@ -124,9 +122,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "tagJnId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({
|
||||
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||
id,
|
||||
name,
|
||||
name: slug,
|
||||
slug,
|
||||
color
|
||||
})
|
||||
@ -200,11 +198,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "secVerTagId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ secVerTagId: id, secVerTagName: name, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||
mapper: ({ secVerTagId: id, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||
// eslint-disable-next-line
|
||||
id,
|
||||
// eslint-disable-next-line
|
||||
name,
|
||||
name: slug,
|
||||
// eslint-disable-next-line
|
||||
slug,
|
||||
// eslint-disable-next-line
|
||||
@ -262,15 +260,13 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||
.select({
|
||||
secVerTagId: "secVerTag.id",
|
||||
secVerTagColor: "secVerTag.color",
|
||||
secVerTagSlug: "secVerTag.slug",
|
||||
secVerTagName: "secVerTag.name"
|
||||
secVerTagSlug: "secVerTag.slug"
|
||||
})
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTagV2).as("tagJnId"),
|
||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||
)
|
||||
.select(
|
||||
db.ref("version").withSchema(TableName.SecretV2).as("orgSecVersion"),
|
||||
@ -292,9 +288,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "tagJnId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({
|
||||
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||
id,
|
||||
name,
|
||||
name: slug,
|
||||
slug,
|
||||
color
|
||||
})
|
||||
@ -330,11 +326,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "secVerTagId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ secVerTagId: id, secVerTagName: name, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||
mapper: ({ secVerTagId: id, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||
// eslint-disable-next-line
|
||||
id,
|
||||
// eslint-disable-next-line
|
||||
name,
|
||||
name: slug,
|
||||
// eslint-disable-next-line
|
||||
slug,
|
||||
// eslint-disable-next-line
|
||||
@ -354,12 +350,66 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||
throw new DatabaseError({ error, name: "FindByRequestId" });
|
||||
}
|
||||
};
|
||||
// special query for migration to v2 secret
|
||||
const findByProjectId = async (projectId: string, tx?: Knex) => {
|
||||
try {
|
||||
const docs = await (tx || db)(TableName.SecretApprovalRequestSecret)
|
||||
.join(
|
||||
TableName.SecretApprovalRequest,
|
||||
`${TableName.SecretApprovalRequest}.id`,
|
||||
`${TableName.SecretApprovalRequestSecret}.requestId`
|
||||
)
|
||||
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
|
||||
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||
.leftJoin(
|
||||
TableName.SecretApprovalRequestSecretTag,
|
||||
`${TableName.SecretApprovalRequestSecret}.id`,
|
||||
`${TableName.SecretApprovalRequestSecretTag}.secretId`
|
||||
)
|
||||
.where({ projectId })
|
||||
.select(selectAllTableCols(TableName.SecretApprovalRequestSecret))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("secretApprovalTagId"),
|
||||
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecretTag).as("secretApprovalTagSecretId"),
|
||||
db.ref("tagId").withSchema(TableName.SecretApprovalRequestSecretTag).as("secretApprovalTagSecretTagId"),
|
||||
db.ref("createdAt").withSchema(TableName.SecretApprovalRequestSecretTag).as("secretApprovalTagCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.SecretApprovalRequestSecretTag).as("secretApprovalTagUpdatedAt")
|
||||
);
|
||||
const formatedDoc = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (data) => SecretApprovalRequestsSecretsSchema.parse(data),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "secretApprovalTagId",
|
||||
label: "tags" as const,
|
||||
mapper: ({
|
||||
secretApprovalTagSecretId,
|
||||
secretApprovalTagId,
|
||||
secretApprovalTagUpdatedAt,
|
||||
secretApprovalTagCreatedAt
|
||||
}) => ({
|
||||
secretApprovalTagSecretId,
|
||||
secretApprovalTagId,
|
||||
secretApprovalTagUpdatedAt,
|
||||
secretApprovalTagCreatedAt
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
return formatedDoc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindByRequestId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretApprovalRequestSecretOrm,
|
||||
insertV2Bridge: secretApprovalRequestSecretV2Orm.insertMany,
|
||||
findByRequestId,
|
||||
findByRequestIdBridgeSecretV2,
|
||||
bulkUpdateNoVersionIncrement,
|
||||
findByProjectId,
|
||||
insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany,
|
||||
insertApprovalSecretV2Tags: secretApprovalRequestSecretV2TagOrm.insertMany
|
||||
};
|
||||
|
@ -8,16 +8,19 @@ import {
|
||||
TSecretApprovalRequestsSecretsInsert,
|
||||
TSecretApprovalRequestsSecretsV2Insert
|
||||
} from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||
import { groupBy, pick, unique } from "@app/lib/fn";
|
||||
import { setKnexStringValue } from "@app/lib/knex";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||
import {
|
||||
decryptSecretWithBot,
|
||||
@ -44,7 +47,10 @@ import {
|
||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||
@ -80,7 +86,10 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findById">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
userDAL: Pick<TUserDALFactory, "find" | "findOne">;
|
||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findById" | "findProjectById">;
|
||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
||||
secretV2BridgeDAL: Pick<
|
||||
@ -89,6 +98,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
||||
>;
|
||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||
@ -108,10 +118,14 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
secretVersionDAL,
|
||||
secretQueueService,
|
||||
projectBotService,
|
||||
smtpService,
|
||||
userDAL,
|
||||
projectEnvDAL,
|
||||
kmsService,
|
||||
secretV2BridgeDAL,
|
||||
secretVersionV2BridgeDAL,
|
||||
secretVersionTagV2BridgeDAL
|
||||
secretVersionTagV2BridgeDAL,
|
||||
licenseService
|
||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||
@ -213,12 +227,10 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
secretKey: el.key,
|
||||
id: el.id,
|
||||
version: el.version,
|
||||
secretValue: el.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||
: undefined,
|
||||
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||
secretComment: el.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||
: undefined,
|
||||
: "",
|
||||
secret: el.secret
|
||||
? {
|
||||
secretKey: el.secret.key,
|
||||
@ -226,10 +238,10 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
version: el.secret.version,
|
||||
secretValue: el.secret.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
||||
: undefined,
|
||||
: "",
|
||||
secretComment: el.secret.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedComment }).toString()
|
||||
: undefined
|
||||
: ""
|
||||
}
|
||||
: undefined,
|
||||
secretVersion: el.secretVersion
|
||||
@ -239,10 +251,10 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
version: el.secretVersion.version,
|
||||
secretValue: el.secretVersion.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
||||
: undefined,
|
||||
: "",
|
||||
secretComment: el.secretVersion.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
||||
: undefined
|
||||
: ""
|
||||
}
|
||||
: undefined
|
||||
}));
|
||||
@ -286,6 +298,14 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan.secretApproval) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to review secret approval request due to plan restriction. Upgrade plan to review secret approval request."
|
||||
});
|
||||
}
|
||||
|
||||
const { policy } = secretApprovalRequest;
|
||||
const { hasRole } = await permissionService.getProjectPermission(
|
||||
ActorType.USER,
|
||||
@ -336,6 +356,14 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan.secretApproval) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to update secret approval request due to plan restriction. Upgrade plan to update secret approval request."
|
||||
});
|
||||
}
|
||||
|
||||
const { policy } = secretApprovalRequest;
|
||||
const { hasRole } = await permissionService.getProjectPermission(
|
||||
ActorType.USER,
|
||||
@ -370,12 +398,21 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
actorAuthMethod,
|
||||
bypassReason
|
||||
}: TMergeSecretApprovalRequestDTO) => {
|
||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
if (!plan.secretApproval) {
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Failed to merge secret approval request due to plan restriction. Upgrade plan to merge secret approval request."
|
||||
});
|
||||
}
|
||||
|
||||
const { policy, folderId, projectId } = secretApprovalRequest;
|
||||
const { hasRole } = await permissionService.getProjectPermission(
|
||||
ActorType.USER,
|
||||
@ -401,8 +438,11 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
secretApprovalRequest.policy.approvers.filter(
|
||||
({ userId: approverId }) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
|
||||
).length;
|
||||
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
||||
|
||||
if (!hasMinApproval && !isSoftEnforcement)
|
||||
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||
|
||||
if (!hasMinApproval) throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||
let mergeStatus;
|
||||
if (shouldUseSecretV2Bridge) {
|
||||
@ -745,6 +785,35 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
actorId,
|
||||
actor
|
||||
});
|
||||
|
||||
if (isSoftEnforcement) {
|
||||
const cfg = getConfig();
|
||||
const project = await projectDAL.findProjectById(projectId);
|
||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||
const requestedByUser = await userDAL.findOne({ id: actorId });
|
||||
const approverUsers = await userDAL.find({
|
||||
$in: {
|
||||
id: policy.approvers.map((approver: { userId: string }) => approver.userId)
|
||||
}
|
||||
});
|
||||
|
||||
await smtpService.sendMail({
|
||||
recipients: approverUsers.filter((approver) => approver.email).map((approver) => approver.email!),
|
||||
subjectLine: "Infisical Secret Change Policy Bypassed",
|
||||
|
||||
substitutions: {
|
||||
projectName: project.name,
|
||||
requesterFullName: `${requestedByUser.firstName} ${requestedByUser.lastName}`,
|
||||
requesterEmail: requestedByUser.email,
|
||||
bypassReason,
|
||||
secretPath: policy.secretPath,
|
||||
environment: env.name,
|
||||
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval`
|
||||
},
|
||||
template: SmtpTemplates.AccessSecretRequestBypassed
|
||||
});
|
||||
}
|
||||
|
||||
return mergeStatus;
|
||||
};
|
||||
|
||||
@ -1230,7 +1299,7 @@ export const secretApprovalRequestServiceFactory = ({
|
||||
|
||||
const commitsGroupByKey = groupBy(approvalCommits, (i) => i.key);
|
||||
if (tagIds.length) {
|
||||
await secretApprovalRequestSecretDAL.insertApprovalSecretTags(
|
||||
await secretApprovalRequestSecretDAL.insertApprovalSecretV2Tags(
|
||||
Object.keys(commitTagIds).flatMap((blindIndex) =>
|
||||
commitTagIds[blindIndex]
|
||||
? commitTagIds[blindIndex].map((tagId) => ({
|
||||
|
@ -67,6 +67,7 @@ export type TGenerateSecretApprovalRequestV2BridgeDTO = {
|
||||
|
||||
export type TMergeSecretApprovalRequestDTO = {
|
||||
approvalId: string;
|
||||
bypassReason?: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TStatusChangeDTO = {
|
||||
|
@ -257,7 +257,7 @@ export const secretReplicationServiceFactory = ({
|
||||
secretDAL: secretV2BridgeDAL,
|
||||
folderDAL,
|
||||
secretImportDAL,
|
||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : undefined)
|
||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
||||
});
|
||||
// secrets that gets replicated across imports
|
||||
const sourceDecryptedLocalSecrets = sourceLocalSecrets.map((el) => ({
|
||||
@ -449,7 +449,7 @@ export const secretReplicationServiceFactory = ({
|
||||
});
|
||||
}
|
||||
if (locallyDeletedSecrets.length) {
|
||||
await secretDAL.delete(
|
||||
await secretV2BridgeDAL.delete(
|
||||
{
|
||||
$in: {
|
||||
id: locallyDeletedSecrets.map(({ id }) => id)
|
||||
|
@ -144,6 +144,8 @@ export const secretRotationDALFactory = (db: TDbClient) => {
|
||||
const findRotationOutputsV2ByRotationId = async (rotationId: string) =>
|
||||
secretRotationOutputV2Orm.find({ rotationId });
|
||||
|
||||
// special query
|
||||
|
||||
return {
|
||||
...secretRotationOrm,
|
||||
find,
|
||||
|
@ -164,10 +164,10 @@ export const secretSnapshotServiceFactory = ({
|
||||
secretKey: el.key,
|
||||
secretValue: el.encryptedValue
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||
: undefined,
|
||||
: "",
|
||||
secretComment: el.encryptedComment
|
||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||
: undefined
|
||||
: ""
|
||||
}))
|
||||
};
|
||||
} else {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { Knex } from "knex";
|
||||
import { z } from "zod";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import {
|
||||
@ -99,8 +100,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||
);
|
||||
return sqlNestRelationships({
|
||||
data,
|
||||
@ -131,9 +131,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "tagVersionId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
id,
|
||||
name,
|
||||
name: slug,
|
||||
slug,
|
||||
color,
|
||||
vId
|
||||
@ -194,8 +194,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||
);
|
||||
return sqlNestRelationships({
|
||||
data,
|
||||
@ -226,9 +225,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "tagVersionId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
id,
|
||||
name,
|
||||
name: slug,
|
||||
slug,
|
||||
color,
|
||||
vId
|
||||
@ -352,8 +351,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||
);
|
||||
|
||||
const formated = sqlNestRelationships({
|
||||
@ -376,9 +374,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "tagVersionId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
id,
|
||||
name,
|
||||
name: slug,
|
||||
slug,
|
||||
color,
|
||||
vId
|
||||
@ -507,8 +505,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||
);
|
||||
|
||||
const formated = sqlNestRelationships({
|
||||
@ -531,9 +528,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "tagVersionId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||
id,
|
||||
name,
|
||||
name: slug,
|
||||
slug,
|
||||
color,
|
||||
vId
|
||||
@ -719,6 +716,97 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
// special query for migration for secret v2
|
||||
const findNSecretV1SnapshotByFolderId = async (folderId: string, n = 15, tx?: Knex) => {
|
||||
try {
|
||||
const query = (tx || db.replicaNode())(TableName.Snapshot)
|
||||
.leftJoin(TableName.SnapshotSecret, `${TableName.Snapshot}.id`, `${TableName.SnapshotSecret}.snapshotId`)
|
||||
.leftJoin(
|
||||
TableName.SecretVersion,
|
||||
`${TableName.SnapshotSecret}.secretVersionId`,
|
||||
`${TableName.SecretVersion}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.SecretVersionTag,
|
||||
`${TableName.SecretVersionTag}.${TableName.SecretVersion}Id`,
|
||||
`${TableName.SecretVersion}.id`
|
||||
)
|
||||
.select(selectAllTableCols(TableName.SecretVersion))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.Snapshot).as("snapshotId"),
|
||||
db.ref("createdAt").withSchema(TableName.Snapshot).as("snapshotCreatedAt"),
|
||||
db.ref("updatedAt").withSchema(TableName.Snapshot).as("snapshotUpdatedAt"),
|
||||
db.ref("envId").withSchema(TableName.SnapshotSecret).as("snapshotEnvId"),
|
||||
db.ref("id").withSchema(TableName.SecretVersionTag).as("secretVersionTagId"),
|
||||
db.ref("secret_versionsId").withSchema(TableName.SecretVersionTag).as("secretVersionTagSecretId"),
|
||||
db.ref("secret_tagsId").withSchema(TableName.SecretVersionTag).as("secretVersionTagSecretTagId"),
|
||||
db.raw(
|
||||
`DENSE_RANK() OVER (partition by ${TableName.Snapshot}."id" ORDER BY ${TableName.SecretVersion}."createdAt") as rank`
|
||||
)
|
||||
)
|
||||
.orderBy(`${TableName.Snapshot}.createdAt`, "desc")
|
||||
.where(`${TableName.Snapshot}.folderId`, folderId);
|
||||
const data = await (tx || db)
|
||||
.with("w", query)
|
||||
.select("*")
|
||||
.from<Awaited<typeof query>[number]>("w")
|
||||
.andWhere("w.rank", "<", n);
|
||||
|
||||
return sqlNestRelationships({
|
||||
data,
|
||||
key: "snapshotId",
|
||||
parentMapper: ({ snapshotId: id, snapshotCreatedAt: createdAt, snapshotUpdatedAt: updatedAt }) => ({
|
||||
id,
|
||||
folderId,
|
||||
createdAt,
|
||||
updatedAt
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "id",
|
||||
label: "secretVersions" as const,
|
||||
mapper: (el) => SecretVersionsSchema.extend({ snapshotEnvId: z.string() }).parse(el),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "secretVersionTagId",
|
||||
label: "tags" as const,
|
||||
mapper: ({ secretVersionTagId, secretVersionTagSecretId, secretVersionTagSecretTagId }) => ({
|
||||
id: secretVersionTagId,
|
||||
secretVersionId: secretVersionTagSecretId,
|
||||
secretTagId: secretVersionTagSecretTagId
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindSecretSnapshotDataById" });
|
||||
}
|
||||
};
|
||||
|
||||
const deleteSnapshotsAboveLimit = async (folderId: string, n = 15, tx?: Knex) => {
|
||||
try {
|
||||
const query = await (tx || db)
|
||||
.with("to_delete", (qb) => {
|
||||
void qb
|
||||
.select("id")
|
||||
.from(TableName.Snapshot)
|
||||
.where("folderId", folderId)
|
||||
.orderBy("createdAt", "desc")
|
||||
.offset(n);
|
||||
})
|
||||
.from(TableName.Snapshot)
|
||||
.whereIn("id", (qb) => {
|
||||
void qb.select("id").from("to_delete");
|
||||
})
|
||||
.delete();
|
||||
return query;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "DeleteSnapshotsAboveLimit" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...secretSnapshotOrm,
|
||||
findById,
|
||||
@ -728,6 +816,8 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
||||
countOfSnapshotsByFolderId,
|
||||
findSecretSnapshotDataById,
|
||||
findSecretSnapshotV2DataById,
|
||||
pruneExcessSnapshots
|
||||
pruneExcessSnapshots,
|
||||
findNSecretV1SnapshotByFolderId,
|
||||
deleteSnapshotsAboveLimit
|
||||
};
|
||||
};
|
||||
|
@ -5,17 +5,30 @@ import { Redlock, Settings } from "@app/lib/red-lock";
|
||||
export type TKeyStoreFactory = ReturnType<typeof keyStoreFactory>;
|
||||
|
||||
// all the key prefixes used must be set here to avoid conflict
|
||||
export enum KeyStorePrefixes {
|
||||
SecretReplication = "secret-replication-import-lock",
|
||||
KmsProjectDataKeyCreation = "kms-project-data-key-creation-lock",
|
||||
KmsProjectKeyCreation = "kms-project-key-creation-lock",
|
||||
WaitUntilReadyKmsProjectDataKeyCreation = "wait-until-ready-kms-project-data-key-creation-",
|
||||
WaitUntilReadyKmsProjectKeyCreation = "wait-until-ready-kms-project-key-creation-",
|
||||
KmsOrgKeyCreation = "kms-org-key-creation-lock",
|
||||
KmsOrgDataKeyCreation = "kms-org-data-key-creation-lock",
|
||||
WaitUntilReadyKmsOrgKeyCreation = "wait-until-ready-kms-org-key-creation-",
|
||||
WaitUntilReadyKmsOrgDataKeyCreation = "wait-until-ready-kms-org-data-key-creation-"
|
||||
}
|
||||
export const KeyStorePrefixes = {
|
||||
SecretReplication: "secret-replication-import-lock",
|
||||
KmsProjectDataKeyCreation: "kms-project-data-key-creation-lock",
|
||||
KmsProjectKeyCreation: "kms-project-key-creation-lock",
|
||||
WaitUntilReadyKmsProjectDataKeyCreation: "wait-until-ready-kms-project-data-key-creation-",
|
||||
WaitUntilReadyKmsProjectKeyCreation: "wait-until-ready-kms-project-key-creation-",
|
||||
KmsOrgKeyCreation: "kms-org-key-creation-lock",
|
||||
KmsOrgDataKeyCreation: "kms-org-data-key-creation-lock",
|
||||
WaitUntilReadyKmsOrgKeyCreation: "wait-until-ready-kms-org-key-creation-",
|
||||
WaitUntilReadyKmsOrgDataKeyCreation: "wait-until-ready-kms-org-data-key-creation-",
|
||||
|
||||
SyncSecretIntegrationLock: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
||||
`identity-access-token-status:${identityAccessTokenId}`,
|
||||
ServiceTokenStatusUpdate: (serviceTokenId: string) => `service-token-status:${serviceTokenId}`
|
||||
};
|
||||
|
||||
export const KeyStoreTtls = {
|
||||
SetSyncSecretIntegrationLastRunTimestampInSeconds: 10,
|
||||
AccessTokenStatusUpdateInSeconds: 120
|
||||
};
|
||||
|
||||
type TWaitTillReady = {
|
||||
key: string;
|
||||
@ -37,10 +50,10 @@ export const keyStoreFactory = (redisUrl: string) => {
|
||||
|
||||
const setItemWithExpiry = async (
|
||||
key: string,
|
||||
exp: number | string,
|
||||
expiryInSeconds: number | string,
|
||||
value: string | number | Buffer,
|
||||
prefix?: string
|
||||
) => redis.set(prefix ? `${prefix}:${key}` : key, value, "EX", exp);
|
||||
) => redis.set(prefix ? `${prefix}:${key}` : key, value, "EX", expiryInSeconds);
|
||||
|
||||
const deleteItem = async (key: string) => redis.del(key);
|
||||
|
||||
|
@ -348,10 +348,15 @@ export const ORGANIZATIONS = {
|
||||
LIST_USER_MEMBERSHIPS: {
|
||||
organizationId: "The ID of the organization to get memberships from."
|
||||
},
|
||||
GET_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to get the membership for.",
|
||||
membershipId: "The ID of the membership to get."
|
||||
},
|
||||
UPDATE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to update the membership for.",
|
||||
membershipId: "The ID of the membership to update.",
|
||||
role: "The new role of the membership."
|
||||
role: "The new role of the membership.",
|
||||
isActive: "The active status of the membership"
|
||||
},
|
||||
DELETE_USER_MEMBERSHIP: {
|
||||
organizationId: "The ID of the organization to delete the membership from.",
|
||||
@ -420,6 +425,21 @@ export const PROJECTS = {
|
||||
},
|
||||
LIST_INTEGRATION_AUTHORIZATION: {
|
||||
workspaceId: "The ID of the project to list integration auths for."
|
||||
},
|
||||
LIST_CAS: {
|
||||
slug: "The slug of the project to list CAs for.",
|
||||
status: "The status of the CA to filter by.",
|
||||
friendlyName: "The friendly name of the CA to filter by.",
|
||||
commonName: "The common name of the CA to filter by.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th CA.",
|
||||
limit: "The number of CAs to return."
|
||||
},
|
||||
LIST_CERTIFICATES: {
|
||||
slug: "The slug of the project to list certificates for.",
|
||||
friendlyName: "The friendly name of the certificate to filter by.",
|
||||
commonName: "The common name of the certificate to filter by.",
|
||||
offset: "The offset to start from. If you enter 10, it will start from the 10th certificate.",
|
||||
limit: "The number of certificates to return."
|
||||
}
|
||||
} as const;
|
||||
|
||||
@ -505,6 +525,10 @@ export const ENVIRONMENTS = {
|
||||
DELETE: {
|
||||
workspaceId: "The ID of the project to delete the environment from.",
|
||||
id: "The ID of the environment to delete."
|
||||
},
|
||||
GET: {
|
||||
workspaceId: "The ID of the project the environment belongs to.",
|
||||
id: "The ID of the environment to fetch."
|
||||
}
|
||||
} as const;
|
||||
|
||||
@ -572,7 +596,8 @@ export const RAW_SECRETS = {
|
||||
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
||||
environment: "The slug of the environment 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"
|
||||
},
|
||||
CREATE: {
|
||||
secretName: "The name of the secret to create.",
|
||||
@ -1024,15 +1049,30 @@ export const CERTIFICATE_AUTHORITIES = {
|
||||
caId: "The ID of the CA to generate CSR from",
|
||||
csr: "The generated CSR from the CA"
|
||||
},
|
||||
RENEW_CA_CERT: {
|
||||
caId: "The ID of the CA to renew the CA certificate for",
|
||||
type: "The type of behavior to use for the renewal operation. Currently Infisical is only able to renew a CA certificate with the same key pair.",
|
||||
notAfter: "The expiry date and time for the renewed CA certificate in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
||||
certificate: "The renewed CA certificate body",
|
||||
certificateChain: "The certificate chain of the CA",
|
||||
serialNumber: "The serial number of the renewed CA certificate"
|
||||
},
|
||||
GET_CERT: {
|
||||
caId: "The ID of the CA to get the certificate body and certificate chain from",
|
||||
certificate: "The certificate body of the CA",
|
||||
certificateChain: "The certificate chain of the CA",
|
||||
serialNumber: "The serial number of the CA certificate"
|
||||
},
|
||||
GET_CA_CERTS: {
|
||||
caId: "The ID of the CA to get the CA certificates for",
|
||||
certificate: "The certificate body of the CA certificate",
|
||||
certificateChain: "The certificate chain of the CA certificate",
|
||||
serialNumber: "The serial number of the CA certificate",
|
||||
version: "The version of the CA certificate. The version is incremented for each CA renewal operation."
|
||||
},
|
||||
SIGN_INTERMEDIATE: {
|
||||
caId: "The ID of the CA to sign the intermediate certificate with",
|
||||
csr: "The CSR to sign with the CA",
|
||||
csr: "The pem-encoded CSR to sign with the CA",
|
||||
notBefore: "The date and time when the intermediate CA becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
||||
notAfter: "The date and time when the intermediate CA expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
||||
maxPathLength:
|
||||
@ -1049,6 +1089,8 @@ export const CERTIFICATE_AUTHORITIES = {
|
||||
},
|
||||
ISSUE_CERT: {
|
||||
caId: "The ID of the CA to issue the certificate from",
|
||||
certificateTemplateId: "The ID of the certificate template to issue the certificate from",
|
||||
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
||||
friendlyName: "A friendly name for the certificate",
|
||||
commonName: "The common name (CN) for the certificate",
|
||||
altNames:
|
||||
@ -1062,9 +1104,26 @@ export const CERTIFICATE_AUTHORITIES = {
|
||||
privateKey: "The private key of the issued certificate",
|
||||
serialNumber: "The serial number of the issued certificate"
|
||||
},
|
||||
GET_CRL: {
|
||||
caId: "The ID of the CA to get the certificate revocation list (CRL) for",
|
||||
crl: "The certificate revocation list (CRL) of the CA"
|
||||
SIGN_CERT: {
|
||||
caId: "The ID of the CA to issue the certificate from",
|
||||
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
||||
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
||||
friendlyName: "A friendly name for the certificate",
|
||||
commonName: "The common name (CN) for the certificate",
|
||||
altNames:
|
||||
"A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses.",
|
||||
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
|
||||
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
||||
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
||||
certificate: "The issued certificate",
|
||||
issuingCaCertificate: "The certificate of the issuing CA",
|
||||
certificateChain: "The certificate chain of the issued certificate",
|
||||
serialNumber: "The serial number of the issued certificate"
|
||||
},
|
||||
GET_CRLS: {
|
||||
caId: "The ID of the CA to get the certificate revocation lists (CRLs) for",
|
||||
id: "The ID of certificate revocation list (CRL)",
|
||||
crl: "The certificate revocation list (CRL)"
|
||||
}
|
||||
};
|
||||
|
||||
@ -1090,6 +1149,98 @@ export const CERTIFICATES = {
|
||||
}
|
||||
};
|
||||
|
||||
export const CERTIFICATE_TEMPLATES = {
|
||||
CREATE: {
|
||||
caId: "The ID of the certificate authority to associate the template with",
|
||||
pkiCollectionId: "The ID of the PKI collection to bind to the template",
|
||||
name: "The name of the template",
|
||||
commonName: "The regular expression string to use for validating common names",
|
||||
subjectAlternativeName: "The regular expression string to use for validating subject alternative names",
|
||||
ttl: "The max TTL for the template"
|
||||
},
|
||||
GET: {
|
||||
certificateTemplateId: "The ID of the certificate template to get"
|
||||
},
|
||||
UPDATE: {
|
||||
certificateTemplateId: "The ID of the certificate template to update",
|
||||
caId: "The ID of the certificate authority to update the association with the template",
|
||||
pkiCollectionId: "The ID of the PKI collection to update the binding to the template",
|
||||
name: "The updated name of the template",
|
||||
commonName: "The updated regular expression string for validating common names",
|
||||
subjectAlternativeName: "The updated regular expression string for validating subject alternative names",
|
||||
ttl: "The updated max TTL for the template"
|
||||
},
|
||||
DELETE: {
|
||||
certificateTemplateId: "The ID of the certificate template to delete"
|
||||
}
|
||||
};
|
||||
|
||||
export const CA_CRLS = {
|
||||
GET: {
|
||||
crlId: "The ID of the certificate revocation list (CRL) to get",
|
||||
crl: "The certificate revocation list (CRL)"
|
||||
}
|
||||
};
|
||||
|
||||
export const ALERTS = {
|
||||
CREATE: {
|
||||
projectId: "The ID of the project to create the alert in",
|
||||
pkiCollectionId: "The ID of the PKI collection to bind to the alert",
|
||||
name: "The name of the alert",
|
||||
alertBeforeDays: "The number of days before the certificate expires to trigger the alert",
|
||||
emails: "The email addresses to send the alert email to"
|
||||
},
|
||||
GET: {
|
||||
alertId: "The ID of the alert to get"
|
||||
},
|
||||
UPDATE: {
|
||||
alertId: "The ID of the alert to update",
|
||||
name: "The name of the alert to update to",
|
||||
alertBeforeDays: "The number of days before the certificate expires to trigger the alert to update to",
|
||||
pkiCollectionId: "The ID of the PKI collection to bind to the alert to update to",
|
||||
emails: "The email addresses to send the alert email to update to"
|
||||
},
|
||||
DELETE: {
|
||||
alertId: "The ID of the alert to delete"
|
||||
}
|
||||
};
|
||||
|
||||
export const PKI_COLLECTIONS = {
|
||||
CREATE: {
|
||||
projectId: "The ID of the project to create the PKI collection in",
|
||||
name: "The name of the PKI collection",
|
||||
description: "A description for the PKI collection"
|
||||
},
|
||||
GET: {
|
||||
collectionId: "The ID of the PKI collection to get"
|
||||
},
|
||||
UPDATE: {
|
||||
collectionId: "The ID of the PKI collection to update",
|
||||
name: "The name of the PKI collection to update to",
|
||||
description: "The description for the PKI collection to update to"
|
||||
},
|
||||
DELETE: {
|
||||
collectionId: "The ID of the PKI collection to delete"
|
||||
},
|
||||
LIST_ITEMS: {
|
||||
collectionId: "The ID of the PKI collection to list items from",
|
||||
type: "The type of the PKI collection item to list",
|
||||
offset: "The offset to start from",
|
||||
limit: "The number of items to return"
|
||||
},
|
||||
ADD_ITEM: {
|
||||
collectionId: "The ID of the PKI collection to add the item to",
|
||||
type: "The type of the PKI collection item to add",
|
||||
itemId: "The resource ID of the PKI collection item to add"
|
||||
},
|
||||
DELETE_ITEM: {
|
||||
collectionId: "The ID of the PKI collection to delete the item from",
|
||||
collectionItemId: "The ID of the PKI collection item to delete",
|
||||
type: "The type of the deleted PKI collection item",
|
||||
itemId: "The resource ID of the deleted PKI collection item"
|
||||
}
|
||||
};
|
||||
|
||||
export const PROJECT_ROLE = {
|
||||
CREATE: {
|
||||
projectSlug: "Slug of the project to create the role for.",
|
||||
|
@ -140,7 +140,8 @@ const envSchema = z
|
||||
MAINTENANCE_MODE: zodStrBool.default("false"),
|
||||
CAPTCHA_SECRET: zpStr(z.string().optional()),
|
||||
PLAIN_API_KEY: zpStr(z.string().optional()),
|
||||
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional())
|
||||
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional()),
|
||||
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false")
|
||||
})
|
||||
.transform((data) => ({
|
||||
...data,
|
||||
|
@ -226,8 +226,9 @@ export const infisicalSymmetricDecrypt = <T = string>({
|
||||
keyEncoding: SecretKeyEncoding;
|
||||
}) => {
|
||||
const appCfg = getConfig();
|
||||
const rootEncryptionKey = appCfg.ROOT_ENCRYPTION_KEY;
|
||||
const encryptionKey = appCfg.ENCRYPTION_KEY;
|
||||
// the or gate is used used in migration
|
||||
const rootEncryptionKey = appCfg?.ROOT_ENCRYPTION_KEY || process.env.ROOT_ENCRYPTION_KEY;
|
||||
const encryptionKey = appCfg?.ENCRYPTION_KEY || process.env.ENCRYPTION_KEY;
|
||||
if (rootEncryptionKey && keyEncoding === SecretKeyEncoding.BASE64) {
|
||||
const data = decryptSymmetric({ key: rootEncryptionKey, iv, tag, ciphertext });
|
||||
return data as T;
|
||||
|
@ -1,3 +1,8 @@
|
||||
export const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000;
|
||||
|
||||
export const secondsToMillis = (seconds: number) => seconds * 1000;
|
||||
|
||||
export const applyJitter = (delayMs: number, jitterMs: number) => {
|
||||
const jitter = Math.floor(Math.random() * (2 * jitterMs)) - jitterMs;
|
||||
return delayMs + jitter;
|
||||
};
|
||||
|
@ -17,6 +17,23 @@ export const groupBy = <T, Key extends string | number | symbol>(
|
||||
{} as Record<Key, T[]>
|
||||
);
|
||||
|
||||
/**
|
||||
* Sorts an array of items into groups. The return value is a map where the keys are
|
||||
* the group ids the given getGroupId function produced and the value will be the last found one for the group key
|
||||
*/
|
||||
export const groupByUnique = <T, Key extends string | number | symbol>(
|
||||
array: readonly T[],
|
||||
getGroupId: (item: T) => Key
|
||||
): Record<Key, T> =>
|
||||
array.reduce(
|
||||
(acc, item) => {
|
||||
const groupId = getGroupId(item);
|
||||
acc[groupId] = item;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<Key, T>
|
||||
);
|
||||
|
||||
/**
|
||||
* Given a list of items returns a new list with only
|
||||
* unique items. Accepts an optional identity function
|
||||
|
@ -1,2 +1,8 @@
|
||||
export const getLastMidnightDateISO = (last = 1) =>
|
||||
`${new Date(new Date().setDate(new Date().getDate() - last)).toISOString().slice(0, 10)}T00:00:00Z`;
|
||||
|
||||
export const getTimeDifferenceInSeconds = (lhsTimestamp: string, rhsTimestamp: string) => {
|
||||
const lhs = new Date(lhsTimestamp);
|
||||
const rhs = new Date(rhsTimestamp);
|
||||
return Math.floor((Number(lhs) - Number(rhs)) / 1000);
|
||||
};
|
||||
|
@ -19,23 +19,43 @@ export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
||||
|
||||
export type TFindFilter<R extends object = object> = Partial<R> & {
|
||||
$in?: Partial<{ [k in keyof R]: R[k][] }>;
|
||||
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
||||
};
|
||||
export const buildFindFilter =
|
||||
<R extends object = object>({ $in, ...filter }: TFindFilter<R>) =>
|
||||
<R extends object = object>({ $in, $search, ...filter }: TFindFilter<R>) =>
|
||||
(bd: Knex.QueryBuilder<R, R>) => {
|
||||
void bd.where(filter);
|
||||
if ($in) {
|
||||
Object.entries($in).forEach(([key, val]) => {
|
||||
void bd.whereIn(key as never, val as never);
|
||||
if (val) {
|
||||
void bd.whereIn(key as never, val as never);
|
||||
}
|
||||
});
|
||||
}
|
||||
if ($search) {
|
||||
Object.entries($search).forEach(([key, val]) => {
|
||||
if (val) {
|
||||
void bd.whereILike(key as never, val as never);
|
||||
}
|
||||
});
|
||||
}
|
||||
return bd;
|
||||
};
|
||||
|
||||
export type TFindOpt<R extends object = object> = {
|
||||
export type TFindReturn<TQuery extends Knex.QueryBuilder, TCount extends boolean = false> = Array<
|
||||
Awaited<TQuery>[0] &
|
||||
(TCount extends true
|
||||
? {
|
||||
count: string;
|
||||
}
|
||||
: unknown)
|
||||
>;
|
||||
|
||||
export type TFindOpt<R extends object = object, TCount extends boolean = boolean> = {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
sort?: Array<[keyof R, "asc" | "desc"] | [keyof R, "asc" | "desc", "first" | "last"]>;
|
||||
count?: TCount;
|
||||
tx?: Knex;
|
||||
};
|
||||
|
||||
@ -66,18 +86,22 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
|
||||
throw new DatabaseError({ error, name: "Find one" });
|
||||
}
|
||||
},
|
||||
find: async (
|
||||
find: async <TCount extends boolean = false>(
|
||||
filter: TFindFilter<Tables[Tname]["base"]>,
|
||||
{ offset, limit, sort, tx }: TFindOpt<Tables[Tname]["base"]> = {}
|
||||
{ offset, limit, sort, count, tx }: TFindOpt<Tables[Tname]["base"], TCount> = {}
|
||||
) => {
|
||||
try {
|
||||
const query = (tx || db.replicaNode())(tableName).where(buildFindFilter(filter));
|
||||
if (count) {
|
||||
void query.select(db.raw("COUNT(*) OVER() AS count"));
|
||||
void query.select("*");
|
||||
}
|
||||
if (limit) void query.limit(limit);
|
||||
if (offset) void query.offset(offset);
|
||||
if (sort) {
|
||||
void query.orderBy(sort.map(([column, order, nulls]) => ({ column: column as string, order, nulls })));
|
||||
}
|
||||
const res = await query;
|
||||
const res = (await query) as TFindReturn<typeof query, TCount>;
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find one" });
|
||||
@ -104,6 +128,29 @@ export const ormify = <DbOps extends object, Tname extends keyof Tables>(db: Kne
|
||||
throw new DatabaseError({ error, name: "Create" });
|
||||
}
|
||||
},
|
||||
// This spilit the insert into multiple chunk
|
||||
batchInsert: async (data: readonly Tables[Tname]["insert"][], tx?: Knex) => {
|
||||
try {
|
||||
if (!data.length) return [];
|
||||
const res = await (tx || db).batchInsert(tableName, data as never).returning("*");
|
||||
return res as Tables[Tname]["base"][];
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "batchInsert" });
|
||||
}
|
||||
},
|
||||
upsert: async (data: readonly Tables[Tname]["insert"][], onConflictField: keyof Tables[Tname]["base"], tx?: Knex) => {
|
||||
try {
|
||||
if (!data.length) return [];
|
||||
const res = await (tx || db)(tableName)
|
||||
.insert(data as never)
|
||||
.onConflict(onConflictField as never)
|
||||
.merge()
|
||||
.returning("*");
|
||||
return res;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Create" });
|
||||
}
|
||||
},
|
||||
updateById: async (
|
||||
id: string,
|
||||
{
|
||||
|
@ -42,3 +42,13 @@ export type RequiredKeys<T> = {
|
||||
}[keyof T];
|
||||
|
||||
export type PickRequired<T> = Pick<T, RequiredKeys<T>>;
|
||||
|
||||
export enum EnforcementLevel {
|
||||
Hard = "hard",
|
||||
Soft = "soft"
|
||||
}
|
||||
|
||||
export enum SecretSharingAccessType {
|
||||
Anyone = "anyone",
|
||||
Organization = "organization"
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user