mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-15 09:42:14 +00:00
Compare commits
434 Commits
infisical/
...
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 | |||
1cb4dc9e84 |
@ -22,14 +22,14 @@ jobs:
|
|||||||
# uncomment this when testing locally using nektos/act
|
# uncomment this when testing locally using nektos/act
|
||||||
- uses: KengoTODA/actions-setup-docker-compose@v1
|
- uses: KengoTODA/actions-setup-docker-compose@v1
|
||||||
if: ${{ env.ACT }}
|
if: ${{ env.ACT }}
|
||||||
name: Install `docker-compose` for local simulations
|
name: Install `docker compose` for local simulations
|
||||||
with:
|
with:
|
||||||
version: "2.14.2"
|
version: "2.14.2"
|
||||||
- name: 📦Build the latest image
|
- name: 📦Build the latest image
|
||||||
run: docker build --tag infisical-api .
|
run: docker build --tag infisical-api .
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
- name: Start postgres and redis
|
- 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
|
- name: Start the server
|
||||||
run: |
|
run: |
|
||||||
echo "SECRET_SCANNING_GIT_APP_ID=793712" >> .env
|
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
|
run: oasdiff breaking https://app.infisical.com/api/docs/json http://localhost:4000/api/docs/json --fail-on ERR
|
||||||
- name: cleanup
|
- name: cleanup
|
||||||
run: |
|
run: |
|
||||||
docker-compose -f "docker-compose.dev.yml" down
|
docker compose -f "docker-compose.dev.yml" down
|
||||||
docker stop infisical-api
|
docker stop infisical-api
|
||||||
docker remove 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: actions/checkout@v3
|
||||||
- uses: KengoTODA/actions-setup-docker-compose@v1
|
- uses: KengoTODA/actions-setup-docker-compose@v1
|
||||||
if: ${{ env.ACT }}
|
if: ${{ env.ACT }}
|
||||||
name: Install `docker-compose` for local simulations
|
name: Install `docker compose` for local simulations
|
||||||
with:
|
with:
|
||||||
version: "2.14.2"
|
version: "2.14.2"
|
||||||
- name: 🔧 Setup Node 20
|
- name: 🔧 Setup Node 20
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
run: npm install
|
run: npm install
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
- name: Start postgres and redis
|
- 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
|
- name: Start integration test
|
||||||
run: npm run test:e2e
|
run: npm run test:e2e
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
@ -44,4 +44,4 @@ jobs:
|
|||||||
ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218
|
ENCRYPTION_KEY: 4bnfe4e407b8921c104518903515b218
|
||||||
- name: cleanup
|
- name: cleanup
|
||||||
run: |
|
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_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
|
||||||
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
|
||||||
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
|
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
|
run: go test -v -count=1 ./test
|
||||||
|
13
Makefile
13
Makefile
@ -15,3 +15,16 @@ up-prod:
|
|||||||
|
|
||||||
down:
|
down:
|
||||||
docker compose -f docker-compose.dev.yml 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
|
||||||
|
|
||||||
|
576
backend/e2e-test/routes/v3/secrets-v2.spec.ts
Normal file
576
backend/e2e-test/routes/v3/secrets-v2.spec.ts
Normal file
@ -0,0 +1,576 @@
|
|||||||
|
import { SecretType } from "@app/db/schemas";
|
||||||
|
import { seedData1 } from "@app/db/seed-data";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
type TRawSecret = {
|
||||||
|
secretKey: string;
|
||||||
|
secretValue: string;
|
||||||
|
secretComment?: string;
|
||||||
|
version: number;
|
||||||
|
};
|
||||||
|
const createSecret = async (dto: { path: string; key: string; value: string; comment: string; type?: SecretType }) => {
|
||||||
|
const createSecretReqBody = {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
type: dto.type || SecretType.Shared,
|
||||||
|
secretPath: dto.path,
|
||||||
|
secretKey: dto.key,
|
||||||
|
secretValue: dto.value,
|
||||||
|
secretComment: dto.comment
|
||||||
|
};
|
||||||
|
const createSecRes = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v3/secrets/raw/${dto.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: createSecretReqBody
|
||||||
|
});
|
||||||
|
expect(createSecRes.statusCode).toBe(200);
|
||||||
|
const createdSecretPayload = JSON.parse(createSecRes.payload);
|
||||||
|
expect(createdSecretPayload).toHaveProperty("secret");
|
||||||
|
return createdSecretPayload.secret as TRawSecret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSecret = async (dto: { path: string; key: string }) => {
|
||||||
|
const deleteSecRes = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v3/secrets/raw/${dto.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
secretPath: dto.path
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(deleteSecRes.statusCode).toBe(200);
|
||||||
|
const updatedSecretPayload = JSON.parse(deleteSecRes.payload);
|
||||||
|
expect(updatedSecretPayload).toHaveProperty("secret");
|
||||||
|
return updatedSecretPayload.secret as TRawSecret;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe.each([{ auth: AuthMode.JWT }, { auth: AuthMode.IDENTITY_ACCESS_TOKEN }])(
|
||||||
|
"Secret V2 Architecture - $auth mode",
|
||||||
|
async ({ auth }) => {
|
||||||
|
let folderId = "";
|
||||||
|
let authToken = "";
|
||||||
|
const secretTestCases = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
secret: {
|
||||||
|
key: "SEC1",
|
||||||
|
value: "something-secret",
|
||||||
|
comment: "some comment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/nested1/nested2/folder",
|
||||||
|
secret: {
|
||||||
|
key: "NESTED-SEC1",
|
||||||
|
value: "something-secret",
|
||||||
|
comment: "some comment"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
secret: {
|
||||||
|
key: "secret-key-2",
|
||||||
|
value: `-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn
|
||||||
|
hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq
|
||||||
|
fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI
|
||||||
|
ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15
|
||||||
|
QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT
|
||||||
|
aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46
|
||||||
|
IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie
|
||||||
|
nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi
|
||||||
|
TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw
|
||||||
|
q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj
|
||||||
|
YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP
|
||||||
|
ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7
|
||||||
|
6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3
|
||||||
|
EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt
|
||||||
|
IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K
|
||||||
|
d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH
|
||||||
|
UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL
|
||||||
|
3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2
|
||||||
|
HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0
|
||||||
|
PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8
|
||||||
|
Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib
|
||||||
|
BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb
|
||||||
|
HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo
|
||||||
|
QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX
|
||||||
|
MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9
|
||||||
|
omQDpP86RX/hIIQ+JyLSaWYa
|
||||||
|
-----END PRIVATE KEY-----`,
|
||||||
|
comment:
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/nested1/nested2/folder",
|
||||||
|
secret: {
|
||||||
|
key: "secret-key-3",
|
||||||
|
value: `-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCa6eeFk+cMVqFn
|
||||||
|
hoVQDYgn2Ptp5Azysr2UPq6P73pCL9BzUtOXKZROqDyGehzzfg3wE2KdYU1Jk5Uq
|
||||||
|
fP0ZOWDIlM2SaVCSI3FW32o5+ZiggjpqcVdLFc/PS0S/ZdSmpPd8h11iO2brtIAI
|
||||||
|
ugTW8fcKlGSNUwx9aFmE7A6JnTRliTxB1l6QaC+YAwTK39VgeVH2gDSWC407aS15
|
||||||
|
QobAkaBKKmFkzB5D7i2ZJwt+uXJV/rbLmyDmtnw0lubciGn7NX9wbYef180fisqT
|
||||||
|
aPNAz0nPKk0fFH2Wd5MZixNGbrrpDA+FCYvI5doThZyT2hpj08qWP07oXXCAqw46
|
||||||
|
IEupNSILAgMBAAECggEBAIJb5KzeaiZS3B3O8G4OBQ5rJB3WfyLYUHnoSWLsBbie
|
||||||
|
nc392/ovThLmtZAAQE6SO85Tsb93+t64Z2TKqv1H8G658UeMgfWIB78v4CcLJ2mi
|
||||||
|
TN/3opqXrzjkQOTDHzBgT7al/mpETHZ6fOdbCemK0fVALGFUioUZg4M8VXtuI4Jw
|
||||||
|
q28jAyoRKrCrzda4BeQ553NZ4G5RvwhX3O2I8B8upTbt5hLcisBKy8MPLYY5LUFj
|
||||||
|
YKAP+raf6QLliP6KYHuVxUlgzxjLTxVG41etcyqqZF+foyiKBO3PU3n8oh++tgQP
|
||||||
|
ExOxiR0JSkBG5b+oOBD0zxcvo3/SjBHn0dJOZCSU2SkCgYEAyCe676XnNyBZMRD7
|
||||||
|
6trsaoiCWBpA6M8H44+x3w4cQFtqV38RyLy60D+iMKjIaLqeBbnay61VMzo24Bz3
|
||||||
|
EuF2n4+9k/MetLJ0NCw8HmN5k0WSMD2BFsJWG8glVbzaqzehP4tIclwDTYc1jQVt
|
||||||
|
IoV2/iL7HGT+x2daUwbU5kN5hK0CgYEAxiLB+fmjxJW7VY4SHDLqPdpIW0q/kv4K
|
||||||
|
d/yZBrCX799vjmFb9vLh7PkQUfJhMJ/ttJOd7EtT3xh4mfkBeLfHwVU0d/ahbmSH
|
||||||
|
UJu/E9ZGxAW3PP0kxHZtPrLKQwBnfq8AxBauIhR3rPSorQTIOKtwz1jMlHFSUpuL
|
||||||
|
3KeK2YfDYJcCgYEAkQnJOlNcAuRb/WQzSHIvktssqK8NjiZHryy3Vc0hx7j2jES2
|
||||||
|
HGI2dSVHYD9OSiXA0KFm3OTTsnViwm/60iGzFdjRJV6tR39xGUVcoyCuPnvRfUd0
|
||||||
|
PYvBXgxgkYpyYlPDcwp5CvWGJy3tLi1acgOIwIuUr3S38sL//t4adGk8q1kCgYB8
|
||||||
|
Jbs1Tl53BvrimKpwUNbE+sjrquJu0A7vL68SqgQJoQ7dP9PH4Ff/i+/V6PFM7mib
|
||||||
|
BQOm02wyFbs7fvKVGVJoqWK+6CIucX732x7W5yRgHtS5ukQXdbzt1Ek3wkEW98Cb
|
||||||
|
HTruz7RNAt/NyXlLSODeit1lBbx3Vk9EaxZtRsv88QKBgGn7JwXgez9NOyobsNIo
|
||||||
|
QVO80rpUeenSjuFi+R0VmbLKe/wgAQbYJ0xTAsQ0btqViMzB27D6mJyC+KUIwWNX
|
||||||
|
MN8a+m46v4kqvZkKL2c4gmDibyURNe/vCtCHFuanJS/1mo2tr4XDyEeiuK52eTd9
|
||||||
|
omQDpP86RX/hIIQ+JyLSaWYa
|
||||||
|
-----END PRIVATE KEY-----`,
|
||||||
|
comment:
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/nested1/nested2/folder",
|
||||||
|
secret: {
|
||||||
|
key: "secret-key-3",
|
||||||
|
value:
|
||||||
|
"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gU2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uCg==",
|
||||||
|
comment: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
if (auth === AuthMode.JWT) {
|
||||||
|
authToken = jwtAuthToken;
|
||||||
|
} else if (auth === AuthMode.IDENTITY_ACCESS_TOKEN) {
|
||||||
|
const identityLogin = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: "/api/v1/auth/universal-auth/login",
|
||||||
|
body: {
|
||||||
|
clientSecret: seedData1.machineIdentity.clientCredentials.secret,
|
||||||
|
clientId: seedData1.machineIdentity.clientCredentials.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(identityLogin.statusCode).toBe(200);
|
||||||
|
authToken = identityLogin.json().accessToken;
|
||||||
|
}
|
||||||
|
// create a deep folder
|
||||||
|
const folderCreate = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v1/folders`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${jwtAuthToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
name: "folder",
|
||||||
|
path: "/nested1/nested2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(folderCreate.statusCode).toBe(200);
|
||||||
|
folderId = folderCreate.json().folder.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
const deleteFolder = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v1/folders/${folderId}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
path: "/nested1/nested2"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(deleteFolder.statusCode).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getSecrets = async (environment: string, secretPath = "/") => {
|
||||||
|
const res = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v3/secrets/raw`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
secretPath,
|
||||||
|
environment,
|
||||||
|
workspaceId: seedData1.projectV3.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const secrets: TRawSecret[] = JSON.parse(res.payload).secrets || [];
|
||||||
|
return secrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Create secret in path $path", async ({ secret, path }) => {
|
||||||
|
const createdSecret = await createSecret({ path, ...secret });
|
||||||
|
expect(createdSecret.secretKey).toEqual(secret.key);
|
||||||
|
expect(createdSecret.secretValue).toEqual(secret.value);
|
||||||
|
expect(createdSecret.secretComment || "").toEqual(secret.comment);
|
||||||
|
expect(createdSecret.version).toEqual(1);
|
||||||
|
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
secretValue: secret.value,
|
||||||
|
type: SecretType.Shared
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
await deleteSecret({ path, key: secret.key });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Get secret by name in path $path", async ({ secret, path }) => {
|
||||||
|
await createSecret({ path, ...secret });
|
||||||
|
|
||||||
|
const getSecByNameRes = await testServer.inject({
|
||||||
|
method: "GET",
|
||||||
|
url: `/api/v3/secrets/raw/${secret.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
secretPath: path,
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(getSecByNameRes.statusCode).toBe(200);
|
||||||
|
const getSecretByNamePayload = JSON.parse(getSecByNameRes.payload);
|
||||||
|
expect(getSecretByNamePayload).toHaveProperty("secret");
|
||||||
|
const decryptedSecret = getSecretByNamePayload.secret as TRawSecret;
|
||||||
|
expect(decryptedSecret.secretKey).toEqual(secret.key);
|
||||||
|
expect(decryptedSecret.secretValue).toEqual(secret.value);
|
||||||
|
expect(decryptedSecret.secretComment || "").toEqual(secret.comment);
|
||||||
|
|
||||||
|
await deleteSecret({ path, key: secret.key });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (auth === AuthMode.JWT) {
|
||||||
|
test.each(secretTestCases)(
|
||||||
|
"Creating personal secret without shared throw error in path $path",
|
||||||
|
async ({ secret }) => {
|
||||||
|
const createSecretReqBody = {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
type: SecretType.Personal,
|
||||||
|
secretKey: secret.key,
|
||||||
|
secretValue: secret.value,
|
||||||
|
secretComment: secret.comment
|
||||||
|
};
|
||||||
|
const createSecRes = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v3/secrets/raw/SEC2`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: createSecretReqBody
|
||||||
|
});
|
||||||
|
const payload = JSON.parse(createSecRes.payload);
|
||||||
|
expect(createSecRes.statusCode).toBe(400);
|
||||||
|
expect(payload.error).toEqual("BadRequest");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Creating personal secret in path $path", async ({ secret, path }) => {
|
||||||
|
await createSecret({ path, ...secret });
|
||||||
|
|
||||||
|
const createSecretReqBody = {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
type: SecretType.Personal,
|
||||||
|
secretPath: path,
|
||||||
|
secretKey: secret.key,
|
||||||
|
secretValue: "personal-value",
|
||||||
|
secretComment: secret.comment
|
||||||
|
};
|
||||||
|
const createSecRes = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v3/secrets/raw/${secret.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: createSecretReqBody
|
||||||
|
});
|
||||||
|
expect(createSecRes.statusCode).toBe(200);
|
||||||
|
|
||||||
|
// list secrets should contain personal one and shared one
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
secretValue: secret.value,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
secretValue: "personal-value",
|
||||||
|
type: SecretType.Personal
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecret({ path, key: secret.key });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(secretTestCases)(
|
||||||
|
"Deleting personal one should not delete shared secret in path $path",
|
||||||
|
async ({ secret, path }) => {
|
||||||
|
await createSecret({ path, ...secret }); // shared one
|
||||||
|
await createSecret({ path, ...secret, type: SecretType.Personal });
|
||||||
|
|
||||||
|
// shared secret deletion should delete personal ones also
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}),
|
||||||
|
expect.not.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
type: SecretType.Personal
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
await deleteSecret({ path, key: secret.key });
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Update secret in path $path", async ({ path, secret }) => {
|
||||||
|
await createSecret({ path, ...secret });
|
||||||
|
const updateSecretReqBody = {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
type: SecretType.Shared,
|
||||||
|
secretPath: path,
|
||||||
|
secretKey: secret.key,
|
||||||
|
secretValue: "new-value",
|
||||||
|
secretComment: secret.comment
|
||||||
|
};
|
||||||
|
const updateSecRes = await testServer.inject({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `/api/v3/secrets/raw/${secret.key}`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: updateSecretReqBody
|
||||||
|
});
|
||||||
|
expect(updateSecRes.statusCode).toBe(200);
|
||||||
|
const updatedSecretPayload = JSON.parse(updateSecRes.payload);
|
||||||
|
expect(updatedSecretPayload).toHaveProperty("secret");
|
||||||
|
const decryptedSecret = updatedSecretPayload.secret;
|
||||||
|
expect(decryptedSecret.secretKey).toEqual(secret.key);
|
||||||
|
expect(decryptedSecret.secretValue).toEqual("new-value");
|
||||||
|
expect(decryptedSecret.secretComment || "").toEqual(secret.comment);
|
||||||
|
|
||||||
|
// list secret should have updated value
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
secretValue: "new-value",
|
||||||
|
type: SecretType.Shared
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
await deleteSecret({ path, key: secret.key });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Delete secret in path $path", async ({ secret, path }) => {
|
||||||
|
await createSecret({ path, ...secret });
|
||||||
|
const deletedSecret = await deleteSecret({ path, key: secret.key });
|
||||||
|
expect(deletedSecret.secretKey).toEqual(secret.key);
|
||||||
|
|
||||||
|
// shared secret deletion should delete personal ones also
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.not.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}),
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: secret.key,
|
||||||
|
type: SecretType.Personal
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Bulk create secrets in path $path", async ({ secret, path }) => {
|
||||||
|
const createSharedSecRes = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v3/secrets/batch/raw`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
secretPath: path,
|
||||||
|
secrets: Array.from(Array(5)).map((_e, i) => ({
|
||||||
|
secretKey: `BULK-${secret.key}-${i + 1}`,
|
||||||
|
secretValue: secret.value,
|
||||||
|
secretComment: secret.comment
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(createSharedSecRes.statusCode).toBe(200);
|
||||||
|
const createSharedSecPayload = JSON.parse(createSharedSecRes.payload);
|
||||||
|
expect(createSharedSecPayload).toHaveProperty("secrets");
|
||||||
|
|
||||||
|
// bulk ones should exist
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
Array.from(Array(5)).map((_e, i) =>
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: `BULK-${secret.key}-${i + 1}`,
|
||||||
|
secretValue: secret.value,
|
||||||
|
type: SecretType.Shared
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Bulk create fail on existing secret in path $path", async ({ secret, path }) => {
|
||||||
|
await createSecret({ ...secret, key: `BULK-${secret.key}-1`, path });
|
||||||
|
|
||||||
|
const createSharedSecRes = await testServer.inject({
|
||||||
|
method: "POST",
|
||||||
|
url: `/api/v3/secrets/batch/raw`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
secretPath: path,
|
||||||
|
secrets: Array.from(Array(5)).map((_e, i) => ({
|
||||||
|
secretKey: `BULK-${secret.key}-${i + 1}`,
|
||||||
|
secretValue: secret.value,
|
||||||
|
secretComment: secret.comment
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(createSharedSecRes.statusCode).toBe(400);
|
||||||
|
|
||||||
|
await deleteSecret({ path, key: `BULK-${secret.key}-1` });
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Bulk update secrets in path $path", async ({ secret, path }) => {
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(Array(5)).map((_e, i) => createSecret({ ...secret, key: `BULK-${secret.key}-${i + 1}`, path }))
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateSharedSecRes = await testServer.inject({
|
||||||
|
method: "PATCH",
|
||||||
|
url: `/api/v3/secrets/batch/raw`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
secretPath: path,
|
||||||
|
secrets: Array.from(Array(5)).map((_e, i) => ({
|
||||||
|
secretKey: `BULK-${secret.key}-${i + 1}`,
|
||||||
|
secretValue: "update-value",
|
||||||
|
secretComment: secret.comment
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(updateSharedSecRes.statusCode).toBe(200);
|
||||||
|
const updateSharedSecPayload = JSON.parse(updateSharedSecRes.payload);
|
||||||
|
expect(updateSharedSecPayload).toHaveProperty("secrets");
|
||||||
|
|
||||||
|
// bulk ones should exist
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
Array.from(Array(5)).map((_e, i) =>
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: `BULK-${secret.key}-${i + 1}`,
|
||||||
|
secretValue: "update-value",
|
||||||
|
type: SecretType.Shared
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(Array(5)).map((_e, i) => deleteSecret({ path, key: `BULK-${secret.key}-${i + 1}` }))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(secretTestCases)("Bulk delete secrets in path $path", async ({ secret, path }) => {
|
||||||
|
await Promise.all(
|
||||||
|
Array.from(Array(5)).map((_e, i) => createSecret({ ...secret, key: `BULK-${secret.key}-${i + 1}`, path }))
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletedSharedSecRes = await testServer.inject({
|
||||||
|
method: "DELETE",
|
||||||
|
url: `/api/v3/secrets/batch/raw`,
|
||||||
|
headers: {
|
||||||
|
authorization: `Bearer ${authToken}`
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
workspaceId: seedData1.projectV3.id,
|
||||||
|
environment: seedData1.environment.slug,
|
||||||
|
secretPath: path,
|
||||||
|
secrets: Array.from(Array(5)).map((_e, i) => ({
|
||||||
|
secretKey: `BULK-${secret.key}-${i + 1}`
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deletedSharedSecRes.statusCode).toBe(200);
|
||||||
|
const deletedSecretPayload = JSON.parse(deletedSharedSecRes.payload);
|
||||||
|
expect(deletedSecretPayload).toHaveProperty("secrets");
|
||||||
|
|
||||||
|
// bulk ones should exist
|
||||||
|
const secrets = await getSecrets(seedData1.environment.slug, path);
|
||||||
|
expect(secrets).toEqual(
|
||||||
|
expect.not.arrayContaining(
|
||||||
|
Array.from(Array(5)).map((_e, i) =>
|
||||||
|
expect.objectContaining({
|
||||||
|
secretKey: `BULK-${secret.value}-${i + 1}`,
|
||||||
|
type: SecretType.Shared
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
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",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "tsx watch --clear-screen=false ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine",
|
"dev": "tsx watch --clear-screen=false ./src/main.ts | pino-pretty --colorize --colorizeObjects --singleLine",
|
||||||
"dev:docker": "nodemon",
|
"dev:docker": "nodemon",
|
||||||
"build": "tsup",
|
"build": "tsup --sourcemap",
|
||||||
"build:frontend": "npm run build --prefix ../frontend",
|
"build:frontend": "npm run build --prefix ../frontend",
|
||||||
"start": "node dist/main.mjs",
|
"start": "node --enable-source-maps dist/main.mjs",
|
||||||
"type:check": "tsc --noEmit",
|
"type:check": "tsc --noEmit",
|
||||||
"lint:fix": "eslint --fix --ext js,ts ./src",
|
"lint:fix": "eslint --fix --ext js,ts ./src",
|
||||||
"lint": "eslint 'src/**/*.ts'",
|
"lint": "eslint 'src/**/*.ts'",
|
||||||
"test:e2e": "vitest run -c vitest.e2e.config.ts",
|
"test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1",
|
||||||
"test:e2e-watch": "vitest -c vitest.e2e.config.ts",
|
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
||||||
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
||||||
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
||||||
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
||||||
@ -78,6 +78,7 @@
|
|||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
|
"@types/safe-regex": "^1.1.6",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
@ -121,10 +122,11 @@
|
|||||||
"@fastify/swagger": "^8.14.0",
|
"@fastify/swagger": "^8.14.0",
|
||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
"@peculiar/asn1-schema": "^2.3.8",
|
"@peculiar/asn1-schema": "^2.3.8",
|
||||||
"@peculiar/x509": "^1.10.0",
|
"@peculiar/x509": "^1.12.1",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
"@team-plain/typescript-sdk": "^4.6.1",
|
"@team-plain/typescript-sdk": "^4.6.1",
|
||||||
@ -171,6 +173,7 @@
|
|||||||
"pino": "^8.16.2",
|
"pino": "^8.16.2",
|
||||||
"posthog-node": "^3.6.2",
|
"posthog-node": "^3.6.2",
|
||||||
"probot": "^13.0.0",
|
"probot": "^13.0.0",
|
||||||
|
"safe-regex": "^2.1.1",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
"tedious": "^18.2.1",
|
"tedious": "^18.2.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
|
@ -7,14 +7,33 @@ const prompt = promptSync({
|
|||||||
sigint: true
|
sigint: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type ComponentType = 1 | 2 | 3;
|
||||||
|
|
||||||
console.log(`
|
console.log(`
|
||||||
Component List
|
Component List
|
||||||
--------------
|
--------------
|
||||||
|
0. Exit
|
||||||
1. Service component
|
1. Service component
|
||||||
2. DAL component
|
2. DAL component
|
||||||
3. Router 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) {
|
if (componentType === 1) {
|
||||||
const componentName = prompt("Enter service name: ");
|
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 { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-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 { 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 { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
||||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-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 { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
||||||
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-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 { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-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 { TIntegrationAuthServiceFactory } from "@app/services/integration-auth/integration-auth-service";
|
||||||
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
import { TOrgRoleServiceFactory } from "@app/services/org/org-role-service";
|
||||||
import { TOrgServiceFactory } from "@app/services/org/org-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 { TProjectServiceFactory } from "@app/services/project/project-service";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
import { TProjectEnvServiceFactory } from "@app/services/project-env/project-env-service";
|
||||||
@ -88,6 +93,7 @@ declare module "fastify" {
|
|||||||
id: string;
|
id: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
};
|
};
|
||||||
|
rateLimits: RateLimitConfiguration;
|
||||||
// passport data
|
// passport data
|
||||||
passportUser: {
|
passportUser: {
|
||||||
isUserCompleted: string;
|
isUserCompleted: string;
|
||||||
@ -113,6 +119,7 @@ declare module "fastify" {
|
|||||||
group: TGroupServiceFactory;
|
group: TGroupServiceFactory;
|
||||||
groupProject: TGroupProjectServiceFactory;
|
groupProject: TGroupProjectServiceFactory;
|
||||||
apiKey: TApiKeyServiceFactory;
|
apiKey: TApiKeyServiceFactory;
|
||||||
|
pkiAlert: TPkiAlertServiceFactory;
|
||||||
project: TProjectServiceFactory;
|
project: TProjectServiceFactory;
|
||||||
projectMembership: TProjectMembershipServiceFactory;
|
projectMembership: TProjectMembershipServiceFactory;
|
||||||
projectEnv: TProjectEnvServiceFactory;
|
projectEnv: TProjectEnvServiceFactory;
|
||||||
@ -150,8 +157,10 @@ declare module "fastify" {
|
|||||||
auditLog: TAuditLogServiceFactory;
|
auditLog: TAuditLogServiceFactory;
|
||||||
auditLogStream: TAuditLogStreamServiceFactory;
|
auditLogStream: TAuditLogStreamServiceFactory;
|
||||||
certificate: TCertificateServiceFactory;
|
certificate: TCertificateServiceFactory;
|
||||||
|
certificateTemplate: TCertificateTemplateServiceFactory;
|
||||||
certificateAuthority: TCertificateAuthorityServiceFactory;
|
certificateAuthority: TCertificateAuthorityServiceFactory;
|
||||||
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
certificateAuthorityCrl: TCertificateAuthorityCrlServiceFactory;
|
||||||
|
pkiCollection: TPkiCollectionServiceFactory;
|
||||||
secretScanning: TSecretScanningServiceFactory;
|
secretScanning: TSecretScanningServiceFactory;
|
||||||
license: TLicenseServiceFactory;
|
license: TLicenseServiceFactory;
|
||||||
trustedIp: TTrustedIpServiceFactory;
|
trustedIp: TTrustedIpServiceFactory;
|
||||||
@ -165,6 +174,7 @@ declare module "fastify" {
|
|||||||
rateLimit: TRateLimitServiceFactory;
|
rateLimit: TRateLimitServiceFactory;
|
||||||
userEngagement: TUserEngagementServiceFactory;
|
userEngagement: TUserEngagementServiceFactory;
|
||||||
externalKms: TExternalKmsServiceFactory;
|
externalKms: TExternalKmsServiceFactory;
|
||||||
|
orgAdmin: TOrgAdminServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
98
backend/src/@types/knex.d.ts
vendored
98
backend/src/@types/knex.d.ts
vendored
@ -53,6 +53,9 @@ import {
|
|||||||
TCertificateSecretsUpdate,
|
TCertificateSecretsUpdate,
|
||||||
TCertificatesInsert,
|
TCertificatesInsert,
|
||||||
TCertificatesUpdate,
|
TCertificatesUpdate,
|
||||||
|
TCertificateTemplates,
|
||||||
|
TCertificateTemplatesInsert,
|
||||||
|
TCertificateTemplatesUpdate,
|
||||||
TDynamicSecretLeases,
|
TDynamicSecretLeases,
|
||||||
TDynamicSecretLeasesInsert,
|
TDynamicSecretLeasesInsert,
|
||||||
TDynamicSecretLeasesUpdate,
|
TDynamicSecretLeasesUpdate,
|
||||||
@ -161,6 +164,15 @@ import {
|
|||||||
TOrgRoles,
|
TOrgRoles,
|
||||||
TOrgRolesInsert,
|
TOrgRolesInsert,
|
||||||
TOrgRolesUpdate,
|
TOrgRolesUpdate,
|
||||||
|
TPkiAlerts,
|
||||||
|
TPkiAlertsInsert,
|
||||||
|
TPkiAlertsUpdate,
|
||||||
|
TPkiCollectionItems,
|
||||||
|
TPkiCollectionItemsInsert,
|
||||||
|
TPkiCollectionItemsUpdate,
|
||||||
|
TPkiCollections,
|
||||||
|
TPkiCollectionsInsert,
|
||||||
|
TPkiCollectionsUpdate,
|
||||||
TProjectBots,
|
TProjectBots,
|
||||||
TProjectBotsInsert,
|
TProjectBotsInsert,
|
||||||
TProjectBotsUpdate,
|
TProjectBotsUpdate,
|
||||||
@ -204,6 +216,9 @@ import {
|
|||||||
TSecretApprovalRequestSecretTags,
|
TSecretApprovalRequestSecretTags,
|
||||||
TSecretApprovalRequestSecretTagsInsert,
|
TSecretApprovalRequestSecretTagsInsert,
|
||||||
TSecretApprovalRequestSecretTagsUpdate,
|
TSecretApprovalRequestSecretTagsUpdate,
|
||||||
|
TSecretApprovalRequestSecretTagsV2,
|
||||||
|
TSecretApprovalRequestSecretTagsV2Insert,
|
||||||
|
TSecretApprovalRequestSecretTagsV2Update,
|
||||||
TSecretApprovalRequestsInsert,
|
TSecretApprovalRequestsInsert,
|
||||||
TSecretApprovalRequestsReviewers,
|
TSecretApprovalRequestsReviewers,
|
||||||
TSecretApprovalRequestsReviewersInsert,
|
TSecretApprovalRequestsReviewersInsert,
|
||||||
@ -211,6 +226,9 @@ import {
|
|||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
TSecretApprovalRequestsSecretsInsert,
|
TSecretApprovalRequestsSecretsInsert,
|
||||||
TSecretApprovalRequestsSecretsUpdate,
|
TSecretApprovalRequestsSecretsUpdate,
|
||||||
|
TSecretApprovalRequestsSecretsV2,
|
||||||
|
TSecretApprovalRequestsSecretsV2Insert,
|
||||||
|
TSecretApprovalRequestsSecretsV2Update,
|
||||||
TSecretApprovalRequestsUpdate,
|
TSecretApprovalRequestsUpdate,
|
||||||
TSecretBlindIndexes,
|
TSecretBlindIndexes,
|
||||||
TSecretBlindIndexesInsert,
|
TSecretBlindIndexesInsert,
|
||||||
@ -227,9 +245,15 @@ import {
|
|||||||
TSecretReferences,
|
TSecretReferences,
|
||||||
TSecretReferencesInsert,
|
TSecretReferencesInsert,
|
||||||
TSecretReferencesUpdate,
|
TSecretReferencesUpdate,
|
||||||
|
TSecretReferencesV2,
|
||||||
|
TSecretReferencesV2Insert,
|
||||||
|
TSecretReferencesV2Update,
|
||||||
TSecretRotationOutputs,
|
TSecretRotationOutputs,
|
||||||
TSecretRotationOutputsInsert,
|
TSecretRotationOutputsInsert,
|
||||||
TSecretRotationOutputsUpdate,
|
TSecretRotationOutputsUpdate,
|
||||||
|
TSecretRotationOutputV2,
|
||||||
|
TSecretRotationOutputV2Insert,
|
||||||
|
TSecretRotationOutputV2Update,
|
||||||
TSecretRotations,
|
TSecretRotations,
|
||||||
TSecretRotationsInsert,
|
TSecretRotationsInsert,
|
||||||
TSecretRotationsUpdate,
|
TSecretRotationsUpdate,
|
||||||
@ -248,6 +272,9 @@ import {
|
|||||||
TSecretSnapshotSecrets,
|
TSecretSnapshotSecrets,
|
||||||
TSecretSnapshotSecretsInsert,
|
TSecretSnapshotSecretsInsert,
|
||||||
TSecretSnapshotSecretsUpdate,
|
TSecretSnapshotSecretsUpdate,
|
||||||
|
TSecretSnapshotSecretsV2,
|
||||||
|
TSecretSnapshotSecretsV2Insert,
|
||||||
|
TSecretSnapshotSecretsV2Update,
|
||||||
TSecretSnapshotsInsert,
|
TSecretSnapshotsInsert,
|
||||||
TSecretSnapshotsUpdate,
|
TSecretSnapshotsUpdate,
|
||||||
TSecretsUpdate,
|
TSecretsUpdate,
|
||||||
@ -263,6 +290,9 @@ import {
|
|||||||
TSecretVersionTagJunction,
|
TSecretVersionTagJunction,
|
||||||
TSecretVersionTagJunctionInsert,
|
TSecretVersionTagJunctionInsert,
|
||||||
TSecretVersionTagJunctionUpdate,
|
TSecretVersionTagJunctionUpdate,
|
||||||
|
TSecretVersionV2TagJunction,
|
||||||
|
TSecretVersionV2TagJunctionInsert,
|
||||||
|
TSecretVersionV2TagJunctionUpdate,
|
||||||
TServiceTokens,
|
TServiceTokens,
|
||||||
TServiceTokensInsert,
|
TServiceTokensInsert,
|
||||||
TServiceTokensUpdate,
|
TServiceTokensUpdate,
|
||||||
@ -291,6 +321,17 @@ import {
|
|||||||
TWebhooksInsert,
|
TWebhooksInsert,
|
||||||
TWebhooksUpdate
|
TWebhooksUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import {
|
||||||
|
TSecretV2TagJunction,
|
||||||
|
TSecretV2TagJunctionInsert,
|
||||||
|
TSecretV2TagJunctionUpdate
|
||||||
|
} from "@app/db/schemas/secret-v2-tag-junction";
|
||||||
|
import {
|
||||||
|
TSecretVersionsV2,
|
||||||
|
TSecretVersionsV2Insert,
|
||||||
|
TSecretVersionsV2Update
|
||||||
|
} from "@app/db/schemas/secret-versions-v2";
|
||||||
|
import { TSecretsV2, TSecretsV2Insert, TSecretsV2Update } from "@app/db/schemas/secrets-v2";
|
||||||
|
|
||||||
declare module "knex" {
|
declare module "knex" {
|
||||||
namespace Knex {
|
namespace Knex {
|
||||||
@ -326,6 +367,11 @@ declare module "knex/types/tables" {
|
|||||||
TCertificateAuthorityCrlUpdate
|
TCertificateAuthorityCrlUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
|
[TableName.Certificate]: KnexOriginal.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
|
||||||
|
[TableName.CertificateTemplate]: KnexOriginal.CompositeTableType<
|
||||||
|
TCertificateTemplates,
|
||||||
|
TCertificateTemplatesInsert,
|
||||||
|
TCertificateTemplatesUpdate
|
||||||
|
>;
|
||||||
[TableName.CertificateBody]: KnexOriginal.CompositeTableType<
|
[TableName.CertificateBody]: KnexOriginal.CompositeTableType<
|
||||||
TCertificateBodies,
|
TCertificateBodies,
|
||||||
TCertificateBodiesInsert,
|
TCertificateBodiesInsert,
|
||||||
@ -336,6 +382,17 @@ declare module "knex/types/tables" {
|
|||||||
TCertificateSecretsInsert,
|
TCertificateSecretsInsert,
|
||||||
TCertificateSecretsUpdate
|
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<
|
[TableName.UserGroupMembership]: KnexOriginal.CompositeTableType<
|
||||||
TUserGroupMembership,
|
TUserGroupMembership,
|
||||||
TUserGroupMembershipInsert,
|
TUserGroupMembershipInsert,
|
||||||
@ -645,7 +702,23 @@ declare module "knex/types/tables" {
|
|||||||
TSecretScanningGitRisksUpdate
|
TSecretScanningGitRisksUpdate
|
||||||
>;
|
>;
|
||||||
[TableName.TrustedIps]: KnexOriginal.CompositeTableType<TTrustedIps, TTrustedIpsInsert, TTrustedIpsUpdate>;
|
[TableName.TrustedIps]: KnexOriginal.CompositeTableType<TTrustedIps, TTrustedIpsInsert, TTrustedIpsUpdate>;
|
||||||
|
[TableName.SecretV2]: KnexOriginal.CompositeTableType<TSecretsV2, TSecretsV2Insert, TSecretsV2Update>;
|
||||||
|
[TableName.SecretVersionV2]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretVersionsV2,
|
||||||
|
TSecretVersionsV2Insert,
|
||||||
|
TSecretVersionsV2Update
|
||||||
|
>;
|
||||||
|
[TableName.SecretReferenceV2]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretReferencesV2,
|
||||||
|
TSecretReferencesV2Insert,
|
||||||
|
TSecretReferencesV2Update
|
||||||
|
>;
|
||||||
// Junction tables
|
// Junction tables
|
||||||
|
[TableName.SecretV2JnTag]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretV2TagJunction,
|
||||||
|
TSecretV2TagJunctionInsert,
|
||||||
|
TSecretV2TagJunctionUpdate
|
||||||
|
>;
|
||||||
[TableName.JnSecretTag]: KnexOriginal.CompositeTableType<
|
[TableName.JnSecretTag]: KnexOriginal.CompositeTableType<
|
||||||
TSecretTagJunction,
|
TSecretTagJunction,
|
||||||
TSecretTagJunctionInsert,
|
TSecretTagJunctionInsert,
|
||||||
@ -656,6 +729,31 @@ declare module "knex/types/tables" {
|
|||||||
TSecretVersionTagJunctionInsert,
|
TSecretVersionTagJunctionInsert,
|
||||||
TSecretVersionTagJunctionUpdate
|
TSecretVersionTagJunctionUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretVersionV2Tag]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretVersionV2TagJunction,
|
||||||
|
TSecretVersionV2TagJunctionInsert,
|
||||||
|
TSecretVersionV2TagJunctionUpdate
|
||||||
|
>;
|
||||||
|
[TableName.SnapshotSecretV2]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretSnapshotSecretsV2,
|
||||||
|
TSecretSnapshotSecretsV2Insert,
|
||||||
|
TSecretSnapshotSecretsV2Update
|
||||||
|
>;
|
||||||
|
[TableName.SecretApprovalRequestSecretV2]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretApprovalRequestsSecretsV2,
|
||||||
|
TSecretApprovalRequestsSecretsV2Insert,
|
||||||
|
TSecretApprovalRequestsSecretsV2Update
|
||||||
|
>;
|
||||||
|
[TableName.SecretApprovalRequestSecretTagV2]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretApprovalRequestSecretTagsV2,
|
||||||
|
TSecretApprovalRequestSecretTagsV2Insert,
|
||||||
|
TSecretApprovalRequestSecretTagsV2Update
|
||||||
|
>;
|
||||||
|
[TableName.SecretRotationOutputV2]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretRotationOutputV2,
|
||||||
|
TSecretRotationOutputV2Insert,
|
||||||
|
TSecretRotationOutputV2Update
|
||||||
|
>;
|
||||||
// KMS service
|
// KMS service
|
||||||
[TableName.KmsServerRootConfig]: KnexOriginal.CompositeTableType<
|
[TableName.KmsServerRootConfig]: KnexOriginal.CompositeTableType<
|
||||||
TKmsRootConfig,
|
TKmsRootConfig,
|
||||||
|
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasKmsDataKeyCol = await knex.schema.hasColumn(TableName.Organization, "kmsEncryptedDataKey");
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (tb) => {
|
||||||
|
if (!hasKmsDataKeyCol) {
|
||||||
|
tb.binary("kmsEncryptedDataKey");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasKmsDataKeyCol = await knex.schema.hasColumn(TableName.Organization, "kmsEncryptedDataKey");
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
if (hasKmsDataKeyCol) {
|
||||||
|
t.dropColumn("kmsEncryptedDataKey");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasKmsSecretManagerEncryptedDataKey = await knex.schema.hasColumn(
|
||||||
|
TableName.Project,
|
||||||
|
"kmsSecretManagerEncryptedDataKey"
|
||||||
|
);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Project, (tb) => {
|
||||||
|
if (!hasKmsSecretManagerEncryptedDataKey) {
|
||||||
|
tb.binary("kmsSecretManagerEncryptedDataKey");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasKmsSecretManagerEncryptedDataKey = await knex.schema.hasColumn(
|
||||||
|
TableName.Project,
|
||||||
|
"kmsSecretManagerEncryptedDataKey"
|
||||||
|
);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||||
|
if (hasKmsSecretManagerEncryptedDataKey) {
|
||||||
|
t.dropColumn("kmsSecretManagerEncryptedDataKey");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
181
backend/src/db/migrations/20240730181850_secret-v2.ts
Normal file
181
backend/src/db/migrations/20240730181850_secret-v2.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { SecretType, TableName } from "../schemas";
|
||||||
|
import { createJunctionTable, createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const doesSecretV2TableExist = await knex.schema.hasTable(TableName.SecretV2);
|
||||||
|
if (!doesSecretV2TableExist) {
|
||||||
|
await knex.schema.createTable(TableName.SecretV2, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
|
t.string("type").notNullable().defaultTo(SecretType.Shared);
|
||||||
|
t.string("key", 500).notNullable();
|
||||||
|
t.binary("encryptedValue");
|
||||||
|
t.binary("encryptedComment");
|
||||||
|
t.string("reminderNote");
|
||||||
|
t.integer("reminderRepeatDays");
|
||||||
|
t.boolean("skipMultilineEncoding").defaultTo(false);
|
||||||
|
t.jsonb("metadata");
|
||||||
|
t.uuid("userId");
|
||||||
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
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);
|
||||||
|
|
||||||
|
// many to many relation between tags
|
||||||
|
await createJunctionTable(knex, TableName.SecretV2JnTag, TableName.SecretV2, TableName.SecretTag);
|
||||||
|
|
||||||
|
const doesSecretV2VersionTableExist = await knex.schema.hasTable(TableName.SecretVersionV2);
|
||||||
|
if (!doesSecretV2VersionTableExist) {
|
||||||
|
await knex.schema.createTable(TableName.SecretVersionV2, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
|
t.string("type").notNullable().defaultTo(SecretType.Shared);
|
||||||
|
t.string("key", 500).notNullable();
|
||||||
|
t.binary("encryptedValue");
|
||||||
|
t.binary("encryptedComment");
|
||||||
|
t.string("reminderNote");
|
||||||
|
t.integer("reminderRepeatDays");
|
||||||
|
t.boolean("skipMultilineEncoding").defaultTo(false);
|
||||||
|
t.jsonb("metadata");
|
||||||
|
// to avoid orphan rows
|
||||||
|
t.uuid("envId");
|
||||||
|
t.foreign("envId").references("id").inTable(TableName.Environment).onDelete("CASCADE");
|
||||||
|
t.uuid("secretId").notNullable();
|
||||||
|
t.uuid("folderId").notNullable();
|
||||||
|
t.uuid("userId");
|
||||||
|
t.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SecretVersionV2);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretReferenceV2))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretReferenceV2, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("environment").notNullable();
|
||||||
|
t.string("secretPath").notNullable();
|
||||||
|
t.string("secretKey", 500).notNullable();
|
||||||
|
t.uuid("secretId").notNullable();
|
||||||
|
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createJunctionTable(knex, TableName.SecretVersionV2Tag, TableName.SecretVersionV2, TableName.SecretTag);
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretApprovalRequestSecretV2))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretApprovalRequestSecretV2, (t) => {
|
||||||
|
// everything related to secret
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.integer("version").defaultTo(1);
|
||||||
|
t.string("key", 500).notNullable();
|
||||||
|
t.binary("encryptedValue");
|
||||||
|
t.binary("encryptedComment");
|
||||||
|
t.string("reminderNote");
|
||||||
|
t.integer("reminderRepeatDays");
|
||||||
|
t.boolean("skipMultilineEncoding").defaultTo(false);
|
||||||
|
t.jsonb("metadata");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
// commit details
|
||||||
|
t.uuid("requestId").notNullable();
|
||||||
|
t.foreign("requestId").references("id").inTable(TableName.SecretApprovalRequest).onDelete("CASCADE");
|
||||||
|
t.string("op").notNullable();
|
||||||
|
t.uuid("secretId");
|
||||||
|
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("SET NULL");
|
||||||
|
t.uuid("secretVersion");
|
||||||
|
t.foreign("secretVersion").references("id").inTable(TableName.SecretVersionV2).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretApprovalRequestSecretTagV2))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretApprovalRequestSecretTagV2, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.uuid("secretId").notNullable();
|
||||||
|
t.foreign("secretId").references("id").inTable(TableName.SecretApprovalRequestSecretV2).onDelete("CASCADE");
|
||||||
|
t.uuid("tagId").notNullable();
|
||||||
|
t.foreign("tagId").references("id").inTable(TableName.SecretTag).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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").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").index().notNullable();
|
||||||
|
t.foreign("secretVersionId").references("id").inTable(TableName.SecretVersionV2).onDelete("CASCADE");
|
||||||
|
t.uuid("snapshotId").index().notNullable();
|
||||||
|
t.foreign("snapshotId").references("id").inTable(TableName.Snapshot).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.IntegrationAuth)) {
|
||||||
|
const hasEncryptedAccess = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccess");
|
||||||
|
const hasEncryptedAccessId = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccessId");
|
||||||
|
const hasEncryptedRefresh = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedRefresh");
|
||||||
|
const hasEncryptedAwsIamAssumRole = await knex.schema.hasColumn(
|
||||||
|
TableName.IntegrationAuth,
|
||||||
|
"encryptedAwsAssumeIamRoleArn"
|
||||||
|
);
|
||||||
|
await knex.schema.alterTable(TableName.IntegrationAuth, (t) => {
|
||||||
|
if (!hasEncryptedAccess) t.binary("encryptedAccess");
|
||||||
|
if (!hasEncryptedAccessId) t.binary("encryptedAccessId");
|
||||||
|
if (!hasEncryptedRefresh) t.binary("encryptedRefresh");
|
||||||
|
if (!hasEncryptedAwsIamAssumRole) t.binary("encryptedAwsAssumeIamRoleArn");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretRotationOutputV2))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretRotationOutputV2, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("key").notNullable();
|
||||||
|
t.uuid("secretId").notNullable();
|
||||||
|
t.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
|
||||||
|
t.uuid("rotationId").notNullable();
|
||||||
|
t.foreign("rotationId").references("id").inTable(TableName.SecretRotation).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SnapshotSecretV2);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretApprovalRequestSecretTagV2);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretApprovalRequestSecretV2);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretV2JnTag);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretReferenceV2);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretRotationOutputV2);
|
||||||
|
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SecretVersionV2);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretVersionV2Tag);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretVersionV2);
|
||||||
|
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SecretV2);
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretV2);
|
||||||
|
|
||||||
|
if (await knex.schema.hasTable(TableName.IntegrationAuth)) {
|
||||||
|
const hasEncryptedAccess = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccess");
|
||||||
|
const hasEncryptedAccessId = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedAccessId");
|
||||||
|
const hasEncryptedRefresh = await knex.schema.hasColumn(TableName.IntegrationAuth, "encryptedRefresh");
|
||||||
|
const hasEncryptedAwsIamAssumRole = await knex.schema.hasColumn(
|
||||||
|
TableName.IntegrationAuth,
|
||||||
|
"encryptedAwsAssumeIamRoleArn"
|
||||||
|
);
|
||||||
|
await knex.schema.alterTable(TableName.IntegrationAuth, (t) => {
|
||||||
|
if (hasEncryptedAccess) t.dropColumn("encryptedAccess");
|
||||||
|
if (hasEncryptedAccessId) t.dropColumn("encryptedAccessId");
|
||||||
|
if (hasEncryptedRefresh) t.dropColumn("encryptedRefresh");
|
||||||
|
if (hasEncryptedAwsIamAssumRole) t.dropColumn("encryptedAwsAssumeIamRoleArn");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
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({
|
export const AccessApprovalPoliciesApproversSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
approverId: z.string().uuid(),
|
|
||||||
policyId: z.string().uuid(),
|
policyId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
approverUserId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
|
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const AccessApprovalPoliciesSchema = z.object({
|
export const AccessApprovalPoliciesSchema = z.object({
|
||||||
@ -17,7 +15,7 @@ export const AccessApprovalPoliciesSchema = z.object({
|
|||||||
envId: z.string().uuid(),
|
envId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
enforcementLevel: z.string().default("hard")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
export type TAccessApprovalPolicies = z.infer<typeof AccessApprovalPoliciesSchema>;
|
||||||
|
@ -9,11 +9,12 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const AccessApprovalRequestsReviewersSchema = z.object({
|
export const AccessApprovalRequestsReviewersSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
member: z.string().uuid(),
|
member: z.string().uuid().nullable().optional(),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
requestId: z.string().uuid(),
|
requestId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
reviewerUserId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalRequestsReviewers = z.infer<typeof AccessApprovalRequestsReviewersSchema>;
|
export type TAccessApprovalRequestsReviewers = z.infer<typeof AccessApprovalRequestsReviewersSchema>;
|
||||||
|
@ -11,12 +11,13 @@ export const AccessApprovalRequestsSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
policyId: z.string().uuid(),
|
policyId: z.string().uuid(),
|
||||||
privilegeId: z.string().uuid().nullable().optional(),
|
privilegeId: z.string().uuid().nullable().optional(),
|
||||||
requestedBy: z.string().uuid(),
|
requestedBy: z.string().uuid().nullable().optional(),
|
||||||
isTemporary: z.boolean(),
|
isTemporary: z.boolean(),
|
||||||
temporaryRange: z.string().nullable().optional(),
|
temporaryRange: z.string().nullable().optional(),
|
||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
requestedByUserId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;
|
export type TAccessApprovalRequests = z.infer<typeof AccessApprovalRequestsSchema>;
|
||||||
|
@ -27,7 +27,8 @@ export const CertificateAuthoritiesSchema = z.object({
|
|||||||
maxPathLength: z.number().nullable().optional(),
|
maxPathLength: z.number().nullable().optional(),
|
||||||
keyAlgorithm: z.string(),
|
keyAlgorithm: z.string(),
|
||||||
notBefore: z.date().nullable().optional(),
|
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>;
|
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
||||||
|
@ -15,7 +15,9 @@ export const CertificateAuthorityCertsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
caId: z.string().uuid(),
|
caId: z.string().uuid(),
|
||||||
encryptedCertificate: zodBuffer,
|
encryptedCertificate: zodBuffer,
|
||||||
encryptedCertificateChain: zodBuffer
|
encryptedCertificateChain: zodBuffer,
|
||||||
|
version: z.number(),
|
||||||
|
caSecretId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateAuthorityCerts = z.infer<typeof CertificateAuthorityCertsSchema>;
|
export type TCertificateAuthorityCerts = z.infer<typeof CertificateAuthorityCertsSchema>;
|
||||||
|
@ -14,7 +14,8 @@ export const CertificateAuthorityCrlSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
caId: z.string().uuid(),
|
caId: z.string().uuid(),
|
||||||
encryptedCrl: zodBuffer
|
encryptedCrl: zodBuffer,
|
||||||
|
caSecretId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateAuthorityCrl = z.infer<typeof CertificateAuthorityCrlSchema>;
|
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(),
|
notAfter: z.date(),
|
||||||
revokedAt: z.date().nullable().optional(),
|
revokedAt: z.date().nullable().optional(),
|
||||||
revocationReason: z.number().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>;
|
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||||
|
@ -14,6 +14,7 @@ export * from "./certificate-authority-crl";
|
|||||||
export * from "./certificate-authority-secret";
|
export * from "./certificate-authority-secret";
|
||||||
export * from "./certificate-bodies";
|
export * from "./certificate-bodies";
|
||||||
export * from "./certificate-secrets";
|
export * from "./certificate-secrets";
|
||||||
|
export * from "./certificate-templates";
|
||||||
export * from "./certificates";
|
export * from "./certificates";
|
||||||
export * from "./dynamic-secret-leases";
|
export * from "./dynamic-secret-leases";
|
||||||
export * from "./dynamic-secrets";
|
export * from "./dynamic-secrets";
|
||||||
@ -52,6 +53,9 @@ export * from "./org-bots";
|
|||||||
export * from "./org-memberships";
|
export * from "./org-memberships";
|
||||||
export * from "./org-roles";
|
export * from "./org-roles";
|
||||||
export * from "./organizations";
|
export * from "./organizations";
|
||||||
|
export * from "./pki-alerts";
|
||||||
|
export * from "./pki-collection-items";
|
||||||
|
export * from "./pki-collections";
|
||||||
export * from "./project-bots";
|
export * from "./project-bots";
|
||||||
export * from "./project-environments";
|
export * from "./project-environments";
|
||||||
export * from "./project-keys";
|
export * from "./project-keys";
|
||||||
@ -66,26 +70,35 @@ export * from "./scim-tokens";
|
|||||||
export * from "./secret-approval-policies";
|
export * from "./secret-approval-policies";
|
||||||
export * from "./secret-approval-policies-approvers";
|
export * from "./secret-approval-policies-approvers";
|
||||||
export * from "./secret-approval-request-secret-tags";
|
export * from "./secret-approval-request-secret-tags";
|
||||||
|
export * from "./secret-approval-request-secret-tags-v2";
|
||||||
export * from "./secret-approval-requests";
|
export * from "./secret-approval-requests";
|
||||||
export * from "./secret-approval-requests-reviewers";
|
export * from "./secret-approval-requests-reviewers";
|
||||||
export * from "./secret-approval-requests-secrets";
|
export * from "./secret-approval-requests-secrets";
|
||||||
|
export * from "./secret-approval-requests-secrets-v2";
|
||||||
export * from "./secret-blind-indexes";
|
export * from "./secret-blind-indexes";
|
||||||
export * from "./secret-folder-versions";
|
export * from "./secret-folder-versions";
|
||||||
export * from "./secret-folders";
|
export * from "./secret-folders";
|
||||||
export * from "./secret-imports";
|
export * from "./secret-imports";
|
||||||
export * from "./secret-references";
|
export * from "./secret-references";
|
||||||
|
export * from "./secret-references-v2";
|
||||||
|
export * from "./secret-rotation-output-v2";
|
||||||
export * from "./secret-rotation-outputs";
|
export * from "./secret-rotation-outputs";
|
||||||
export * from "./secret-rotations";
|
export * from "./secret-rotations";
|
||||||
export * from "./secret-scanning-git-risks";
|
export * from "./secret-scanning-git-risks";
|
||||||
export * from "./secret-sharing";
|
export * from "./secret-sharing";
|
||||||
export * from "./secret-snapshot-folders";
|
export * from "./secret-snapshot-folders";
|
||||||
export * from "./secret-snapshot-secrets";
|
export * from "./secret-snapshot-secrets";
|
||||||
|
export * from "./secret-snapshot-secrets-v2";
|
||||||
export * from "./secret-snapshots";
|
export * from "./secret-snapshots";
|
||||||
export * from "./secret-tag-junction";
|
export * from "./secret-tag-junction";
|
||||||
export * from "./secret-tags";
|
export * from "./secret-tags";
|
||||||
|
export * from "./secret-v2-tag-junction";
|
||||||
export * from "./secret-version-tag-junction";
|
export * from "./secret-version-tag-junction";
|
||||||
|
export * from "./secret-version-v2-tag-junction";
|
||||||
export * from "./secret-versions";
|
export * from "./secret-versions";
|
||||||
|
export * from "./secret-versions-v2";
|
||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
|
export * from "./secrets-v2";
|
||||||
export * from "./service-tokens";
|
export * from "./service-tokens";
|
||||||
export * from "./super-admin";
|
export * from "./super-admin";
|
||||||
export * from "./trusted-ips";
|
export * from "./trusted-ips";
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const IntegrationAuthsSchema = z.object({
|
export const IntegrationAuthsSchema = z.object({
|
||||||
@ -32,7 +34,11 @@ export const IntegrationAuthsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
awsAssumeIamRoleArnCipherText: z.string().nullable().optional(),
|
awsAssumeIamRoleArnCipherText: z.string().nullable().optional(),
|
||||||
awsAssumeIamRoleArnIV: z.string().nullable().optional(),
|
awsAssumeIamRoleArnIV: z.string().nullable().optional(),
|
||||||
awsAssumeIamRoleArnTag: z.string().nullable().optional()
|
awsAssumeIamRoleArnTag: z.string().nullable().optional(),
|
||||||
|
encryptedAccess: zodBuffer.nullable().optional(),
|
||||||
|
encryptedAccessId: zodBuffer.nullable().optional(),
|
||||||
|
encryptedRefresh: zodBuffer.nullable().optional(),
|
||||||
|
encryptedAwsAssumeIamRoleArn: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIntegrationAuths = z.infer<typeof IntegrationAuthsSchema>;
|
export type TIntegrationAuths = z.infer<typeof IntegrationAuthsSchema>;
|
||||||
|
@ -13,9 +13,9 @@ export const KmsKeysSchema = z.object({
|
|||||||
isDisabled: z.boolean().default(false).nullable().optional(),
|
isDisabled: z.boolean().default(false).nullable().optional(),
|
||||||
isReserved: z.boolean().default(true).nullable().optional(),
|
isReserved: z.boolean().default(true).nullable().optional(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
|
slug: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date()
|
||||||
slug: z.string()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||||
|
@ -9,6 +9,10 @@ export enum TableName {
|
|||||||
Certificate = "certificates",
|
Certificate = "certificates",
|
||||||
CertificateBody = "certificate_bodies",
|
CertificateBody = "certificate_bodies",
|
||||||
CertificateSecret = "certificate_secrets",
|
CertificateSecret = "certificate_secrets",
|
||||||
|
CertificateTemplate = "certificate_templates",
|
||||||
|
PkiAlert = "pki_alerts",
|
||||||
|
PkiCollection = "pki_collections",
|
||||||
|
PkiCollectionItem = "pki_collection_items",
|
||||||
Groups = "groups",
|
Groups = "groups",
|
||||||
GroupProjectMembership = "group_project_memberships",
|
GroupProjectMembership = "group_project_memberships",
|
||||||
GroupProjectMembershipRole = "group_project_membership_roles",
|
GroupProjectMembershipRole = "group_project_membership_roles",
|
||||||
@ -90,9 +94,18 @@ export enum TableName {
|
|||||||
TrustedIps = "trusted_ips",
|
TrustedIps = "trusted_ips",
|
||||||
DynamicSecret = "dynamic_secrets",
|
DynamicSecret = "dynamic_secrets",
|
||||||
DynamicSecretLease = "dynamic_secret_leases",
|
DynamicSecretLease = "dynamic_secret_leases",
|
||||||
|
SecretV2 = "secrets_v2",
|
||||||
|
SecretReferenceV2 = "secret_references_v2",
|
||||||
|
SecretVersionV2 = "secret_versions_v2",
|
||||||
|
SecretApprovalRequestSecretV2 = "secret_approval_requests_secrets_v2",
|
||||||
|
SecretApprovalRequestSecretTagV2 = "secret_approval_request_secret_tags_v2",
|
||||||
|
SnapshotSecretV2 = "secret_snapshot_secrets_v2",
|
||||||
// junction tables with tags
|
// junction tables with tags
|
||||||
|
SecretV2JnTag = "secret_v2_tag_junction",
|
||||||
JnSecretTag = "secret_tag_junction",
|
JnSecretTag = "secret_tag_junction",
|
||||||
SecretVersionTag = "secret_version_tag_junction",
|
SecretVersionTag = "secret_version_tag_junction",
|
||||||
|
SecretVersionV2Tag = "secret_version_v2_tag_junction",
|
||||||
|
SecretRotationOutputV2 = "secret_rotation_output_v2",
|
||||||
// KMS Service
|
// KMS Service
|
||||||
KmsServerRootConfig = "kms_root_config",
|
KmsServerRootConfig = "kms_root_config",
|
||||||
KmsKey = "kms_keys",
|
KmsKey = "kms_keys",
|
||||||
@ -157,7 +170,8 @@ export enum SecretType {
|
|||||||
|
|
||||||
export enum ProjectVersion {
|
export enum ProjectVersion {
|
||||||
V1 = 1,
|
V1 = 1,
|
||||||
V2 = 2
|
V2 = 2,
|
||||||
|
V3 = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectUpgradeStatus {
|
export enum ProjectUpgradeStatus {
|
||||||
|
@ -18,7 +18,7 @@ export const OrgMembershipsSchema = z.object({
|
|||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
roleId: z.string().uuid().nullable().optional(),
|
roleId: z.string().uuid().nullable().optional(),
|
||||||
projectFavorites: z.string().array().nullable().optional(),
|
projectFavorites: z.string().array().nullable().optional(),
|
||||||
isActive: z.boolean()
|
isActive: z.boolean().default(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;
|
export type TOrgMemberships = z.infer<typeof OrgMembershipsSchema>;
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const OrganizationsSchema = z.object({
|
export const OrganizationsSchema = z.object({
|
||||||
@ -16,7 +18,8 @@ export const OrganizationsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
authEnforced: z.boolean().default(false).nullable().optional(),
|
authEnforced: z.boolean().default(false).nullable().optional(),
|
||||||
scimEnabled: z.boolean().default(false).nullable().optional(),
|
scimEnabled: z.boolean().default(false).nullable().optional(),
|
||||||
kmsDefaultKeyId: z.string().uuid().nullable().optional()
|
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
|
||||||
|
kmsEncryptedDataKey: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
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({
|
export const ProjectUserAdditionalPrivilegeSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
projectMembershipId: z.string().uuid(),
|
projectMembershipId: z.string().uuid().nullable().optional(),
|
||||||
isTemporary: z.boolean().default(false),
|
isTemporary: z.boolean().default(false),
|
||||||
temporaryMode: z.string().nullable().optional(),
|
temporaryMode: z.string().nullable().optional(),
|
||||||
temporaryRange: z.string().nullable().optional(),
|
temporaryRange: z.string().nullable().optional(),
|
||||||
@ -18,7 +18,9 @@ export const ProjectUserAdditionalPrivilegeSchema = z.object({
|
|||||||
temporaryAccessEndTime: z.date().nullable().optional(),
|
temporaryAccessEndTime: z.date().nullable().optional(),
|
||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
userId: z.string().uuid(),
|
||||||
|
projectId: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjectUserAdditionalPrivilege = z.infer<typeof ProjectUserAdditionalPrivilegeSchema>;
|
export type TProjectUserAdditionalPrivilege = z.infer<typeof ProjectUserAdditionalPrivilegeSchema>;
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const ProjectsSchema = z.object({
|
export const ProjectsSchema = z.object({
|
||||||
@ -20,7 +22,8 @@ export const ProjectsSchema = z.object({
|
|||||||
pitVersionLimit: z.number().default(10),
|
pitVersionLimit: z.number().default(10),
|
||||||
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
|
kmsCertificateKeyId: z.string().uuid().nullable().optional(),
|
||||||
auditLogsRetentionDays: z.number().nullable().optional(),
|
auditLogsRetentionDays: z.number().nullable().optional(),
|
||||||
kmsSecretManagerKeyId: z.string().uuid().nullable().optional()
|
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
||||||
|
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
@ -15,7 +15,6 @@ export const RateLimitSchema = z.object({
|
|||||||
authRateLimit: z.number().default(60),
|
authRateLimit: z.number().default(60),
|
||||||
inviteUserRateLimit: z.number().default(30),
|
inviteUserRateLimit: z.number().default(30),
|
||||||
mfaRateLimit: z.number().default(20),
|
mfaRateLimit: z.number().default(20),
|
||||||
creationLimit: z.number().default(30),
|
|
||||||
publicEndpointLimit: z.number().default(30),
|
publicEndpointLimit: z.number().default(30),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
// 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 SecretApprovalRequestSecretTagsV2Schema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
secretId: z.string().uuid(),
|
||||||
|
tagId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretApprovalRequestSecretTagsV2 = z.infer<typeof SecretApprovalRequestSecretTagsV2Schema>;
|
||||||
|
export type TSecretApprovalRequestSecretTagsV2Insert = Omit<
|
||||||
|
z.input<typeof SecretApprovalRequestSecretTagsV2Schema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TSecretApprovalRequestSecretTagsV2Update = Partial<
|
||||||
|
Omit<z.input<typeof SecretApprovalRequestSecretTagsV2Schema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -0,0 +1,37 @@
|
|||||||
|
// 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 { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SecretApprovalRequestsSecretsV2Schema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
version: z.number().default(1).nullable().optional(),
|
||||||
|
key: z.string(),
|
||||||
|
encryptedValue: zodBuffer.nullable().optional(),
|
||||||
|
encryptedComment: zodBuffer.nullable().optional(),
|
||||||
|
reminderNote: z.string().nullable().optional(),
|
||||||
|
reminderRepeatDays: z.number().nullable().optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
||||||
|
metadata: z.unknown().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
requestId: z.string().uuid(),
|
||||||
|
op: z.string(),
|
||||||
|
secretId: z.string().uuid().nullable().optional(),
|
||||||
|
secretVersion: z.string().uuid().nullable().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretApprovalRequestsSecretsV2 = z.infer<typeof SecretApprovalRequestsSecretsV2Schema>;
|
||||||
|
export type TSecretApprovalRequestsSecretsV2Insert = Omit<
|
||||||
|
z.input<typeof SecretApprovalRequestsSecretsV2Schema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TSecretApprovalRequestsSecretsV2Update = Partial<
|
||||||
|
Omit<z.input<typeof SecretApprovalRequestsSecretsV2Schema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -15,12 +15,12 @@ export const SecretApprovalRequestsSchema = z.object({
|
|||||||
conflicts: z.unknown().nullable().optional(),
|
conflicts: z.unknown().nullable().optional(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
folderId: z.string().uuid(),
|
folderId: z.string().uuid(),
|
||||||
bypassReason: z.string().nullable().optional(),
|
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
isReplicated: z.boolean().nullable().optional(),
|
isReplicated: z.boolean().nullable().optional(),
|
||||||
committerUserId: z.string().uuid(),
|
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>;
|
export type TSecretApprovalRequests = z.infer<typeof SecretApprovalRequestsSchema>;
|
||||||
|
20
backend/src/db/schemas/secret-references-v2.ts
Normal file
20
backend/src/db/schemas/secret-references-v2.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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 SecretReferencesV2Schema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
environment: z.string(),
|
||||||
|
secretPath: z.string(),
|
||||||
|
secretKey: z.string(),
|
||||||
|
secretId: z.string().uuid()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretReferencesV2 = z.infer<typeof SecretReferencesV2Schema>;
|
||||||
|
export type TSecretReferencesV2Insert = Omit<z.input<typeof SecretReferencesV2Schema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretReferencesV2Update = Partial<Omit<z.input<typeof SecretReferencesV2Schema>, TImmutableDBKeys>>;
|
21
backend/src/db/schemas/secret-rotation-output-v2.ts
Normal file
21
backend/src/db/schemas/secret-rotation-output-v2.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 SecretRotationOutputV2Schema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
key: z.string(),
|
||||||
|
secretId: z.string().uuid(),
|
||||||
|
rotationId: z.string().uuid()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretRotationOutputV2 = z.infer<typeof SecretRotationOutputV2Schema>;
|
||||||
|
export type TSecretRotationOutputV2Insert = Omit<z.input<typeof SecretRotationOutputV2Schema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretRotationOutputV2Update = Partial<
|
||||||
|
Omit<z.input<typeof SecretRotationOutputV2Schema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretSharingAccessType } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const SecretSharingSchema = z.object({
|
export const SecretSharingSchema = z.object({
|
||||||
@ -18,10 +16,12 @@ export const SecretSharingSchema = z.object({
|
|||||||
expiresAt: z.date(),
|
expiresAt: z.date(),
|
||||||
userId: z.string().uuid().nullable().optional(),
|
userId: z.string().uuid().nullable().optional(),
|
||||||
orgId: z.string().uuid().nullable().optional(),
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
accessType: z.nativeEnum(SecretSharingAccessType).default(SecretSharingAccessType.Organization),
|
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: 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>;
|
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
||||||
|
23
backend/src/db/schemas/secret-snapshot-secrets-v2.ts
Normal file
23
backend/src/db/schemas/secret-snapshot-secrets-v2.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 SecretSnapshotSecretsV2Schema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
envId: z.string().uuid(),
|
||||||
|
secretVersionId: z.string().uuid(),
|
||||||
|
snapshotId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretSnapshotSecretsV2 = z.infer<typeof SecretSnapshotSecretsV2Schema>;
|
||||||
|
export type TSecretSnapshotSecretsV2Insert = Omit<z.input<typeof SecretSnapshotSecretsV2Schema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretSnapshotSecretsV2Update = Partial<
|
||||||
|
Omit<z.input<typeof SecretSnapshotSecretsV2Schema>, TImmutableDBKeys>
|
||||||
|
>;
|
@ -9,7 +9,6 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const SecretTagsSchema = z.object({
|
export const SecretTagsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
name: z.string(),
|
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
color: z.string().nullable().optional(),
|
color: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
18
backend/src/db/schemas/secret-v2-tag-junction.ts
Normal file
18
backend/src/db/schemas/secret-v2-tag-junction.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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 SecretV2TagJunctionSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
secrets_v2Id: z.string().uuid(),
|
||||||
|
secret_tagsId: z.string().uuid()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretV2TagJunction = z.infer<typeof SecretV2TagJunctionSchema>;
|
||||||
|
export type TSecretV2TagJunctionInsert = Omit<z.input<typeof SecretV2TagJunctionSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretV2TagJunctionUpdate = Partial<Omit<z.input<typeof SecretV2TagJunctionSchema>, TImmutableDBKeys>>;
|
23
backend/src/db/schemas/secret-version-v2-tag-junction.ts
Normal file
23
backend/src/db/schemas/secret-version-v2-tag-junction.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 SecretVersionV2TagJunctionSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
secret_versions_v2Id: z.string().uuid(),
|
||||||
|
secret_tagsId: z.string().uuid()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretVersionV2TagJunction = z.infer<typeof SecretVersionV2TagJunctionSchema>;
|
||||||
|
export type TSecretVersionV2TagJunctionInsert = Omit<
|
||||||
|
z.input<typeof SecretVersionV2TagJunctionSchema>,
|
||||||
|
TImmutableDBKeys
|
||||||
|
>;
|
||||||
|
export type TSecretVersionV2TagJunctionUpdate = Partial<
|
||||||
|
Omit<z.input<typeof SecretVersionV2TagJunctionSchema>, TImmutableDBKeys>
|
||||||
|
>;
|
33
backend/src/db/schemas/secret-versions-v2.ts
Normal file
33
backend/src/db/schemas/secret-versions-v2.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// 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 { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SecretVersionsV2Schema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
version: z.number().default(1),
|
||||||
|
type: z.string().default("shared"),
|
||||||
|
key: z.string(),
|
||||||
|
encryptedValue: zodBuffer.nullable().optional(),
|
||||||
|
encryptedComment: zodBuffer.nullable().optional(),
|
||||||
|
reminderNote: z.string().nullable().optional(),
|
||||||
|
reminderRepeatDays: z.number().nullable().optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
||||||
|
metadata: z.unknown().nullable().optional(),
|
||||||
|
envId: z.string().uuid().nullable().optional(),
|
||||||
|
secretId: z.string().uuid(),
|
||||||
|
folderId: z.string().uuid(),
|
||||||
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretVersionsV2 = z.infer<typeof SecretVersionsV2Schema>;
|
||||||
|
export type TSecretVersionsV2Insert = Omit<z.input<typeof SecretVersionsV2Schema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretVersionsV2Update = Partial<Omit<z.input<typeof SecretVersionsV2Schema>, TImmutableDBKeys>>;
|
31
backend/src/db/schemas/secrets-v2.ts
Normal file
31
backend/src/db/schemas/secrets-v2.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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 { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SecretsV2Schema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
version: z.number().default(1),
|
||||||
|
type: z.string().default("shared"),
|
||||||
|
key: z.string(),
|
||||||
|
encryptedValue: zodBuffer.nullable().optional(),
|
||||||
|
encryptedComment: zodBuffer.nullable().optional(),
|
||||||
|
reminderNote: z.string().nullable().optional(),
|
||||||
|
reminderRepeatDays: z.number().nullable().optional(),
|
||||||
|
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
||||||
|
metadata: z.unknown().nullable().optional(),
|
||||||
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
folderId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretsV2 = z.infer<typeof SecretsV2Schema>;
|
||||||
|
export type TSecretsV2Insert = Omit<z.input<typeof SecretsV2Schema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretsV2Update = Partial<Omit<z.input<typeof SecretsV2Schema>, TImmutableDBKeys>>;
|
@ -33,6 +33,11 @@ export const seedData1 = {
|
|||||||
name: "first project",
|
name: "first project",
|
||||||
slug: "first-project"
|
slug: "first-project"
|
||||||
},
|
},
|
||||||
|
projectV3: {
|
||||||
|
id: "77fa7aed-9288-401e-a4c9-3a9430be62a4",
|
||||||
|
name: "first project v2",
|
||||||
|
slug: "first-project-v2"
|
||||||
|
},
|
||||||
environment: {
|
environment: {
|
||||||
name: "Development",
|
name: "Development",
|
||||||
slug: "dev"
|
slug: "dev"
|
||||||
|
50
backend/src/db/seeds/4-project-v3.ts
Normal file
50
backend/src/db/seeds/4-project-v3.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { ProjectMembershipRole, ProjectVersion, TableName } from "../schemas";
|
||||||
|
import { seedData1 } from "../seed-data";
|
||||||
|
|
||||||
|
export const DEFAULT_PROJECT_ENVS = [
|
||||||
|
{ name: "Development", slug: "dev" },
|
||||||
|
{ name: "Staging", slug: "staging" },
|
||||||
|
{ name: "Production", slug: "prod" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function seed(knex: Knex): Promise<void> {
|
||||||
|
const [projectV2] = await knex(TableName.Project)
|
||||||
|
.insert({
|
||||||
|
name: seedData1.projectV3.name,
|
||||||
|
orgId: seedData1.organization.id,
|
||||||
|
slug: seedData1.projectV3.slug,
|
||||||
|
version: ProjectVersion.V3,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
|
id: seedData1.projectV3.id
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
const projectMembershipV3 = await knex(TableName.ProjectMembership)
|
||||||
|
.insert({
|
||||||
|
projectId: projectV2.id,
|
||||||
|
userId: seedData1.id
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
await knex(TableName.ProjectUserMembershipRole).insert({
|
||||||
|
role: ProjectMembershipRole.Admin,
|
||||||
|
projectMembershipId: projectMembershipV3[0].id
|
||||||
|
});
|
||||||
|
|
||||||
|
// create default environments and default folders
|
||||||
|
const projectV3Envs = await knex(TableName.Environment)
|
||||||
|
.insert(
|
||||||
|
DEFAULT_PROJECT_ENVS.map(({ name, slug }, index) => ({
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
projectId: seedData1.projectV3.id,
|
||||||
|
position: index + 1
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
.returning("*");
|
||||||
|
await knex(TableName.SecretFolder).insert(
|
||||||
|
projectV3Envs.map(({ id }) => ({ name: "root", envId: id, parentId: null }))
|
||||||
|
);
|
||||||
|
}
|
@ -86,4 +86,15 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
role: ProjectMembershipRole.Admin,
|
role: ProjectMembershipRole.Admin,
|
||||||
projectMembershipId: identityProjectMembership[0].id
|
projectMembershipId: identityProjectMembership[0].id
|
||||||
});
|
});
|
||||||
|
const identityProjectMembershipV3 = await knex(TableName.IdentityProjectMembership)
|
||||||
|
.insert({
|
||||||
|
identityId: seedData1.machineIdentity.id,
|
||||||
|
projectId: seedData1.projectV3.id
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
await knex(TableName.IdentityProjectMembershipRole).insert({
|
||||||
|
role: ProjectMembershipRole.Admin,
|
||||||
|
projectMembershipId: identityProjectMembershipV3[0].id
|
||||||
|
});
|
||||||
}
|
}
|
@ -17,11 +17,11 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
approvers: z.string().array().min(1),
|
approverUserIds: z.string().array().min(1),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||||
})
|
})
|
||||||
.refine((data) => data.approvals <= data.approvers.length, {
|
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||||
path: ["approvals"],
|
path: ["approvals"],
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
message: "The number of approvals should be lower than the number of approvers."
|
||||||
}),
|
}),
|
||||||
@ -56,7 +56,16 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
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()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -69,6 +78,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectSlug: req.query.projectSlug
|
projectSlug: req.query.projectSlug
|
||||||
});
|
});
|
||||||
|
|
||||||
return { approvals };
|
return { approvals };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -117,11 +127,11 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.trim()
|
.trim()
|
||||||
.optional()
|
.optional()
|
||||||
.transform((val) => (val === "" ? "/" : val)),
|
.transform((val) => (val === "" ? "/" : val)),
|
||||||
approvers: z.string().array().min(1),
|
approverUserIds: z.string().array().min(1),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard)
|
||||||
})
|
})
|
||||||
.refine((data) => data.approvals <= data.approvers.length, {
|
.refine((data) => data.approvals <= data.approverUserIds.length, {
|
||||||
path: ["approvals"],
|
path: ["approvals"],
|
||||||
message: "The number of approvals should be lower than the number of approvers."
|
message: "The number of approvals should be lower than the number of approvers."
|
||||||
}),
|
}),
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
import { z } from "zod";
|
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 { ApprovalStatus } from "@app/ee/services/access-approval-request/access-approval-request-types";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const approvalRequestUser = z.object({ userId: z.string() }).merge(
|
||||||
|
UsersSchema.pick({
|
||||||
|
email: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
username: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
export const registerAccessApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
export const registerAccessApprovalRequestRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
@ -104,10 +113,11 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
}),
|
}),
|
||||||
reviewers: z
|
reviewers: z
|
||||||
.object({
|
.object({
|
||||||
member: z.string(),
|
userId: z.string(),
|
||||||
status: z.string()
|
status: z.string()
|
||||||
})
|
})
|
||||||
.array()
|
.array(),
|
||||||
|
requestedByUser: approvalRequestUser
|
||||||
}).array()
|
}).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,86 +1,31 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { CA_CRLS } from "@app/lib/api-docs";
|
||||||
import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
|
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
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) => {
|
export const registerCaCrlRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:caId/crl",
|
url: "/:crlId",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
schema: {
|
schema: {
|
||||||
description: "Get CRL of the CA",
|
description: "Get CRL in DER format",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CRL.caId)
|
crlId: z.string().trim().describe(CA_CRLS.GET.crlId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.instanceof(Buffer)
|
||||||
crl: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CRL.crl)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req, res) => {
|
||||||
const { crl, ca } = await server.services.certificateAuthorityCrl.getCaCrl({
|
const { crl } = await server.services.certificateAuthorityCrl.getCrlById(req.params.crlId);
|
||||||
caId: req.params.caId,
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
res.header("Content-Type", "application/pkix-crl");
|
||||||
...req.auditLogInfo,
|
|
||||||
projectId: ca.projectId,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_CA_CRL,
|
|
||||||
metadata: {
|
|
||||||
caId: ca.id,
|
|
||||||
dn: ca.dn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return Buffer.from(crl);
|
||||||
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("/")
|
.default("/")
|
||||||
.transform(removeTrailingSlash)
|
.transform(removeTrailingSlash)
|
||||||
.describe(DYNAMIC_SECRET_LEASES.RENEW.path),
|
.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: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
import { ExternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import {
|
||||||
ExternalKmsAwsSchema,
|
ExternalKmsAwsSchema,
|
||||||
ExternalKmsInputSchema,
|
ExternalKmsInputSchema,
|
||||||
@ -19,6 +20,23 @@ const sanitizedExternalSchema = KmsKeysSchema.extend({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sanitizedExternalSchemaForGetAll = KmsKeysSchema.pick({
|
||||||
|
id: true,
|
||||||
|
description: true,
|
||||||
|
isDisabled: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
slug: true
|
||||||
|
})
|
||||||
|
.extend({
|
||||||
|
externalKms: ExternalKmsSchema.pick({
|
||||||
|
provider: true,
|
||||||
|
status: true,
|
||||||
|
statusDetails: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.array();
|
||||||
|
|
||||||
const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({
|
const sanitizedExternalSchemaForGetById = KmsKeysSchema.extend({
|
||||||
external: ExternalKmsSchema.pick({
|
external: ExternalKmsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
@ -39,8 +57,8 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().min(1).trim().toLowerCase().optional(),
|
slug: z.string().min(1).trim().toLowerCase(),
|
||||||
description: z.string().min(1).trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsInputSchema
|
provider: ExternalKmsInputSchema
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -60,6 +78,21 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
provider: req.body.provider,
|
provider: req.body.provider,
|
||||||
description: req.body.description
|
description: req.body.description
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
provider: req.body.provider.type,
|
||||||
|
slug: req.body.slug,
|
||||||
|
description: req.body.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -76,7 +109,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().min(1).trim().toLowerCase().optional(),
|
slug: z.string().min(1).trim().toLowerCase().optional(),
|
||||||
description: z.string().min(1).trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsInputUpdateSchema
|
provider: ExternalKmsInputUpdateSchema
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@ -97,6 +130,21 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
description: req.body.description,
|
description: req.body.description,
|
||||||
id: req.params.id
|
id: req.params.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
provider: req.body.provider.type,
|
||||||
|
slug: req.body.slug,
|
||||||
|
description: req.body.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -126,6 +174,19 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.id
|
id: req.params.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
slug: externalKms.slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -155,10 +216,48 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.id
|
id: req.params.id
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_KMS,
|
||||||
|
metadata: {
|
||||||
|
kmsId: externalKms.id,
|
||||||
|
slug: externalKms.slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
externalKmsList: sanitizedExternalSchemaForGetAll
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const externalKmsList = await server.services.externalKms.list({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
return { externalKmsList };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/slug/:slug",
|
url: "/slug/:slug",
|
||||||
|
@ -4,6 +4,7 @@ import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
|||||||
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
||||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||||
|
import { registerExternalKmsRouter } from "./external-kms-router";
|
||||||
import { registerGroupRouter } from "./group-router";
|
import { registerGroupRouter } from "./group-router";
|
||||||
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||||
import { registerLdapRouter } from "./ldap-router";
|
import { registerLdapRouter } from "./ldap-router";
|
||||||
@ -60,7 +61,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (pkiRouter) => {
|
async (pkiRouter) => {
|
||||||
await pkiRouter.register(registerCaCrlRouter, { prefix: "/ca" });
|
await pkiRouter.register(registerCaCrlRouter, { prefix: "/crl" });
|
||||||
},
|
},
|
||||||
{ prefix: "/pki" }
|
{ prefix: "/pki" }
|
||||||
);
|
);
|
||||||
@ -87,4 +88,8 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
{ prefix: "/additional-privilege" }
|
{ prefix: "/additional-privilege" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await server.register(registerExternalKmsRouter, {
|
||||||
|
prefix: "/external-kms"
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@ -107,7 +107,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -101,7 +101,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
message: "Slug must be a valid"
|
message: "Slug must be a valid"
|
||||||
}),
|
}),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
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: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@ -120,7 +120,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
roleId: req.params.roleId,
|
roleId: req.params.roleId,
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { role };
|
return { role };
|
||||||
|
@ -4,9 +4,10 @@ import { AuditLogsSchema, SecretSnapshotsSchema } from "@app/db/schemas";
|
|||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
|
import { AUDIT_LOGS, PROJECTS } from "@app/lib/api-docs";
|
||||||
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
|
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { KmsType } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -171,4 +172,212 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async () => ({ actors: [] })
|
handler: async () => ({ actors: [] })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:workspaceId/kms",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretManagerKmsKey: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
isExternal: z.boolean()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const kmsKey = await server.services.project.getProjectKmsKeys({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId
|
||||||
|
});
|
||||||
|
|
||||||
|
return kmsKey;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:workspaceId/kms",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
kms: z.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(KmsType.Internal) }),
|
||||||
|
z.object({ type: z.literal(KmsType.External), kmsId: z.string() })
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretManagerKmsKey: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
isExternal: z.boolean()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { secretManagerKmsKey } = await server.services.project.updateProjectKmsKey({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_PROJECT_KMS,
|
||||||
|
metadata: {
|
||||||
|
secretManagerKmsKey: {
|
||||||
|
id: secretManagerKmsKey.id,
|
||||||
|
slug: secretManagerKmsKey.slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
secretManagerKmsKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:workspaceId/kms/backup",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretManager: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const backup = await server.services.project.getProjectKmsBackup({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_PROJECT_KMS_BACKUP,
|
||||||
|
metadata: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return backup;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:workspaceId/kms/backup",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
backup: z.string().min(1)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
secretManagerKmsKey: z.object({
|
||||||
|
id: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
isExternal: z.boolean()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const backup = await server.services.project.loadProjectKmsBackup({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
backup: req.body.backup
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
event: {
|
||||||
|
type: EventType.LOAD_PROJECT_KMS_BACKUP,
|
||||||
|
metadata: {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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(),
|
authRateLimit: z.number(),
|
||||||
inviteUserRateLimit: z.number(),
|
inviteUserRateLimit: z.number(),
|
||||||
mfaRateLimit: z.number(),
|
mfaRateLimit: z.number(),
|
||||||
creationLimit: z.number(),
|
|
||||||
publicEndpointLimit: z.number()
|
publicEndpointLimit: z.number()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@ -3,16 +3,14 @@ import { z } from "zod";
|
|||||||
import {
|
import {
|
||||||
SecretApprovalRequestsReviewersSchema,
|
SecretApprovalRequestsReviewersSchema,
|
||||||
SecretApprovalRequestsSchema,
|
SecretApprovalRequestsSchema,
|
||||||
SecretApprovalRequestsSecretsSchema,
|
|
||||||
SecretsSchema,
|
|
||||||
SecretTagsSchema,
|
SecretTagsSchema,
|
||||||
SecretVersionsSchema,
|
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
const approvalRequestUser = z.object({ userId: z.string() }).merge(
|
const approvalRequestUser = z.object({ userId: z.string() }).merge(
|
||||||
@ -261,46 +259,32 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
committerUser: approvalRequestUser,
|
committerUser: approvalRequestUser,
|
||||||
reviewers: approvalRequestUser.extend({ status: z.string() }).array(),
|
reviewers: approvalRequestUser.extend({ status: z.string() }).array(),
|
||||||
secretPath: z.string(),
|
secretPath: z.string(),
|
||||||
commits: SecretApprovalRequestsSecretsSchema.omit({ secretBlindIndex: true })
|
commits: secretRawSchema
|
||||||
.merge(
|
.omit({ _id: true, environment: true, workspace: true, type: true, version: true })
|
||||||
z.object({
|
.extend({
|
||||||
tags: tagSchema,
|
op: z.string(),
|
||||||
secret: SecretsSchema.pick({
|
tags: tagSchema,
|
||||||
id: true,
|
secret: z
|
||||||
version: true,
|
.object({
|
||||||
secretKeyIV: true,
|
id: z.string(),
|
||||||
secretKeyTag: true,
|
version: z.number(),
|
||||||
secretKeyCiphertext: true,
|
secretKey: z.string(),
|
||||||
secretValueIV: true,
|
secretValue: z.string().optional(),
|
||||||
secretValueTag: true,
|
secretComment: z.string().optional()
|
||||||
secretValueCiphertext: true,
|
|
||||||
secretCommentIV: true,
|
|
||||||
secretCommentTag: true,
|
|
||||||
secretCommentCiphertext: true
|
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
secretVersion: SecretVersionsSchema.pick({
|
secretVersion: z
|
||||||
id: true,
|
.object({
|
||||||
version: true,
|
id: z.string(),
|
||||||
secretKeyIV: true,
|
version: z.number(),
|
||||||
secretKeyTag: true,
|
secretKey: z.string(),
|
||||||
secretKeyCiphertext: true,
|
secretValue: z.string().optional(),
|
||||||
secretValueIV: true,
|
secretComment: z.string().optional(),
|
||||||
secretValueTag: true,
|
tags: tagSchema
|
||||||
secretValueCiphertext: true,
|
|
||||||
secretCommentIV: true,
|
|
||||||
secretCommentTag: true,
|
|
||||||
secretCommentCiphertext: true
|
|
||||||
})
|
})
|
||||||
.merge(
|
.optional()
|
||||||
z.object({
|
})
|
||||||
tags: tagSchema
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.array()
|
.array()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretRotationOutputsSchema, SecretRotationsSchema, SecretsSchema } from "@app/db/schemas";
|
import { SecretRotationOutputsSchema, SecretRotationsSchema } from "@app/db/schemas";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@ -112,18 +112,10 @@ export const registerSecretRotationRouter = async (server: FastifyZodProvider) =
|
|||||||
outputs: z
|
outputs: z
|
||||||
.object({
|
.object({
|
||||||
key: z.string(),
|
key: z.string(),
|
||||||
secret: SecretsSchema.pick({
|
secret: z.object({
|
||||||
id: true,
|
secretKey: z.string(),
|
||||||
version: true,
|
id: z.string(),
|
||||||
secretKeyIV: true,
|
version: z.number()
|
||||||
secretKeyTag: true,
|
|
||||||
secretKeyCiphertext: true,
|
|
||||||
secretValueIV: true,
|
|
||||||
secretValueTag: true,
|
|
||||||
secretValueCiphertext: true,
|
|
||||||
secretCommentIV: true,
|
|
||||||
secretCommentTag: true,
|
|
||||||
secretCommentCiphertext: true
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretVersionsSchema } from "@app/db/schemas";
|
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSecretVersionRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretVersionRouter = async (server: FastifyZodProvider) => {
|
||||||
@ -22,7 +22,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secretVersions: SecretVersionsSchema.omit({ secretBlindIndex: true }).array()
|
secretVersions: secretRawSchema.array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretSnapshotsSchema, SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas";
|
import { SecretSnapshotsSchema, SecretTagsSchema } from "@app/db/schemas";
|
||||||
import { PROJECTS } from "@app/lib/api-docs";
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||||
@ -27,17 +28,17 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
name: z.string()
|
name: z.string()
|
||||||
}),
|
}),
|
||||||
secretVersions: SecretVersionsSchema.omit({ secretBlindIndex: true })
|
secretVersions: secretRawSchema
|
||||||
.merge(
|
.omit({ _id: true, environment: true, workspace: true, type: true })
|
||||||
z.object({
|
.extend({
|
||||||
tags: SecretTagsSchema.pick({
|
secretId: z.string(),
|
||||||
id: true,
|
tags: SecretTagsSchema.pick({
|
||||||
slug: true,
|
id: true,
|
||||||
name: true,
|
slug: true,
|
||||||
color: true
|
name: true,
|
||||||
}).array()
|
color: true
|
||||||
})
|
}).array()
|
||||||
)
|
})
|
||||||
.array(),
|
.array(),
|
||||||
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
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 { 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>;
|
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
|
||||||
|
|
||||||
@ -15,12 +15,12 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
.where(buildFindFilter(filter))
|
.where(buildFindFilter(filter))
|
||||||
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
.join(
|
.leftJoin(
|
||||||
TableName.AccessApprovalPolicyApprover,
|
TableName.AccessApprovalPolicyApprover,
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
`${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("name").withSchema(TableName.Environment).as("envName"))
|
||||||
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
|
||||||
.select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
|
.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(), {
|
const doc = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), {
|
||||||
[`${TableName.AccessApprovalPolicy}.id` as "id"]: id
|
[`${TableName.AccessApprovalPolicy}.id` as "id"]: id
|
||||||
});
|
});
|
||||||
const formatedDoc = mergeOneToManyRelation(
|
const formattedDoc = sqlNestRelationships({
|
||||||
doc,
|
data: doc,
|
||||||
"id",
|
key: "id",
|
||||||
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
|
parentMapper: (data) => ({
|
||||||
...el,
|
environment: {
|
||||||
envId,
|
id: data.envId,
|
||||||
environment: { id: envId, name, slug }
|
name: data.envName,
|
||||||
|
slug: data.envSlug
|
||||||
|
},
|
||||||
|
projectId: data.projectId,
|
||||||
|
...AccessApprovalPoliciesSchema.parse(data)
|
||||||
}),
|
}),
|
||||||
({ approverId }) => approverId,
|
childrenMapper: [
|
||||||
"approvers"
|
{
|
||||||
);
|
key: "approverUserId",
|
||||||
return formatedDoc?.[0];
|
label: "userApprovers" as const,
|
||||||
|
mapper: ({ approverUserId }) => ({
|
||||||
|
userId: approverUserId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedDoc?.[0];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "FindById" });
|
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) => {
|
const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
|
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
|
||||||
const formatedDoc = mergeOneToManyRelation(
|
|
||||||
docs,
|
const formattedDocs = sqlNestRelationships({
|
||||||
"id",
|
data: docs,
|
||||||
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({
|
key: "id",
|
||||||
...el,
|
parentMapper: (data) => ({
|
||||||
envId,
|
environment: {
|
||||||
environment: { id: envId, name, slug }
|
id: data.envId,
|
||||||
|
name: data.envName,
|
||||||
|
slug: data.envSlug
|
||||||
|
},
|
||||||
|
projectId: data.projectId,
|
||||||
|
...AccessApprovalPoliciesSchema.parse(data)
|
||||||
|
// secretPath: data.secretPath || undefined,
|
||||||
}),
|
}),
|
||||||
({ approverId }) => approverId,
|
childrenMapper: [
|
||||||
"approvers"
|
{
|
||||||
);
|
key: "approverUserId",
|
||||||
return formatedDoc.map((policy) => ({ ...policy, secretPath: policy.secretPath || undefined }));
|
label: "userApprovers" as const,
|
||||||
|
mapper: ({ approverUserId }) => ({
|
||||||
|
userId: approverUserId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return formattedDocs;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find" });
|
throw new DatabaseError({ error, name: "Find" });
|
||||||
}
|
}
|
||||||
|
@ -34,8 +34,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectDAL,
|
projectDAL
|
||||||
projectMembershipDAL
|
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createAccessApprovalPolicy = async ({
|
const createAccessApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@ -45,7 +44,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
approvals,
|
approvals,
|
||||||
approvers,
|
approverUserIds,
|
||||||
projectSlug,
|
projectSlug,
|
||||||
environment,
|
environment,
|
||||||
enforcementLevel
|
enforcementLevel
|
||||||
@ -53,7 +52,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
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" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -70,15 +69,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
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({
|
await verifyApprovers({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
@ -86,7 +76,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
permissionService,
|
permissionService,
|
||||||
userIds: secretApprovers.map((approver) => approver.userId)
|
userIds: approverUserIds
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
@ -101,8 +91,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
secretApprovers.map(({ id }) => ({
|
approverUserIds.map((userId) => ({
|
||||||
approverId: id,
|
approverUserId: userId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
@ -138,7 +128,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
const updateAccessApprovalPolicy = async ({
|
const updateAccessApprovalPolicy = async ({
|
||||||
policyId,
|
policyId,
|
||||||
approvers,
|
approverUserIds,
|
||||||
secretPath,
|
secretPath,
|
||||||
name,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
@ -171,16 +161,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
if (approvers) {
|
if (approverUserIds) {
|
||||||
// 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 }
|
|
||||||
);
|
|
||||||
|
|
||||||
await verifyApprovers({
|
await verifyApprovers({
|
||||||
projectId: accessApprovalPolicy.projectId,
|
projectId: accessApprovalPolicy.projectId,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
@ -188,15 +169,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
secretPath: doc.secretPath!,
|
secretPath: doc.secretPath!,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
permissionService,
|
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.delete({ policyId: doc.id }, tx);
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
secretApprovers.map(({ id }) => ({
|
approverUserIds.map((userId) => ({
|
||||||
approverId: id,
|
approverUserId: userId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
|
@ -17,7 +17,7 @@ export type TCreateAccessApprovalPolicy = {
|
|||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
approvers: string[];
|
approverUserIds: string[];
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
name: string;
|
name: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
@ -26,7 +26,7 @@ export type TCreateAccessApprovalPolicy = {
|
|||||||
export type TUpdateAccessApprovalPolicy = {
|
export type TUpdateAccessApprovalPolicy = {
|
||||||
policyId: string;
|
policyId: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
approvers?: string[];
|
approverUserIds?: string[];
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
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 { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||||
|
|
||||||
@ -40,6 +40,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.join<TUsers>(
|
||||||
|
db(TableName.Users).as("requestedByUser"),
|
||||||
|
`${TableName.AccessApprovalRequest}.requestedByUserId`,
|
||||||
|
`requestedByUser.id`
|
||||||
|
)
|
||||||
|
|
||||||
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
|
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
@ -52,7 +58,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
|
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
|
||||||
)
|
)
|
||||||
|
|
||||||
.select(db.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
|
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
|
|
||||||
.select(
|
.select(
|
||||||
db.ref("projectId").withSchema(TableName.Environment),
|
db.ref("projectId").withSchema(TableName.Environment),
|
||||||
@ -61,15 +67,20 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
.select(
|
.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")
|
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: ADD SUPPORT FOR GROUPS!!!!
|
||||||
.select(
|
.select(
|
||||||
db
|
db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
|
||||||
.ref("projectMembershipId")
|
db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
|
||||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
|
||||||
.as("privilegeMembershipId"),
|
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("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
|
||||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
|
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
|
||||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
|
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
|
||||||
@ -102,9 +113,18 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
enforcementLevel: doc.policyEnforcementLevel,
|
enforcementLevel: doc.policyEnforcementLevel,
|
||||||
envId: doc.policyEnvId
|
envId: doc.policyEnvId
|
||||||
},
|
},
|
||||||
|
requestedByUser: {
|
||||||
|
userId: doc.requestedByUserId,
|
||||||
|
email: doc.requestedByUserEmail,
|
||||||
|
firstName: doc.requestedByUserFirstName,
|
||||||
|
lastName: doc.requestedByUserLastName,
|
||||||
|
username: doc.requestedByUserUsername
|
||||||
|
},
|
||||||
privilege: doc.privilegeId
|
privilege: doc.privilegeId
|
||||||
? {
|
? {
|
||||||
membershipId: doc.privilegeMembershipId,
|
membershipId: doc.privilegeMembershipId,
|
||||||
|
userId: doc.privilegeUserId,
|
||||||
|
projectId: doc.projectId,
|
||||||
isTemporary: doc.privilegeIsTemporary,
|
isTemporary: doc.privilegeIsTemporary,
|
||||||
temporaryMode: doc.privilegeTemporaryMode,
|
temporaryMode: doc.privilegeTemporaryMode,
|
||||||
temporaryRange: doc.privilegeTemporaryRange,
|
temporaryRange: doc.privilegeTemporaryRange,
|
||||||
@ -118,11 +138,11 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
key: "reviewerMemberId",
|
key: "reviewerUserId",
|
||||||
label: "reviewers" as const,
|
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 }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -146,30 +166,65 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.AccessApprovalPolicy}.id`
|
`${TableName.AccessApprovalPolicy}.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.join<TUsers>(
|
||||||
|
db(TableName.Users).as("requestedByUser"),
|
||||||
|
`${TableName.AccessApprovalRequest}.requestedByUserId`,
|
||||||
|
`requestedByUser.id`
|
||||||
|
)
|
||||||
|
|
||||||
.join(
|
.join(
|
||||||
TableName.AccessApprovalPolicyApprover,
|
TableName.AccessApprovalPolicyApprover,
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.join<TUsers>(
|
||||||
|
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
|
||||||
|
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
||||||
|
"accessApprovalPolicyApproverUser.id"
|
||||||
|
)
|
||||||
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AccessApprovalRequestReviewer,
|
TableName.AccessApprovalRequestReviewer,
|
||||||
`${TableName.AccessApprovalRequest}.id`,
|
`${TableName.AccessApprovalRequest}.id`,
|
||||||
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.leftJoin<TUsers>(
|
||||||
|
db(TableName.Users).as("accessApprovalReviewerUser"),
|
||||||
|
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
|
||||||
|
`accessApprovalReviewerUser.id`
|
||||||
|
)
|
||||||
|
|
||||||
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
.select(
|
.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("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("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
|
||||||
tx.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
|
tx.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
|
||||||
tx.ref("projectId").withSchema(TableName.Environment),
|
tx.ref("projectId").withSchema(TableName.Environment),
|
||||||
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||||
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||||
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals")
|
||||||
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
@ -189,15 +244,45 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
approvals: el.policyApprovals,
|
approvals: el.policyApprovals,
|
||||||
secretPath: el.policySecretPath,
|
secretPath: el.policySecretPath,
|
||||||
enforcementLevel: el.policyEnforcementLevel
|
enforcementLevel: el.policyEnforcementLevel
|
||||||
|
},
|
||||||
|
requestedByUser: {
|
||||||
|
userId: el.requestedByUserId,
|
||||||
|
email: el.requestedByUserEmail,
|
||||||
|
firstName: el.requestedByUserFirstName,
|
||||||
|
lastName: el.requestedByUserLastName,
|
||||||
|
username: el.requestedByUserUsername
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
key: "reviewerMemberId",
|
key: "reviewerUserId",
|
||||||
label: "reviewers" as const,
|
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;
|
if (!formatedDoc?.[0]) return;
|
||||||
@ -235,7 +320,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
.where(`${TableName.Environment}.projectId`, projectId)
|
.where(`${TableName.Environment}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||||
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
|
.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({
|
const formattedRequests = sqlNestRelationships({
|
||||||
data: accessRequests,
|
data: accessRequests,
|
||||||
@ -245,9 +330,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
{
|
{
|
||||||
key: "reviewerMemberId",
|
key: "reviewerUserId",
|
||||||
label: "reviewers" as const,
|
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">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
userDAL: Pick<TUserDALFactory, "findUserByProjectMembershipId" | "findUsersByProjectMembershipIds">;
|
userDAL: Pick<
|
||||||
|
TUserDALFactory,
|
||||||
|
"findUserByProjectMembershipId" | "findUsersByProjectMembershipIds" | "find" | "findById"
|
||||||
|
>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
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" });
|
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" });
|
if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" });
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(project.id);
|
await projectDAL.checkProjectUpgradeStatus(project.id);
|
||||||
@ -114,13 +117,15 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
policyId: policy.id
|
policyId: policy.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const approverUsers = await userDAL.findUsersByProjectMembershipIds(
|
const approverUsers = await userDAL.find({
|
||||||
approvers.map((approver) => approver.approverId)
|
$in: {
|
||||||
);
|
id: approvers.map((approver) => approver.approverUserId)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const duplicateRequests = await accessApprovalRequestDAL.find({
|
const duplicateRequests = await accessApprovalRequestDAL.find({
|
||||||
policyId: policy.id,
|
policyId: policy.id,
|
||||||
requestedBy: membership.id,
|
requestedByUserId: actorId,
|
||||||
permissions: JSON.stringify(requestedPermissions),
|
permissions: JSON.stringify(requestedPermissions),
|
||||||
isTemporary
|
isTemporary
|
||||||
});
|
});
|
||||||
@ -153,7 +158,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const approvalRequest = await accessApprovalRequestDAL.create(
|
const approvalRequest = await accessApprovalRequestDAL.create(
|
||||||
{
|
{
|
||||||
policyId: policy.id,
|
policyId: policy.id,
|
||||||
requestedBy: membership.id,
|
requestedByUserId: actorId,
|
||||||
temporaryRange: temporaryRange || null,
|
temporaryRange: temporaryRange || null,
|
||||||
permissions: JSON.stringify(requestedPermissions),
|
permissions: JSON.stringify(requestedPermissions),
|
||||||
isTemporary
|
isTemporary
|
||||||
@ -212,7 +217,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
|
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
|
||||||
|
|
||||||
if (authorProjectMembershipId) {
|
if (authorProjectMembershipId) {
|
||||||
requests = requests.filter((request) => request.requestedBy === authorProjectMembershipId);
|
requests = requests.filter((request) => request.requestedByUserId === actorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envSlug) {
|
if (envSlug) {
|
||||||
@ -246,8 +251,8 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
accessApprovalRequest.requestedBy !== membership.id && // The request wasn't made by the current user
|
accessApprovalRequest.requestedByUserId !== actorId && // 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
|
!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" });
|
throw new UnauthorizedError({ message: "You are not authorized to approve this request" });
|
||||||
}
|
}
|
||||||
@ -273,7 +278,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const review = await accessApprovalRequestReviewerDAL.findOne(
|
const review = await accessApprovalRequestReviewerDAL.findOne(
|
||||||
{
|
{
|
||||||
requestId: accessApprovalRequest.id,
|
requestId: accessApprovalRequest.id,
|
||||||
member: membership.id
|
reviewerUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -282,7 +287,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
{
|
{
|
||||||
status,
|
status,
|
||||||
requestId: accessApprovalRequest.id,
|
requestId: accessApprovalRequest.id,
|
||||||
member: membership.id
|
reviewerUserId: actorId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@ -303,7 +308,8 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
// Permanent access
|
// Permanent access
|
||||||
const privilege = await additionalPrivilegeDAL.create(
|
const privilege = await additionalPrivilegeDAL.create(
|
||||||
{
|
{
|
||||||
projectMembershipId: accessApprovalRequest.requestedBy,
|
userId: accessApprovalRequest.requestedByUserId,
|
||||||
|
projectId: accessApprovalRequest.projectId,
|
||||||
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||||
permissions: JSON.stringify(accessApprovalRequest.permissions)
|
permissions: JSON.stringify(accessApprovalRequest.permissions)
|
||||||
},
|
},
|
||||||
@ -317,7 +323,8 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const privilege = await additionalPrivilegeDAL.create(
|
const privilege = await additionalPrivilegeDAL.create(
|
||||||
{
|
{
|
||||||
projectMembershipId: accessApprovalRequest.requestedBy,
|
userId: accessApprovalRequest.requestedByUserId,
|
||||||
|
projectId: accessApprovalRequest.projectId,
|
||||||
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||||
permissions: JSON.stringify(accessApprovalRequest.permissions),
|
permissions: JSON.stringify(accessApprovalRequest.permissions),
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
|
@ -75,15 +75,16 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
.del()
|
.del()
|
||||||
.returning("id");
|
.returning("id");
|
||||||
numberOfRetryOnFailure = 0; // reset
|
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) {
|
} catch (error) {
|
||||||
numberOfRetryOnFailure += 1;
|
numberOfRetryOnFailure += 1;
|
||||||
logger.error(error, "Failed to delete audit log on pruning");
|
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 };
|
return { ...auditLogOrm, pruneAuditLog, find };
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
@ -61,6 +62,10 @@ export const auditLogServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createAuditLog = async (data: TCreateAuditLogDTO) => {
|
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
|
// add all cases in which project id or org id cannot be added
|
||||||
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
||||||
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
|
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must 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 { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
|
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
||||||
|
|
||||||
export type TListProjectAuditLogDTO = {
|
export type TListProjectAuditLogDTO = {
|
||||||
auditLogActor?: string;
|
auditLogActor?: string;
|
||||||
@ -130,16 +131,42 @@ export enum EventType {
|
|||||||
GET_CA = "get-certificate-authority",
|
GET_CA = "get-certificate-authority",
|
||||||
UPDATE_CA = "update-certificate-authority",
|
UPDATE_CA = "update-certificate-authority",
|
||||||
DELETE_CA = "delete-certificate-authority",
|
DELETE_CA = "delete-certificate-authority",
|
||||||
|
RENEW_CA = "renew-certificate-authority",
|
||||||
GET_CA_CSR = "get-certificate-authority-csr",
|
GET_CA_CSR = "get-certificate-authority-csr",
|
||||||
|
GET_CA_CERTS = "get-certificate-authority-certs",
|
||||||
GET_CA_CERT = "get-certificate-authority-cert",
|
GET_CA_CERT = "get-certificate-authority-cert",
|
||||||
SIGN_INTERMEDIATE = "sign-intermediate",
|
SIGN_INTERMEDIATE = "sign-intermediate",
|
||||||
IMPORT_CA_CERT = "import-certificate-authority-cert",
|
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",
|
ISSUE_CERT = "issue-cert",
|
||||||
|
SIGN_CERT = "sign-cert",
|
||||||
GET_CERT = "get-cert",
|
GET_CERT = "get-cert",
|
||||||
DELETE_CERT = "delete-cert",
|
DELETE_CERT = "delete-cert",
|
||||||
REVOKE_CERT = "revoke-cert",
|
REVOKE_CERT = "revoke-cert",
|
||||||
GET_CERT_BODY = "get-cert-body"
|
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",
|
||||||
|
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 {
|
interface UserActorMetadata {
|
||||||
@ -329,6 +356,7 @@ interface DeleteIntegrationEvent {
|
|||||||
targetServiceId?: string;
|
targetServiceId?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
region?: string;
|
region?: string;
|
||||||
|
shouldDeleteIntegrationSecrets?: boolean;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1086,6 +1114,14 @@ interface DeleteCa {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RenewCa {
|
||||||
|
type: EventType.RENEW_CA;
|
||||||
|
metadata: {
|
||||||
|
caId: string;
|
||||||
|
dn: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface GetCaCsr {
|
interface GetCaCsr {
|
||||||
type: EventType.GET_CA_CSR;
|
type: EventType.GET_CA_CSR;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -1094,6 +1130,14 @@ interface GetCaCsr {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetCaCerts {
|
||||||
|
type: EventType.GET_CA_CERTS;
|
||||||
|
metadata: {
|
||||||
|
caId: string;
|
||||||
|
dn: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface GetCaCert {
|
interface GetCaCert {
|
||||||
type: EventType.GET_CA_CERT;
|
type: EventType.GET_CA_CERT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -1119,8 +1163,8 @@ interface ImportCaCert {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetCaCrl {
|
interface GetCaCrls {
|
||||||
type: EventType.GET_CA_CRL;
|
type: EventType.GET_CA_CRLS;
|
||||||
metadata: {
|
metadata: {
|
||||||
caId: string;
|
caId: string;
|
||||||
dn: string;
|
dn: string;
|
||||||
@ -1136,6 +1180,15 @@ interface IssueCert {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SignCert {
|
||||||
|
type: EventType.SIGN_CERT;
|
||||||
|
metadata: {
|
||||||
|
caId: string;
|
||||||
|
dn: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface GetCert {
|
interface GetCert {
|
||||||
type: EventType.GET_CERT;
|
type: EventType.GET_CERT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -1172,6 +1225,201 @@ 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: {
|
||||||
|
kmsId: string;
|
||||||
|
provider: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteKmsEvent {
|
||||||
|
type: EventType.DELETE_KMS;
|
||||||
|
metadata: {
|
||||||
|
kmsId: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateKmsEvent {
|
||||||
|
type: EventType.UPDATE_KMS;
|
||||||
|
metadata: {
|
||||||
|
kmsId: string;
|
||||||
|
provider: string;
|
||||||
|
slug?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetKmsEvent {
|
||||||
|
type: EventType.GET_KMS;
|
||||||
|
metadata: {
|
||||||
|
kmsId: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateProjectKmsEvent {
|
||||||
|
type: EventType.UPDATE_PROJECT_KMS;
|
||||||
|
metadata: {
|
||||||
|
secretManagerKmsKey: {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetProjectKmsBackupEvent {
|
||||||
|
type: EventType.GET_PROJECT_KMS_BACKUP;
|
||||||
|
metadata: Record<string, string>; // no metadata yet
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoadProjectKmsBackupEvent {
|
||||||
|
type: EventType.LOAD_PROJECT_KMS_BACKUP;
|
||||||
|
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 =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@ -1264,13 +1512,39 @@ export type Event =
|
|||||||
| GetCa
|
| GetCa
|
||||||
| UpdateCa
|
| UpdateCa
|
||||||
| DeleteCa
|
| DeleteCa
|
||||||
|
| RenewCa
|
||||||
| GetCaCsr
|
| GetCaCsr
|
||||||
|
| GetCaCerts
|
||||||
| GetCaCert
|
| GetCaCert
|
||||||
| SignIntermediate
|
| SignIntermediate
|
||||||
| ImportCaCert
|
| ImportCaCert
|
||||||
| GetCaCrl
|
| GetCaCrls
|
||||||
| IssueCert
|
| IssueCert
|
||||||
|
| SignCert
|
||||||
| GetCert
|
| GetCert
|
||||||
| DeleteCert
|
| DeleteCert
|
||||||
| RevokeCert
|
| RevokeCert
|
||||||
| GetCertBody;
|
| GetCertBody
|
||||||
|
| CreatePkiAlert
|
||||||
|
| GetPkiAlert
|
||||||
|
| UpdatePkiAlert
|
||||||
|
| DeletePkiAlert
|
||||||
|
| CreatePkiCollection
|
||||||
|
| GetPkiCollection
|
||||||
|
| UpdatePkiCollection
|
||||||
|
| DeletePkiCollection
|
||||||
|
| GetPkiCollectionItems
|
||||||
|
| AddPkiCollectionItem
|
||||||
|
| DeletePkiCollectionItem
|
||||||
|
| CreateKmsEvent
|
||||||
|
| UpdateKmsEvent
|
||||||
|
| DeleteKmsEvent
|
||||||
|
| GetKmsEvent
|
||||||
|
| UpdateProjectKmsEvent
|
||||||
|
| GetProjectKmsBackupEvent
|
||||||
|
| LoadProjectKmsBackupEvent
|
||||||
|
| OrgAdminAccessProjectEvent
|
||||||
|
| CreateCertificateTemplate
|
||||||
|
| UpdateCertificateTemplate
|
||||||
|
| GetCertificateTemplate
|
||||||
|
| DeleteCertificateTemplate;
|
||||||
|
@ -2,24 +2,24 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
// import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
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 { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
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 = {
|
type TCertificateAuthorityCrlServiceFactoryDep = {
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "findOne">;
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "find" | "findById">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
// licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateAuthorityCrlServiceFactory = ReturnType<typeof certificateAuthorityCrlServiceFactory>;
|
export type TCertificateAuthorityCrlServiceFactory = ReturnType<typeof certificateAuthorityCrlServiceFactory>;
|
||||||
@ -29,13 +29,42 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
certificateAuthorityCrlDAL,
|
certificateAuthorityCrlDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService,
|
kmsService,
|
||||||
permissionService,
|
permissionService // licenseService
|
||||||
licenseService
|
|
||||||
}: TCertificateAuthorityCrlServiceFactoryDep) => {
|
}: 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);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
@ -52,15 +81,14 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
// const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan.caCrl)
|
// if (!plan.caCrl)
|
||||||
throw new BadRequestError({
|
// throw new BadRequestError({
|
||||||
message:
|
// message:
|
||||||
"Failed to get CA certificate revocation list (CRL) due to plan restriction. Upgrade plan to get the CA CRL."
|
// "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 });
|
const caCrls = await certificateAuthorityCrlDAL.find({ caId: ca.id }, { sort: [["createdAt", "desc"]] });
|
||||||
if (!caCrl) throw new BadRequestError({ message: "CRL not found" });
|
|
||||||
|
|
||||||
const keyId = await getProjectKmsCertificateKeyId({
|
const keyId = await getProjectKmsCertificateKeyId({
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
@ -72,15 +100,23 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
|
|
||||||
const decryptedCrl = kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
|
const decryptedCrls = await Promise.all(
|
||||||
const crl = new x509.X509Crl(decryptedCrl);
|
caCrls.map(async (caCrl) => {
|
||||||
|
const decryptedCrl = await kmsDecryptor({ cipherTextBlob: caCrl.encryptedCrl });
|
||||||
|
const crl = new x509.X509Crl(decryptedCrl);
|
||||||
|
|
||||||
const base64crl = crl.toString("base64");
|
const base64crl = crl.toString("base64");
|
||||||
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
|
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
|
||||||
|
return {
|
||||||
|
id: caCrl.id,
|
||||||
|
crl: crlPem
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crl: crlPem,
|
ca,
|
||||||
ca
|
crls: decryptedCrls
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -166,7 +202,8 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getCaCrl
|
getCrlById,
|
||||||
|
getCaCrls
|
||||||
// rotateCaCrl
|
// rotateCaCrl
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TGetCrl = {
|
export type TGetCrlById = string;
|
||||||
|
|
||||||
|
export type TGetCaCrlsDTO = {
|
||||||
caId: string;
|
caId: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@ -12,10 +12,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
|
const countLeasesForDynamicSecret = async (dynamicSecretId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
|
const doc = await (tx || db)(TableName.DynamicSecretLease).count("*").where({ dynamicSecretId }).first();
|
||||||
.count("*")
|
|
||||||
.where({ dynamicSecretId })
|
|
||||||
.first();
|
|
||||||
return parseInt(doc || "0", 10);
|
return parseInt(doc || "0", 10);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
|
throw new DatabaseError({ error, name: "DynamicSecretCountLeases" });
|
||||||
@ -24,7 +21,7 @@ export const dynamicSecretLeaseDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findById = async (id: string, tx?: Knex) => {
|
const findById = async (id: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const doc = await (tx || db.replicaNode())(TableName.DynamicSecretLease)
|
const doc = await (tx || db)(TableName.DynamicSecretLease)
|
||||||
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
|
.where({ [`${TableName.DynamicSecretLease}.id` as "id"]: id })
|
||||||
.first()
|
.first()
|
||||||
.join(
|
.join(
|
||||||
|
@ -31,6 +31,8 @@ export const externalKmsDALFactory = (db: TDbClient) => {
|
|||||||
isReserved: el.isReserved,
|
isReserved: el.isReserved,
|
||||||
orgId: el.orgId,
|
orgId: el.orgId,
|
||||||
slug: el.slug,
|
slug: el.slug,
|
||||||
|
createdAt: el.createdAt,
|
||||||
|
updatedAt: el.updatedAt,
|
||||||
externalKms: {
|
externalKms: {
|
||||||
id: el.externalKmsId,
|
id: el.externalKmsId,
|
||||||
provider: el.externalKmsProvider,
|
provider: el.externalKmsProvider,
|
||||||
|
@ -5,7 +5,9 @@ import { BadRequestError } from "@app/lib/errors";
|
|||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { TExternalKmsDALFactory } from "./external-kms-dal";
|
import { TExternalKmsDALFactory } from "./external-kms-dal";
|
||||||
@ -22,9 +24,10 @@ import { ExternalKmsAwsSchema, KmsProviders } from "./providers/model";
|
|||||||
|
|
||||||
type TExternalKmsServiceFactoryDep = {
|
type TExternalKmsServiceFactoryDep = {
|
||||||
externalKmsDAL: TExternalKmsDALFactory;
|
externalKmsDAL: TExternalKmsDALFactory;
|
||||||
kmsService: Pick<TKmsServiceFactory, "getOrgKmsKeyId" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "getOrgKmsKeyId" | "createCipherPairWithDataKey">;
|
||||||
kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">;
|
kmsDAL: Pick<TKmsKeyDALFactory, "create" | "updateById" | "findById" | "deleteById" | "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TExternalKmsServiceFactory = ReturnType<typeof externalKmsServiceFactory>;
|
export type TExternalKmsServiceFactory = ReturnType<typeof externalKmsServiceFactory>;
|
||||||
@ -32,6 +35,7 @@ export type TExternalKmsServiceFactory = ReturnType<typeof externalKmsServiceFac
|
|||||||
export const externalKmsServiceFactory = ({
|
export const externalKmsServiceFactory = ({
|
||||||
externalKmsDAL,
|
externalKmsDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
|
licenseService,
|
||||||
kmsService,
|
kmsService,
|
||||||
kmsDAL
|
kmsDAL
|
||||||
}: TExternalKmsServiceFactoryDep) => {
|
}: TExternalKmsServiceFactoryDep) => {
|
||||||
@ -51,7 +55,15 @@ export const externalKmsServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Kms);
|
||||||
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
|
if (!plan.externalKms) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to create external KMS due to plan restriction. Upgrade to the Enterprise plan."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const kmsSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
|
const kmsSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
|
||||||
|
|
||||||
let sanitizedProviderInput = "";
|
let sanitizedProviderInput = "";
|
||||||
@ -59,21 +71,23 @@ export const externalKmsServiceFactory = ({
|
|||||||
case KmsProviders.Aws:
|
case KmsProviders.Aws:
|
||||||
{
|
{
|
||||||
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
|
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
|
||||||
await externalKms.validateConnection();
|
|
||||||
// if missing kms key this generate a new kms key id and returns new provider input
|
// if missing kms key this generate a new kms key id and returns new provider input
|
||||||
const newProviderInput = await externalKms.generateInputKmsKey();
|
const newProviderInput = await externalKms.generateInputKmsKey();
|
||||||
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
||||||
|
|
||||||
|
await externalKms.validateConnection();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({ message: "external kms provided is invalid" });
|
throw new BadRequestError({ message: "external kms provided is invalid" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgKmsKeyId = await kmsService.getOrgKmsKeyId(actorOrgId);
|
const { encryptor: orgDataKeyEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
type: KmsDataKey.Organization,
|
||||||
kmsId: orgKmsKeyId
|
orgId: actorOrgId
|
||||||
});
|
});
|
||||||
const { cipherTextBlob: encryptedProviderInputs } = kmsEncryptor({
|
|
||||||
|
const { cipherTextBlob: encryptedProviderInputs } = orgDataKeyEncryptor({
|
||||||
plainText: Buffer.from(sanitizedProviderInput, "utf8")
|
plainText: Buffer.from(sanitizedProviderInput, "utf8")
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -119,19 +133,28 @@ export const externalKmsServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(kmsDoc.orgId);
|
||||||
|
if (!plan.externalKms) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to update external KMS due to plan restriction. Upgrade to the Enterprise plan."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const kmsSlug = slug ? slugify(slug) : undefined;
|
const kmsSlug = slug ? slugify(slug) : undefined;
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
||||||
|
|
||||||
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
|
|
||||||
let sanitizedProviderInput = "";
|
let sanitizedProviderInput = "";
|
||||||
if (provider) {
|
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
|
||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
await kmsService.createCipherPairWithDataKey({
|
||||||
kmsId: orgDefaultKmsId
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: actorOrgId
|
||||||
});
|
});
|
||||||
const decryptedProviderInputBlob = kmsDecryptor({
|
if (provider) {
|
||||||
|
const decryptedProviderInputBlob = orgDataKeyDecryptor({
|
||||||
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
|
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -154,10 +177,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
|
|
||||||
let encryptedProviderInputs: Buffer | undefined;
|
let encryptedProviderInputs: Buffer | undefined;
|
||||||
if (sanitizedProviderInput) {
|
if (sanitizedProviderInput) {
|
||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
const { cipherTextBlob } = orgDataKeyEncryptor({
|
||||||
kmsId: orgDefaultKmsId
|
|
||||||
});
|
|
||||||
const { cipherTextBlob } = kmsEncryptor({
|
|
||||||
plainText: Buffer.from(sanitizedProviderInput, "utf8")
|
plainText: Buffer.from(sanitizedProviderInput, "utf8")
|
||||||
});
|
});
|
||||||
encryptedProviderInputs = cipherTextBlob;
|
encryptedProviderInputs = cipherTextBlob;
|
||||||
@ -197,7 +217,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
||||||
@ -218,7 +238,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId });
|
const externalKmsDocs = await externalKmsDAL.find({ orgId: actorOrgId });
|
||||||
|
|
||||||
@ -234,16 +254,18 @@ export const externalKmsServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
||||||
|
|
||||||
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
|
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
type: KmsDataKey.Organization,
|
||||||
kmsId: orgDefaultKmsId
|
orgId: actorOrgId
|
||||||
});
|
});
|
||||||
const decryptedProviderInputBlob = kmsDecryptor({
|
|
||||||
|
const decryptedProviderInputBlob = orgDataKeyDecryptor({
|
||||||
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
|
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
|
||||||
});
|
});
|
||||||
switch (externalKmsDoc.provider) {
|
switch (externalKmsDoc.provider) {
|
||||||
@ -273,16 +295,17 @@ export const externalKmsServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new BadRequestError({ message: "External kms not found" });
|
||||||
|
|
||||||
const orgDefaultKmsId = await kmsService.getOrgKmsKeyId(kmsDoc.orgId);
|
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
type: KmsDataKey.Organization,
|
||||||
kmsId: orgDefaultKmsId
|
orgId: actorOrgId
|
||||||
});
|
});
|
||||||
const decryptedProviderInputBlob = kmsDecryptor({
|
|
||||||
|
const decryptedProviderInputBlob = orgDataKeyDecryptor({
|
||||||
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
|
cipherTextBlob: externalKmsDoc.encryptedProviderInputs
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,17 +50,26 @@ type TAwsKmsProviderFactoryReturn = TExternalKmsProviderFns & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Promise<TAwsKmsProviderFactoryReturn> => {
|
export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Promise<TAwsKmsProviderFactoryReturn> => {
|
||||||
const providerInputs = await ExternalKmsAwsSchema.parseAsync(inputs);
|
let providerInputs = await ExternalKmsAwsSchema.parseAsync(inputs);
|
||||||
const awsClient = await getAwsKmsClient(providerInputs);
|
let awsClient = await getAwsKmsClient(providerInputs);
|
||||||
|
|
||||||
const generateInputKmsKey = async () => {
|
const generateInputKmsKey = async () => {
|
||||||
if (providerInputs.kmsKeyId) return providerInputs;
|
if (providerInputs.kmsKeyId) return providerInputs;
|
||||||
|
|
||||||
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
|
const command = new CreateKeyCommand({ Tags: [{ TagKey: "author", TagValue: "infisical" }] });
|
||||||
const kmsKey = await awsClient.send(command);
|
const kmsKey = await awsClient.send(command);
|
||||||
|
|
||||||
if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key");
|
if (!kmsKey.KeyMetadata?.KeyId) throw new Error("Failed to generate kms key");
|
||||||
|
|
||||||
return { ...providerInputs, kmsKeyId: kmsKey.KeyMetadata?.KeyId };
|
const updatedProviderInputs = await ExternalKmsAwsSchema.parseAsync({
|
||||||
|
...providerInputs,
|
||||||
|
kmsKeyId: kmsKey.KeyMetadata?.KeyId
|
||||||
|
});
|
||||||
|
|
||||||
|
providerInputs = updatedProviderInputs;
|
||||||
|
awsClient = await getAwsKmsClient(providerInputs);
|
||||||
|
|
||||||
|
return updatedProviderInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateConnection = async () => {
|
const validateConnection = async () => {
|
||||||
|
@ -336,31 +336,36 @@ export const removeUsersFromGroupByUserIds = async ({
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: this part can be optimized
|
const promises: Array<Promise<void>> = [];
|
||||||
for await (const userId of userIds) {
|
for (const userId of userIds) {
|
||||||
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(userId, group.id, projectIds, tx);
|
promises.push(
|
||||||
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
(async () => {
|
||||||
|
const t = await userGroupMembershipDAL.filterProjectsByUserMembership(userId, group.id, projectIds, tx);
|
||||||
|
const projectsToDeleteKeyFor = projectIds.filter((p) => !t.has(p));
|
||||||
|
|
||||||
if (projectsToDeleteKeyFor.length) {
|
if (projectsToDeleteKeyFor.length) {
|
||||||
await projectKeyDAL.delete(
|
await projectKeyDAL.delete(
|
||||||
{
|
{
|
||||||
receiverId: userId,
|
receiverId: userId,
|
||||||
$in: {
|
$in: {
|
||||||
projectId: projectsToDeleteKeyFor
|
projectId: projectsToDeleteKeyFor
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await userGroupMembershipDAL.delete(
|
await userGroupMembershipDAL.delete(
|
||||||
{
|
{
|
||||||
groupId: group.id,
|
groupId: group.id,
|
||||||
userId
|
userId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
|
);
|
||||||
|
})()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (membersToRemoveFromGroupPending.length) {
|
if (membersToRemoveFromGroupPending.length) {
|
||||||
|
@ -39,7 +39,13 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
secretApproval: false,
|
secretApproval: false,
|
||||||
secretRotation: true,
|
secretRotation: true,
|
||||||
caCrl: false,
|
caCrl: false,
|
||||||
instanceUserManagement: false
|
instanceUserManagement: false,
|
||||||
|
externalKms: false,
|
||||||
|
rateLimits: {
|
||||||
|
readLimit: 60,
|
||||||
|
writeLimit: 200,
|
||||||
|
secretsLimit: 40
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenceRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
@ -57,6 +57,12 @@ export type TFeatureSet = {
|
|||||||
secretRotation: true;
|
secretRotation: true;
|
||||||
caCrl: false;
|
caCrl: false;
|
||||||
instanceUserManagement: false;
|
instanceUserManagement: false;
|
||||||
|
externalKms: false;
|
||||||
|
rateLimits: {
|
||||||
|
readLimit: number;
|
||||||
|
writeLimit: number;
|
||||||
|
secretsLimit: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
@ -9,6 +9,10 @@ export enum OrgPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
|
AccessAllProjects = "access-all-projects"
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrgPermissionSubjects {
|
export enum OrgPermissionSubjects {
|
||||||
Workspace = "workspace",
|
Workspace = "workspace",
|
||||||
Role = "role",
|
Role = "role",
|
||||||
@ -21,7 +25,9 @@ export enum OrgPermissionSubjects {
|
|||||||
Groups = "groups",
|
Groups = "groups",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
SecretScanning = "secret-scanning",
|
SecretScanning = "secret-scanning",
|
||||||
Identity = "identity"
|
Identity = "identity",
|
||||||
|
Kms = "kms",
|
||||||
|
AdminConsole = "organization-admin-console"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
@ -37,7 +43,9 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
|
| [OrgPermissionActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Identity];
|
| [OrgPermissionActions, OrgPermissionSubjects.Identity]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
@ -100,6 +108,13 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Kms);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Kms);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
|
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||||
|
|
||||||
return build({ conditionsMatcher });
|
return build({ conditionsMatcher });
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,7 +126,6 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Groups);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
|
@ -66,6 +66,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
|
||||||
`${TableName.GroupProjectMembership}.id`
|
`${TableName.GroupProjectMembership}.id`
|
||||||
)
|
)
|
||||||
|
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ProjectRoles,
|
TableName.ProjectRoles,
|
||||||
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
`${TableName.GroupProjectMembershipRole}.customRoleId`,
|
||||||
@ -73,6 +74,12 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
|
|
||||||
|
.leftJoin(
|
||||||
|
TableName.ProjectUserAdditionalPrivilege,
|
||||||
|
`${TableName.GroupProjectMembership}.projectId`,
|
||||||
|
`${TableName.Project}.id`
|
||||||
|
)
|
||||||
.select(selectAllTableCols(TableName.GroupProjectMembershipRole))
|
.select(selectAllTableCols(TableName.GroupProjectMembershipRole))
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.GroupProjectMembership).as("membershipId"),
|
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("projectId").withSchema(TableName.GroupProjectMembership),
|
||||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
db.ref("orgId").withSchema(TableName.Project),
|
db.ref("orgId").withSchema(TableName.Project),
|
||||||
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug")
|
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
|
||||||
)
|
|
||||||
.select("permissions");
|
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)
|
const docs = await db(TableName.ProjectMembership)
|
||||||
.join(
|
.join(
|
||||||
@ -98,12 +126,13 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
)
|
)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ProjectUserAdditionalPrivilege,
|
TableName.ProjectUserAdditionalPrivilege,
|
||||||
`${TableName.ProjectUserAdditionalPrivilege}.projectMembershipId`,
|
`${TableName.ProjectUserAdditionalPrivilege}.projectId`,
|
||||||
`${TableName.ProjectMembership}.id`
|
`${TableName.ProjectMembership}.projectId`
|
||||||
)
|
)
|
||||||
|
|
||||||
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.ProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
.where("userId", userId)
|
.where(`${TableName.ProjectMembership}.userId`, userId)
|
||||||
.where(`${TableName.ProjectMembership}.projectId`, projectId)
|
.where(`${TableName.ProjectMembership}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
|
.select(selectAllTableCols(TableName.ProjectUserMembershipRole))
|
||||||
.select(
|
.select(
|
||||||
@ -120,6 +149,10 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
|
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryMode"),
|
||||||
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
|
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApIsTemporary"),
|
||||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("userApTemporaryRange"),
|
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
|
db
|
||||||
.ref("temporaryAccessStartTime")
|
.ref("temporaryAccessStartTime")
|
||||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
@ -198,6 +231,31 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
permissions: z.unknown(),
|
permissions: z.unknown(),
|
||||||
customRoleSlug: z.string().optional().nullable()
|
customRoleSlug: z.string().optional().nullable()
|
||||||
}).parse(data)
|
}).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)
|
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
||||||
) ?? [];
|
) ?? [];
|
||||||
|
|
||||||
const activeAdditionalPrivileges = permission?.[0]?.additionalPrivileges?.filter(
|
const activeAdditionalPrivileges =
|
||||||
({ isTemporary, temporaryAccessEndTime }) =>
|
permission?.[0]?.additionalPrivileges?.filter(
|
||||||
!isTemporary || (isTemporary && temporaryAccessEndTime && new Date() < temporaryAccessEndTime)
|
({ 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 {
|
return {
|
||||||
...(permission[0] || groupPermission[0]),
|
...(permission[0] || groupPermission[0]),
|
||||||
roles: [...activeRoles, ...activeGroupRoles],
|
roles: [...activeRoles, ...activeGroupRoles],
|
||||||
additionalPrivileges: activeAdditionalPrivileges
|
additionalPrivileges: [...activeAdditionalPrivileges, ...activeGroupAdditionalPrivileges]
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
throw new DatabaseError({ error, name: "GetProjectPermission" });
|
||||||
|
@ -23,12 +23,17 @@ export enum ProjectPermissionSub {
|
|||||||
IpAllowList = "ip-allowlist",
|
IpAllowList = "ip-allowlist",
|
||||||
Project = "workspace",
|
Project = "workspace",
|
||||||
Secrets = "secrets",
|
Secrets = "secrets",
|
||||||
|
SecretFolders = "secret-folders",
|
||||||
SecretRollback = "secret-rollback",
|
SecretRollback = "secret-rollback",
|
||||||
SecretApproval = "secret-approval",
|
SecretApproval = "secret-approval",
|
||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
CertificateAuthorities = "certificate-authorities",
|
CertificateAuthorities = "certificate-authorities",
|
||||||
Certificates = "certificates"
|
Certificates = "certificates",
|
||||||
|
CertificateTemplates = "certificate-templates",
|
||||||
|
PkiAlerts = "pki-alerts",
|
||||||
|
PkiCollections = "pki-collections",
|
||||||
|
Kms = "kms"
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubjectFields = {
|
type SubjectFields = {
|
||||||
@ -41,6 +46,10 @@ export type ProjectPermissionSet =
|
|||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
|
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
|
||||||
]
|
]
|
||||||
|
| [
|
||||||
|
ProjectPermissionActions,
|
||||||
|
ProjectPermissionSub.SecretFolders | (ForcedSubject<ProjectPermissionSub.SecretFolders> & SubjectFields)
|
||||||
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
||||||
@ -57,10 +66,14 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
| [ProjectPermissionActions, ProjectPermissionSub.Identity]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback];
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
||||||
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
||||||
|
|
||||||
const buildAdminPermissionRules = () => {
|
const buildAdminPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
@ -154,9 +167,26 @@ const buildAdminPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
||||||
can(ProjectPermissionActions.Delete, 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.Edit, ProjectPermissionSub.Project);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -228,6 +258,11 @@ const buildMemberPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
|
|
||||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||||
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
projectUserAdditionalPrivilegeDAL: TProjectUserAdditionalPrivilegeDALFactory;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById" | "findOne">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,12 +53,17 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
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 (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
|
||||||
if (!dto.isTemporary) {
|
if (!dto.isTemporary) {
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
projectMembershipId,
|
userId: projectMembership.userId,
|
||||||
|
projectId: projectMembership.projectId,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission
|
permissions: customPermission
|
||||||
});
|
});
|
||||||
@ -67,7 +72,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
|
|
||||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
projectMembershipId,
|
projectId: projectMembership.projectId,
|
||||||
|
userId: projectMembership.userId,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission,
|
permissions: customPermission,
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
@ -90,7 +96,11 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
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" });
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -105,7 +115,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (dto?.slug) {
|
if (dto?.slug) {
|
||||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||||
slug: dto.slug,
|
slug: dto.slug,
|
||||||
projectMembershipId: projectMembership.id
|
userId: projectMembership.id,
|
||||||
|
projectId: projectMembership.projectId
|
||||||
});
|
});
|
||||||
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
||||||
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
@ -138,7 +149,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
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" });
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -164,7 +178,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
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" });
|
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -198,7 +215,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
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;
|
return userPrivileges;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,17 +4,16 @@ import { logger } from "@app/lib/logger";
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TRateLimitDALFactory } from "./rate-limit-dal";
|
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,
|
readLimit: 60,
|
||||||
publicEndpointLimit: 30,
|
publicEndpointLimit: 30,
|
||||||
writeLimit: 200,
|
writeLimit: 200,
|
||||||
secretsLimit: 60,
|
secretsLimit: 60,
|
||||||
authRateLimit: 60,
|
authRateLimit: 60,
|
||||||
inviteUserRateLimit: 30,
|
inviteUserRateLimit: 30,
|
||||||
mfaRateLimit: 20,
|
mfaRateLimit: 20
|
||||||
creationLimit: 30
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.freeze(rateLimitMaxConfiguration);
|
Object.freeze(rateLimitMaxConfiguration);
|
||||||
@ -67,8 +66,7 @@ export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateL
|
|||||||
secretsLimit: rateLimit.secretsRateLimit,
|
secretsLimit: rateLimit.secretsRateLimit,
|
||||||
authRateLimit: rateLimit.authRateLimit,
|
authRateLimit: rateLimit.authRateLimit,
|
||||||
inviteUserRateLimit: rateLimit.inviteUserRateLimit,
|
inviteUserRateLimit: rateLimit.inviteUserRateLimit,
|
||||||
mfaRateLimit: rateLimit.mfaRateLimit,
|
mfaRateLimit: rateLimit.mfaRateLimit
|
||||||
creationLimit: rateLimit.creationLimit
|
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.info(`syncRateLimitConfiguration: rate limit configuration: %o`, newRateLimitMaxConfiguration);
|
logger.info(`syncRateLimitConfiguration: rate limit configuration: %o`, newRateLimitMaxConfiguration);
|
||||||
|
@ -5,7 +5,6 @@ export type TRateLimitUpdateDTO = {
|
|||||||
authRateLimit: number;
|
authRateLimit: number;
|
||||||
inviteUserRateLimit: number;
|
inviteUserRateLimit: number;
|
||||||
mfaRateLimit: number;
|
mfaRateLimit: number;
|
||||||
creationLimit: number;
|
|
||||||
publicEndpointLimit: number;
|
publicEndpointLimit: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -14,3 +13,13 @@ export type TRateLimit = {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
} & TRateLimitUpdateDTO;
|
} & TRateLimitUpdateDTO;
|
||||||
|
|
||||||
|
export type RateLimitConfiguration = {
|
||||||
|
readLimit: number;
|
||||||
|
publicEndpointLimit: number;
|
||||||
|
writeLimit: number;
|
||||||
|
secretsLimit: number;
|
||||||
|
authRateLimit: number;
|
||||||
|
inviteUserRateLimit: number;
|
||||||
|
mfaRateLimit: number;
|
||||||
|
};
|
||||||
|
@ -31,6 +31,7 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
|
|||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import {
|
import {
|
||||||
buildScimGroup,
|
buildScimGroup,
|
||||||
buildScimGroupList,
|
buildScimGroupList,
|
||||||
@ -93,6 +94,7 @@ type TScimServiceFactoryDep = {
|
|||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
|
export type TScimServiceFactory = ReturnType<typeof scimServiceFactory>;
|
||||||
@ -112,6 +114,7 @@ export const scimServiceFactory = ({
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
|
projectUserAdditionalPrivilegeDAL,
|
||||||
smtpService
|
smtpService
|
||||||
}: TScimServiceFactoryDep) => {
|
}: TScimServiceFactoryDep) => {
|
||||||
const createScimToken = async ({
|
const createScimToken = async ({
|
||||||
@ -558,6 +561,7 @@ export const scimServiceFactory = ({
|
|||||||
orgId: membership.orgId,
|
orgId: membership.orgId,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
|
projectUserAdditionalPrivilegeDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
licenseService
|
licenseService
|
||||||
|
@ -8,6 +8,7 @@ import { removeTrailingSlash } from "@app/lib/fn";
|
|||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
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 { TSecretApprovalPolicyApproverDALFactory } from "./secret-approval-policy-approver-dal";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "./secret-approval-policy-dal";
|
||||||
import {
|
import {
|
||||||
@ -28,6 +29,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
export type TSecretApprovalPolicyServiceFactory = ReturnType<typeof secretApprovalPolicyServiceFactory>;
|
||||||
@ -36,7 +38,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
projectEnvDAL
|
projectEnvDAL,
|
||||||
|
licenseService
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createSecretApprovalPolicy = async ({
|
const createSecretApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@ -65,6 +68,15 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
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 });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
||||||
|
|
||||||
@ -115,6 +127,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
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 updatedSap = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.updateById(
|
const doc = await secretApprovalPolicyDAL.updateById(
|
||||||
secretApprovalPolicy.id,
|
secretApprovalPolicy.id,
|
||||||
@ -167,6 +187,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionSub.SecretApproval
|
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);
|
await secretApprovalPolicyDAL.deleteById(secretPolicyId);
|
||||||
return sapPolicy;
|
return sapPolicy;
|
||||||
};
|
};
|
||||||
|
@ -356,5 +356,161 @@ export const secretApprovalRequestDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...secretApprovalRequestOrm, findById, findProjectRequestCount, findByProjectId };
|
const findByProjectIdBridgeSecretV2 = async (
|
||||||
|
{ status, limit = 20, offset = 0, projectId, committer, environment, userId }: TFindQueryFilter,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
// akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination
|
||||||
|
// this is the place u wanna look at.
|
||||||
|
const query = (tx || db.replicaNode())(TableName.SecretApprovalRequest)
|
||||||
|
.join(TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, `${TableName.SecretFolder}.id`)
|
||||||
|
.join(TableName.Environment, `${TableName.SecretFolder}.envId`, `${TableName.Environment}.id`)
|
||||||
|
.join(
|
||||||
|
TableName.SecretApprovalPolicy,
|
||||||
|
`${TableName.SecretApprovalRequest}.policyId`,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`
|
||||||
|
)
|
||||||
|
.join(
|
||||||
|
TableName.SecretApprovalPolicyApprover,
|
||||||
|
`${TableName.SecretApprovalPolicy}.id`,
|
||||||
|
`${TableName.SecretApprovalPolicyApprover}.policyId`
|
||||||
|
)
|
||||||
|
.join<TUsers>(
|
||||||
|
db(TableName.Users).as("committerUser"),
|
||||||
|
`${TableName.SecretApprovalRequest}.committerUserId`,
|
||||||
|
`committerUser.id`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretApprovalRequestReviewer,
|
||||||
|
`${TableName.SecretApprovalRequest}.id`,
|
||||||
|
`${TableName.SecretApprovalRequestReviewer}.requestId`
|
||||||
|
)
|
||||||
|
.leftJoin<TSecretApprovalRequestsSecrets>(
|
||||||
|
TableName.SecretApprovalRequestSecretV2,
|
||||||
|
`${TableName.SecretApprovalRequestSecretV2}.requestId`,
|
||||||
|
`${TableName.SecretApprovalRequest}.id`
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
stripUndefinedInWhere({
|
||||||
|
projectId,
|
||||||
|
[`${TableName.Environment}.slug` as "slug"]: environment,
|
||||||
|
[`${TableName.SecretApprovalRequest}.status`]: status,
|
||||||
|
committerUserId: committer
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.andWhere(
|
||||||
|
(bd) =>
|
||||||
|
void bd
|
||||||
|
.where(`${TableName.SecretApprovalPolicyApprover}.approverUserId`, userId)
|
||||||
|
.orWhere(`${TableName.SecretApprovalRequest}.committerUserId`, userId)
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SecretApprovalRequest))
|
||||||
|
.select(
|
||||||
|
db.ref("projectId").withSchema(TableName.Environment),
|
||||||
|
db.ref("slug").withSchema(TableName.Environment).as("environment"),
|
||||||
|
db.ref("id").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerId"),
|
||||||
|
db.ref("reviewerUserId").withSchema(TableName.SecretApprovalRequestReviewer),
|
||||||
|
db.ref("status").withSchema(TableName.SecretApprovalRequestReviewer).as("reviewerStatus"),
|
||||||
|
db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId"),
|
||||||
|
db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName"),
|
||||||
|
db.ref("op").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitOp"),
|
||||||
|
db.ref("secretId").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitSecretId"),
|
||||||
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretV2).as("commitId"),
|
||||||
|
db.raw(
|
||||||
|
`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("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"),
|
||||||
|
db.ref("firstName").withSchema("committerUser").as("committerUserFirstName"),
|
||||||
|
db.ref("lastName").withSchema("committerUser").as("committerUserLastName")
|
||||||
|
)
|
||||||
|
.orderBy("createdAt", "desc");
|
||||||
|
|
||||||
|
const docs = await (tx || db)
|
||||||
|
.with("w", query)
|
||||||
|
.select("*")
|
||||||
|
.from<Awaited<typeof query>[number]>("w")
|
||||||
|
.where("w.rank", ">=", offset)
|
||||||
|
.andWhere("w.rank", "<", offset + limit);
|
||||||
|
const formatedDoc = sqlNestRelationships({
|
||||||
|
data: docs,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (el) => ({
|
||||||
|
...SecretApprovalRequestsSchema.parse(el),
|
||||||
|
environment: el.environment,
|
||||||
|
projectId: el.projectId,
|
||||||
|
policy: {
|
||||||
|
id: el.policyId,
|
||||||
|
name: el.policyName,
|
||||||
|
approvals: el.policyApprovals,
|
||||||
|
secretPath: el.policySecretPath,
|
||||||
|
enforcementLevel: el.policyEnforcementLevel
|
||||||
|
},
|
||||||
|
committerUser: {
|
||||||
|
userId: el.committerUserId,
|
||||||
|
email: el.committerUserEmail,
|
||||||
|
firstName: el.committerUserFirstName,
|
||||||
|
lastName: el.committerUserLastName,
|
||||||
|
username: el.committerUserUsername
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "reviewerId",
|
||||||
|
label: "reviewers" as const,
|
||||||
|
mapper: ({ reviewerUserId, reviewerStatus: s }) =>
|
||||||
|
reviewerUserId ? { userId: reviewerUserId, status: s } : undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "approverUserId",
|
||||||
|
label: "approvers" as const,
|
||||||
|
mapper: ({ approverUserId }) => approverUserId
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "commitId",
|
||||||
|
label: "commits" as const,
|
||||||
|
mapper: ({ commitSecretId: secretId, commitId: id, commitOp: op }) => ({
|
||||||
|
op,
|
||||||
|
id,
|
||||||
|
secretId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return formatedDoc.map((el) => ({
|
||||||
|
...el,
|
||||||
|
policy: { ...el.policy, approvers: el.approvers }
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindSAR" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
deleteByProjectId
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import {
|
import {
|
||||||
SecretApprovalRequestsSecretsSchema,
|
SecretApprovalRequestsSecretsSchema,
|
||||||
|
SecretApprovalRequestsSecretsV2Schema,
|
||||||
TableName,
|
TableName,
|
||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
TSecretTags
|
TSecretTags
|
||||||
@ -15,6 +16,8 @@ export type TSecretApprovalRequestSecretDALFactory = ReturnType<typeof secretApp
|
|||||||
export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
||||||
const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret);
|
const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret);
|
||||||
const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag);
|
const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag);
|
||||||
|
const secretApprovalRequestSecretV2TagOrm = ormify(db, TableName.SecretApprovalRequestSecretTagV2);
|
||||||
|
const secretApprovalRequestSecretV2Orm = ormify(db, TableName.SecretApprovalRequestSecretV2);
|
||||||
|
|
||||||
const bulkUpdateNoVersionIncrement = async (data: TSecretApprovalRequestsSecrets[], tx?: Knex) => {
|
const bulkUpdateNoVersionIncrement = async (data: TSecretApprovalRequestsSecrets[], tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
@ -78,15 +81,13 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
.select({
|
.select({
|
||||||
secVerTagId: "secVerTag.id",
|
secVerTagId: "secVerTag.id",
|
||||||
secVerTagColor: "secVerTag.color",
|
secVerTagColor: "secVerTag.color",
|
||||||
secVerTagSlug: "secVerTag.slug",
|
secVerTagSlug: "secVerTag.slug"
|
||||||
secVerTagName: "secVerTag.name"
|
|
||||||
})
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
||||||
@ -121,9 +122,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagJnId",
|
key: "tagJnId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color
|
color
|
||||||
})
|
})
|
||||||
@ -197,11 +198,11 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "secVerTagId",
|
key: "secVerTagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ secVerTagId: id, secVerTagName: name, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
mapper: ({ secVerTagId: id, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
id,
|
id,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
name,
|
name: slug,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
slug,
|
slug,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@ -221,10 +222,195 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
throw new DatabaseError({ error, name: "FindByRequestId" });
|
throw new DatabaseError({ error, name: "FindByRequestId" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findByRequestIdBridgeSecretV2 = async (requestId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const doc = await (tx || db.replicaNode())({
|
||||||
|
secVerTag: TableName.SecretTag
|
||||||
|
})
|
||||||
|
.from(TableName.SecretApprovalRequestSecretV2)
|
||||||
|
.where({ requestId })
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretApprovalRequestSecretTagV2,
|
||||||
|
`${TableName.SecretApprovalRequestSecretV2}.id`,
|
||||||
|
`${TableName.SecretApprovalRequestSecretTagV2}.secretId`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretTag,
|
||||||
|
`${TableName.SecretApprovalRequestSecretTagV2}.tagId`,
|
||||||
|
`${TableName.SecretTag}.id`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.SecretV2, `${TableName.SecretApprovalRequestSecretV2}.secretId`, `${TableName.SecretV2}.id`)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretVersionV2,
|
||||||
|
`${TableName.SecretVersionV2}.id`,
|
||||||
|
`${TableName.SecretApprovalRequestSecretV2}.secretVersion`
|
||||||
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretVersionV2Tag,
|
||||||
|
`${TableName.SecretVersionV2Tag}.${TableName.SecretVersionV2}Id`,
|
||||||
|
`${TableName.SecretVersionV2}.id`
|
||||||
|
)
|
||||||
|
.leftJoin<TSecretTags>(
|
||||||
|
db.ref(TableName.SecretTag).as("secVerTag"),
|
||||||
|
`${TableName.SecretVersionV2Tag}.${TableName.SecretTag}Id`,
|
||||||
|
db.ref("id").withSchema("secVerTag")
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.SecretApprovalRequestSecretV2))
|
||||||
|
.select({
|
||||||
|
secVerTagId: "secVerTag.id",
|
||||||
|
secVerTagColor: "secVerTag.color",
|
||||||
|
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")
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
db.ref("version").withSchema(TableName.SecretV2).as("orgSecVersion"),
|
||||||
|
db.ref("key").withSchema(TableName.SecretV2).as("orgSecKey"),
|
||||||
|
db.ref("encryptedValue").withSchema(TableName.SecretV2).as("orgSecValue"),
|
||||||
|
db.ref("encryptedComment").withSchema(TableName.SecretV2).as("orgSecComment")
|
||||||
|
)
|
||||||
|
.select(
|
||||||
|
db.ref("version").withSchema(TableName.SecretVersionV2).as("secVerVersion"),
|
||||||
|
db.ref("key").withSchema(TableName.SecretVersionV2).as("secVerKey"),
|
||||||
|
db.ref("encryptedValue").withSchema(TableName.SecretVersionV2).as("secVerValue"),
|
||||||
|
db.ref("encryptedComment").withSchema(TableName.SecretVersionV2).as("secVerComment")
|
||||||
|
);
|
||||||
|
const formatedDoc = sqlNestRelationships({
|
||||||
|
data: doc,
|
||||||
|
key: "id",
|
||||||
|
parentMapper: (data) => SecretApprovalRequestsSecretsV2Schema.omit({ secretVersion: true }).parse(data),
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "tagJnId",
|
||||||
|
label: "tags" as const,
|
||||||
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||||
|
id,
|
||||||
|
name: slug,
|
||||||
|
slug,
|
||||||
|
color
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "secretId",
|
||||||
|
label: "secret" as const,
|
||||||
|
mapper: ({ orgSecVersion, orgSecKey, orgSecValue, orgSecComment, secretId }) =>
|
||||||
|
secretId
|
||||||
|
? {
|
||||||
|
id: secretId,
|
||||||
|
version: orgSecVersion,
|
||||||
|
key: orgSecKey,
|
||||||
|
encryptedValue: orgSecValue,
|
||||||
|
encryptedComment: orgSecComment
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "secretVersion",
|
||||||
|
label: "secretVersion" as const,
|
||||||
|
mapper: ({ secretVersion, secVerVersion, secVerKey, secVerValue, secVerComment }) =>
|
||||||
|
secretVersion
|
||||||
|
? {
|
||||||
|
version: secVerVersion,
|
||||||
|
id: secretVersion,
|
||||||
|
key: secVerKey,
|
||||||
|
encryptedValue: secVerValue,
|
||||||
|
encryptedComment: secVerComment
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "secVerTagId",
|
||||||
|
label: "tags" as const,
|
||||||
|
mapper: ({ secVerTagId: id, secVerTagSlug: slug, secVerTagColor: color }) => ({
|
||||||
|
// eslint-disable-next-line
|
||||||
|
id,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
name: slug,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
slug,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({
|
||||||
|
...el,
|
||||||
|
secret: secret?.[0],
|
||||||
|
secretVersion: secretVersion?.[0]
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
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 {
|
return {
|
||||||
...secretApprovalRequestSecretOrm,
|
...secretApprovalRequestSecretOrm,
|
||||||
|
insertV2Bridge: secretApprovalRequestSecretV2Orm.insertMany,
|
||||||
findByRequestId,
|
findByRequestId,
|
||||||
|
findByRequestIdBridgeSecretV2,
|
||||||
bulkUpdateNoVersionIncrement,
|
bulkUpdateNoVersionIncrement,
|
||||||
insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany
|
findByProjectId,
|
||||||
|
insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany,
|
||||||
|
insertApprovalSecretV2Tags: secretApprovalRequestSecretV2TagOrm.insertMany
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -5,20 +5,25 @@ import {
|
|||||||
SecretEncryptionAlgo,
|
SecretEncryptionAlgo,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
SecretType,
|
SecretType,
|
||||||
TSecretApprovalRequestsSecretsInsert
|
TSecretApprovalRequestsSecretsInsert,
|
||||||
|
TSecretApprovalRequestsSecretsV2Insert
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { groupBy, pick, unique } from "@app/lib/fn";
|
import { groupBy, pick, unique } from "@app/lib/fn";
|
||||||
|
import { setKnexStringValue } from "@app/lib/knex";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
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 { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import {
|
import {
|
||||||
|
decryptSecretWithBot,
|
||||||
fnSecretBlindIndexCheck,
|
fnSecretBlindIndexCheck,
|
||||||
fnSecretBlindIndexCheckV2,
|
fnSecretBlindIndexCheckV2,
|
||||||
fnSecretBulkDelete,
|
fnSecretBulkDelete,
|
||||||
@ -33,9 +38,19 @@ import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version
|
|||||||
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
import { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||||
|
import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
|
import {
|
||||||
|
fnSecretBulkDelete as fnSecretV2BridgeBulkDelete,
|
||||||
|
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
||||||
|
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
||||||
|
getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge
|
||||||
|
} 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 { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
@ -47,6 +62,7 @@ import {
|
|||||||
RequestState,
|
RequestState,
|
||||||
TApprovalRequestCountDTO,
|
TApprovalRequestCountDTO,
|
||||||
TGenerateSecretApprovalRequestDTO,
|
TGenerateSecretApprovalRequestDTO,
|
||||||
|
TGenerateSecretApprovalRequestV2BridgeDTO,
|
||||||
TListApprovalsDTO,
|
TListApprovalsDTO,
|
||||||
TMergeSecretApprovalRequestDTO,
|
TMergeSecretApprovalRequestDTO,
|
||||||
TReviewRequestDTO,
|
TReviewRequestDTO,
|
||||||
@ -62,16 +78,27 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory;
|
secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory;
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findSecretPathByFolderIds">;
|
folderDAL: Pick<TSecretFolderDALFactory, "findBySecretPath" | "findSecretPathByFolderIds">;
|
||||||
secretDAL: TSecretDALFactory;
|
secretDAL: TSecretDALFactory;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret">;
|
secretTagDAL: Pick<
|
||||||
|
TSecretTagDALFactory,
|
||||||
|
"findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "saveTagsToSecretV2" | "deleteTagsToSecretV2"
|
||||||
|
>;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findProjectById">;
|
|
||||||
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
userDAL: Pick<TUserDALFactory, "find" | "findOne">;
|
userDAL: Pick<TUserDALFactory, "find" | "findOne">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findById" | "findProjectById">;
|
||||||
|
secretQueueService: Pick<TSecretQueueFactory, "syncSecrets" | "removeSecretReminder">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
||||||
|
secretV2BridgeDAL: Pick<
|
||||||
|
TSecretV2BridgeDALFactory,
|
||||||
|
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||||
|
>;
|
||||||
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
export type TSecretApprovalRequestServiceFactory = ReturnType<typeof secretApprovalRequestServiceFactory>;
|
||||||
@ -93,7 +120,12 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
projectBotService,
|
projectBotService,
|
||||||
smtpService,
|
smtpService,
|
||||||
userDAL,
|
userDAL,
|
||||||
projectEnvDAL
|
projectEnvDAL,
|
||||||
|
kmsService,
|
||||||
|
secretV2BridgeDAL,
|
||||||
|
secretVersionV2BridgeDAL,
|
||||||
|
secretVersionTagV2BridgeDAL,
|
||||||
|
licenseService
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
@ -125,6 +157,19 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
||||||
|
|
||||||
|
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
|
if (shouldUseSecretV2Bridge) {
|
||||||
|
return secretApprovalRequestDAL.findByProjectIdBridgeSecretV2({
|
||||||
|
projectId,
|
||||||
|
committer,
|
||||||
|
environment,
|
||||||
|
status,
|
||||||
|
userId: actorId,
|
||||||
|
limit,
|
||||||
|
offset
|
||||||
|
});
|
||||||
|
}
|
||||||
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
const approvals = await secretApprovalRequestDAL.findByProjectId({
|
||||||
projectId,
|
projectId,
|
||||||
committer,
|
committer,
|
||||||
@ -149,11 +194,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
|
|
||||||
|
const { projectId } = secretApprovalRequest;
|
||||||
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
@ -165,7 +213,73 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new UnauthorizedError({ message: "User has no access" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const secrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
let secrets;
|
||||||
|
if (shouldUseSecretV2Bridge) {
|
||||||
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
||||||
|
secretApprovalRequest.id
|
||||||
|
);
|
||||||
|
secrets = encrypedSecrets.map((el) => ({
|
||||||
|
...el,
|
||||||
|
secretKey: el.key,
|
||||||
|
id: el.id,
|
||||||
|
version: el.version,
|
||||||
|
secretValue: el.encryptedValue ? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString() : "",
|
||||||
|
secretComment: el.encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||||
|
: "",
|
||||||
|
secret: el.secret
|
||||||
|
? {
|
||||||
|
secretKey: el.secret.key,
|
||||||
|
id: el.secret.id,
|
||||||
|
version: el.secret.version,
|
||||||
|
secretValue: el.secret.encryptedValue
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedValue }).toString()
|
||||||
|
: "",
|
||||||
|
secretComment: el.secret.encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: el.secret.encryptedComment }).toString()
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
secretVersion: el.secretVersion
|
||||||
|
? {
|
||||||
|
secretKey: el.secretVersion.key,
|
||||||
|
id: el.secretVersion.id,
|
||||||
|
version: el.secretVersion.version,
|
||||||
|
secretValue: el.secretVersion.encryptedValue
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedValue }).toString()
|
||||||
|
: "",
|
||||||
|
secretComment: el.secretVersion.encryptedComment
|
||||||
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
if (!botKey) throw new BadRequestError({ message: "Bot key not found" });
|
||||||
|
const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
|
secrets = encrypedSecrets.map((el) => ({
|
||||||
|
...el,
|
||||||
|
...decryptSecretWithBot(el, botKey),
|
||||||
|
secret: el.secret
|
||||||
|
? {
|
||||||
|
id: el.secret.id,
|
||||||
|
version: el.secret.version,
|
||||||
|
...decryptSecretWithBot(el.secret, botKey)
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
secretVersion: el.secretVersion
|
||||||
|
? {
|
||||||
|
id: el.secretVersion.id,
|
||||||
|
version: el.secretVersion.version,
|
||||||
|
...decryptSecretWithBot(el.secretVersion, botKey)
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}));
|
||||||
|
}
|
||||||
const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [
|
const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [
|
||||||
secretApprovalRequest.folderId
|
secretApprovalRequest.folderId
|
||||||
]);
|
]);
|
||||||
@ -184,6 +298,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
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 { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
@ -234,6 +356,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
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 { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
@ -275,6 +405,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
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 { policy, folderId, projectId } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
@ -300,48 +438,167 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretApprovalRequest.policy.approvers.filter(
|
secretApprovalRequest.policy.approvers.filter(
|
||||||
({ userId: approverId }) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
|
({ userId: approverId }) => reviewers[approverId.toString()] === ApprovalStatus.APPROVED
|
||||||
).length;
|
).length;
|
||||||
|
|
||||||
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
const isSoftEnforcement = secretApprovalRequest.policy.enforcementLevel === EnforcementLevel.Soft;
|
||||||
|
|
||||||
if (!hasMinApproval && !isSoftEnforcement)
|
if (!hasMinApproval && !isSoftEnforcement)
|
||||||
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||||
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
|
||||||
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
|
||||||
|
|
||||||
const conflicts: Array<{ secretId: string; op: SecretOperations }> = [];
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create);
|
let mergeStatus;
|
||||||
if (secretCreationCommits.length) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
// this cycle if for bridged secrets
|
||||||
folderId,
|
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
||||||
secretDAL,
|
secretApprovalRequest.id
|
||||||
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
|
|
||||||
if (!secretBlindIndex) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Missing secret blind index"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return { secretBlindIndex };
|
|
||||||
})
|
|
||||||
});
|
|
||||||
secretCreationCommits
|
|
||||||
.filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""])
|
|
||||||
.forEach((el) => {
|
|
||||||
conflicts.push({ op: SecretOperations.Create, secretId: el.id });
|
|
||||||
});
|
|
||||||
secretCreationCommits = secretCreationCommits.filter(
|
|
||||||
({ secretBlindIndex }) => !conflictGroupByBlindIndex[secretBlindIndex || ""]
|
|
||||||
);
|
);
|
||||||
}
|
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
||||||
|
|
||||||
let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Update);
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
if (secretUpdationCommits.length) {
|
type: KmsDataKey.SecretManager,
|
||||||
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
projectId
|
||||||
folderId,
|
});
|
||||||
secretDAL,
|
|
||||||
userId: "",
|
const conflicts: Array<{ secretId: string; op: SecretOperations }> = [];
|
||||||
inputSecrets: secretUpdationCommits
|
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create);
|
||||||
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
if (secretCreationCommits.length) {
|
||||||
.map(({ secretBlindIndex }) => {
|
const secrets = await secretV2BridgeDAL.findBySecretKeys(
|
||||||
|
folderId,
|
||||||
|
secretCreationCommits.map((el) => ({
|
||||||
|
key: el.key,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const creationConflictSecretsGroupByKey = groupBy(secrets, (i) => i.key);
|
||||||
|
secretCreationCommits
|
||||||
|
.filter(({ key }) => creationConflictSecretsGroupByKey[key])
|
||||||
|
.forEach((el) => {
|
||||||
|
conflicts.push({ op: SecretOperations.Create, secretId: el.id });
|
||||||
|
});
|
||||||
|
secretCreationCommits = secretCreationCommits.filter(({ key }) => !creationConflictSecretsGroupByKey[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Update);
|
||||||
|
if (secretUpdationCommits.length) {
|
||||||
|
const secrets = await secretV2BridgeDAL.findBySecretKeys(
|
||||||
|
folderId,
|
||||||
|
secretCreationCommits.map((el) => ({
|
||||||
|
key: el.key,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
const updationConflictSecretsGroupByKey = groupBy(secrets, (i) => i.key);
|
||||||
|
secretUpdationCommits
|
||||||
|
.filter(({ key, secretId }) => updationConflictSecretsGroupByKey[key] || !secretId)
|
||||||
|
.forEach((el) => {
|
||||||
|
conflicts.push({ op: SecretOperations.Update, secretId: el.id });
|
||||||
|
});
|
||||||
|
|
||||||
|
secretUpdationCommits = secretUpdationCommits.filter(
|
||||||
|
({ key, secretId }) => Boolean(secretId) && !updationConflictSecretsGroupByKey[key]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const secretDeletionCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Delete);
|
||||||
|
mergeStatus = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
|
const newSecrets = secretCreationCommits.length
|
||||||
|
? await fnSecretV2BridgeBulkInsert({
|
||||||
|
tx,
|
||||||
|
folderId,
|
||||||
|
inputSecrets: secretCreationCommits.map((el) => ({
|
||||||
|
tagIds: el?.tags.map(({ id }) => id),
|
||||||
|
version: 1,
|
||||||
|
encryptedComment: el.encryptedComment,
|
||||||
|
encryptedValue: el.encryptedValue,
|
||||||
|
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||||
|
key: el.key,
|
||||||
|
references: el.encryptedValue
|
||||||
|
? getAllNestedSecretReferencesV2Bridge(
|
||||||
|
secretManagerDecryptor({
|
||||||
|
cipherTextBlob: el.encryptedValue
|
||||||
|
}).toString()
|
||||||
|
)
|
||||||
|
: [],
|
||||||
|
type: SecretType.Shared
|
||||||
|
})),
|
||||||
|
secretDAL: secretV2BridgeDAL,
|
||||||
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL: secretVersionTagV2BridgeDAL
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
const updatedSecrets = secretUpdationCommits.length
|
||||||
|
? await fnSecretV2BridgeBulkUpdate({
|
||||||
|
folderId,
|
||||||
|
tx,
|
||||||
|
inputSecrets: secretUpdationCommits.map((el) => {
|
||||||
|
const encryptedValue =
|
||||||
|
typeof el.encryptedValue !== "undefined"
|
||||||
|
? {
|
||||||
|
encryptedValue: el.encryptedValue as Buffer,
|
||||||
|
references: el.encryptedValue
|
||||||
|
? getAllNestedSecretReferencesV2Bridge(
|
||||||
|
secretManagerDecryptor({
|
||||||
|
cipherTextBlob: el.encryptedValue
|
||||||
|
}).toString()
|
||||||
|
)
|
||||||
|
: []
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
return {
|
||||||
|
filter: { id: el.secretId as string, type: SecretType.Shared },
|
||||||
|
data: {
|
||||||
|
reminderRepeatDays: el.reminderRepeatDays,
|
||||||
|
encryptedComment: el.encryptedComment,
|
||||||
|
reminderNote: el.reminderNote,
|
||||||
|
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||||
|
key: el.key,
|
||||||
|
tagIds: el?.tags.map(({ id }) => id),
|
||||||
|
...encryptedValue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
secretDAL: secretV2BridgeDAL,
|
||||||
|
secretVersionDAL: secretVersionV2BridgeDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL: secretVersionTagV2BridgeDAL
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
const deletedSecret = secretDeletionCommits.length
|
||||||
|
? await fnSecretV2BridgeBulkDelete({
|
||||||
|
projectId,
|
||||||
|
folderId,
|
||||||
|
tx,
|
||||||
|
actorId: "",
|
||||||
|
secretDAL: secretV2BridgeDAL,
|
||||||
|
secretQueueService,
|
||||||
|
inputSecrets: secretDeletionCommits.map(({ key }) => ({ secretKey: key, type: SecretType.Shared }))
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
const updatedSecretApproval = await secretApprovalRequestDAL.updateById(
|
||||||
|
secretApprovalRequest.id,
|
||||||
|
{
|
||||||
|
conflicts: JSON.stringify(conflicts),
|
||||||
|
hasMerged: true,
|
||||||
|
status: RequestState.Closed,
|
||||||
|
statusChangedByUserId: actorId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
secrets: { created: newSecrets, updated: updatedSecrets, deleted: deletedSecret },
|
||||||
|
approval: updatedSecretApproval
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
|
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
||||||
|
|
||||||
|
const conflicts: Array<{ secretId: string; op: SecretOperations }> = [];
|
||||||
|
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create);
|
||||||
|
if (secretCreationCommits.length) {
|
||||||
|
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
||||||
|
folderId,
|
||||||
|
secretDAL,
|
||||||
|
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Missing secret blind index"
|
message: "Missing secret blind index"
|
||||||
@ -349,80 +606,56 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
return { secretBlindIndex };
|
return { secretBlindIndex };
|
||||||
})
|
})
|
||||||
});
|
|
||||||
secretUpdationCommits
|
|
||||||
.filter(
|
|
||||||
({ secretBlindIndex, secretId }) =>
|
|
||||||
(secretBlindIndex && conflictGroupByBlindIndex[secretBlindIndex]) || !secretId
|
|
||||||
)
|
|
||||||
.forEach((el) => {
|
|
||||||
conflicts.push({ op: SecretOperations.Update, secretId: el.id });
|
|
||||||
});
|
});
|
||||||
|
secretCreationCommits
|
||||||
|
.filter(({ secretBlindIndex }) => conflictGroupByBlindIndex[secretBlindIndex || ""])
|
||||||
|
.forEach((el) => {
|
||||||
|
conflicts.push({ op: SecretOperations.Create, secretId: el.id });
|
||||||
|
});
|
||||||
|
secretCreationCommits = secretCreationCommits.filter(
|
||||||
|
({ secretBlindIndex }) => !conflictGroupByBlindIndex[secretBlindIndex || ""]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
secretUpdationCommits = secretUpdationCommits.filter(
|
let secretUpdationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Update);
|
||||||
({ secretBlindIndex, secretId }) =>
|
if (secretUpdationCommits.length) {
|
||||||
Boolean(secretId) && (secretBlindIndex ? !conflictGroupByBlindIndex[secretBlindIndex] : true)
|
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
||||||
);
|
folderId,
|
||||||
}
|
secretDAL,
|
||||||
|
userId: "",
|
||||||
|
inputSecrets: secretUpdationCommits
|
||||||
|
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
||||||
|
.map(({ secretBlindIndex }) => {
|
||||||
|
if (!secretBlindIndex) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Missing secret blind index"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { secretBlindIndex };
|
||||||
|
})
|
||||||
|
});
|
||||||
|
secretUpdationCommits
|
||||||
|
.filter(
|
||||||
|
({ secretBlindIndex, secretId }) =>
|
||||||
|
(secretBlindIndex && conflictGroupByBlindIndex[secretBlindIndex]) || !secretId
|
||||||
|
)
|
||||||
|
.forEach((el) => {
|
||||||
|
conflicts.push({ op: SecretOperations.Update, secretId: el.id });
|
||||||
|
});
|
||||||
|
|
||||||
const secretDeletionCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Delete);
|
secretUpdationCommits = secretUpdationCommits.filter(
|
||||||
const botKey = await projectBotService.getBotKey(projectId).catch(() => null);
|
({ secretBlindIndex, secretId }) =>
|
||||||
const mergeStatus = await secretApprovalRequestDAL.transaction(async (tx) => {
|
Boolean(secretId) && (secretBlindIndex ? !conflictGroupByBlindIndex[secretBlindIndex] : true)
|
||||||
const newSecrets = secretCreationCommits.length
|
);
|
||||||
? await fnSecretBulkInsert({
|
}
|
||||||
tx,
|
|
||||||
folderId,
|
const secretDeletionCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Delete);
|
||||||
inputSecrets: secretCreationCommits.map((el) => ({
|
mergeStatus = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
...pick(el, [
|
const newSecrets = secretCreationCommits.length
|
||||||
"secretCommentCiphertext",
|
? await fnSecretBulkInsert({
|
||||||
"secretCommentTag",
|
tx,
|
||||||
"secretCommentIV",
|
folderId,
|
||||||
"secretValueIV",
|
inputSecrets: secretCreationCommits.map((el) => ({
|
||||||
"secretValueTag",
|
|
||||||
"secretValueCiphertext",
|
|
||||||
"secretKeyCiphertext",
|
|
||||||
"secretKeyTag",
|
|
||||||
"secretKeyIV",
|
|
||||||
"metadata",
|
|
||||||
"skipMultilineEncoding",
|
|
||||||
"secretReminderNote",
|
|
||||||
"secretReminderRepeatDays",
|
|
||||||
"algorithm",
|
|
||||||
"keyEncoding",
|
|
||||||
"secretBlindIndex"
|
|
||||||
]),
|
|
||||||
tags: el?.tags.map(({ id }) => id),
|
|
||||||
version: 1,
|
|
||||||
type: SecretType.Shared,
|
|
||||||
references: botKey
|
|
||||||
? getAllNestedSecretReferences(
|
|
||||||
decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: el.secretValueCiphertext,
|
|
||||||
iv: el.secretValueIV,
|
|
||||||
tag: el.secretValueTag,
|
|
||||||
key: botKey
|
|
||||||
})
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
})),
|
|
||||||
secretDAL,
|
|
||||||
secretVersionDAL,
|
|
||||||
secretTagDAL,
|
|
||||||
secretVersionTagDAL
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
const updatedSecrets = secretUpdationCommits.length
|
|
||||||
? await fnSecretBulkUpdate({
|
|
||||||
folderId,
|
|
||||||
projectId,
|
|
||||||
tx,
|
|
||||||
inputSecrets: secretUpdationCommits.map((el) => ({
|
|
||||||
filter: {
|
|
||||||
id: el.secretId as string, // this null check is already checked at top on conflict strategy
|
|
||||||
type: SecretType.Shared
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
tags: el?.tags.map(({ id }) => id),
|
|
||||||
...pick(el, [
|
...pick(el, [
|
||||||
"secretCommentCiphertext",
|
"secretCommentCiphertext",
|
||||||
"secretCommentTag",
|
"secretCommentTag",
|
||||||
@ -437,8 +670,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
"skipMultilineEncoding",
|
"skipMultilineEncoding",
|
||||||
"secretReminderNote",
|
"secretReminderNote",
|
||||||
"secretReminderRepeatDays",
|
"secretReminderRepeatDays",
|
||||||
|
"algorithm",
|
||||||
|
"keyEncoding",
|
||||||
"secretBlindIndex"
|
"secretBlindIndex"
|
||||||
]),
|
]),
|
||||||
|
tags: el?.tags.map(({ id }) => id),
|
||||||
|
version: 1,
|
||||||
|
type: SecretType.Shared,
|
||||||
references: botKey
|
references: botKey
|
||||||
? getAllNestedSecretReferences(
|
? getAllNestedSecretReferences(
|
||||||
decryptSymmetric128BitHexKeyUTF8({
|
decryptSymmetric128BitHexKeyUTF8({
|
||||||
@ -449,48 +687,94 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
})),
|
||||||
})),
|
secretDAL,
|
||||||
secretDAL,
|
secretVersionDAL,
|
||||||
secretVersionDAL,
|
secretTagDAL,
|
||||||
secretTagDAL,
|
secretVersionTagDAL
|
||||||
secretVersionTagDAL
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
const deletedSecret = secretDeletionCommits.length
|
|
||||||
? await fnSecretBulkDelete({
|
|
||||||
projectId,
|
|
||||||
folderId,
|
|
||||||
tx,
|
|
||||||
actorId: "",
|
|
||||||
secretDAL,
|
|
||||||
secretQueueService,
|
|
||||||
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
|
|
||||||
if (!secretBlindIndex) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Missing secret blind index"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return { secretBlindIndex, type: SecretType.Shared };
|
|
||||||
})
|
})
|
||||||
})
|
: [];
|
||||||
: [];
|
const updatedSecrets = secretUpdationCommits.length
|
||||||
const updatedSecretApproval = await secretApprovalRequestDAL.updateById(
|
? await fnSecretBulkUpdate({
|
||||||
secretApprovalRequest.id,
|
folderId,
|
||||||
{
|
projectId,
|
||||||
conflicts: JSON.stringify(conflicts),
|
tx,
|
||||||
hasMerged: true,
|
inputSecrets: secretUpdationCommits.map((el) => ({
|
||||||
status: RequestState.Closed,
|
filter: {
|
||||||
statusChangedByUserId: actorId,
|
id: el.secretId as string, // this null check is already checked at top on conflict strategy
|
||||||
bypassReason
|
type: SecretType.Shared
|
||||||
},
|
},
|
||||||
tx
|
data: {
|
||||||
);
|
tags: el?.tags.map(({ id }) => id),
|
||||||
return {
|
...pick(el, [
|
||||||
secrets: { created: newSecrets, updated: updatedSecrets, deleted: deletedSecret },
|
"secretCommentCiphertext",
|
||||||
approval: updatedSecretApproval
|
"secretCommentTag",
|
||||||
};
|
"secretCommentIV",
|
||||||
});
|
"secretValueIV",
|
||||||
|
"secretValueTag",
|
||||||
|
"secretValueCiphertext",
|
||||||
|
"secretKeyCiphertext",
|
||||||
|
"secretKeyTag",
|
||||||
|
"secretKeyIV",
|
||||||
|
"metadata",
|
||||||
|
"skipMultilineEncoding",
|
||||||
|
"secretReminderNote",
|
||||||
|
"secretReminderRepeatDays",
|
||||||
|
"secretBlindIndex"
|
||||||
|
]),
|
||||||
|
references: botKey
|
||||||
|
? getAllNestedSecretReferences(
|
||||||
|
decryptSymmetric128BitHexKeyUTF8({
|
||||||
|
ciphertext: el.secretValueCiphertext,
|
||||||
|
iv: el.secretValueIV,
|
||||||
|
tag: el.secretValueTag,
|
||||||
|
key: botKey
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
secretDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
const deletedSecret = secretDeletionCommits.length
|
||||||
|
? await fnSecretBulkDelete({
|
||||||
|
projectId,
|
||||||
|
folderId,
|
||||||
|
tx,
|
||||||
|
actorId: "",
|
||||||
|
secretDAL,
|
||||||
|
secretQueueService,
|
||||||
|
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
|
||||||
|
if (!secretBlindIndex) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Missing secret blind index"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return { secretBlindIndex, type: SecretType.Shared };
|
||||||
|
})
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
const updatedSecretApproval = await secretApprovalRequestDAL.updateById(
|
||||||
|
secretApprovalRequest.id,
|
||||||
|
{
|
||||||
|
conflicts: JSON.stringify(conflicts),
|
||||||
|
hasMerged: true,
|
||||||
|
status: RequestState.Closed,
|
||||||
|
statusChangedByUserId: actorId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
secrets: { created: newSecrets, updated: updatedSecrets, deleted: deletedSecret },
|
||||||
|
approval: updatedSecretApproval
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [folderId]);
|
const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [folderId]);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
||||||
@ -779,8 +1063,262 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return secretApprovalRequest;
|
return secretApprovalRequest;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const generateSecretApprovalRequestV2Bridge = async ({
|
||||||
|
data,
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
policy,
|
||||||
|
projectId,
|
||||||
|
secretPath,
|
||||||
|
environment
|
||||||
|
}: TGenerateSecretApprovalRequestV2BridgeDTO) => {
|
||||||
|
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
||||||
|
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
);
|
||||||
|
|
||||||
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
|
if (!folder)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Folder not found for the given environment slug & secret path",
|
||||||
|
name: "GenSecretApproval"
|
||||||
|
});
|
||||||
|
const folderId = folder.id;
|
||||||
|
|
||||||
|
const commits: Omit<TSecretApprovalRequestsSecretsV2Insert, "requestId">[] = [];
|
||||||
|
const commitTagIds: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
const { encryptor: secretManagerEncryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.SecretManager,
|
||||||
|
projectId
|
||||||
|
});
|
||||||
|
|
||||||
|
// for created secret approval change
|
||||||
|
const createdSecrets = data[SecretOperations.Create];
|
||||||
|
if (createdSecrets && createdSecrets?.length) {
|
||||||
|
const secrets = await secretV2BridgeDAL.findBySecretKeys(
|
||||||
|
folderId,
|
||||||
|
createdSecrets.map((el) => ({
|
||||||
|
key: el.secretKey,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
if (secrets.length)
|
||||||
|
throw new BadRequestError({ message: `Secret already exist: ${secrets.map((el) => el.key).join(",")}` });
|
||||||
|
|
||||||
|
commits.push(
|
||||||
|
...createdSecrets.map((createdSecret) => ({
|
||||||
|
op: SecretOperations.Create,
|
||||||
|
version: 1,
|
||||||
|
encryptedComment: setKnexStringValue(
|
||||||
|
createdSecret.secretComment,
|
||||||
|
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||||
|
),
|
||||||
|
encryptedValue: setKnexStringValue(
|
||||||
|
createdSecret.secretValue,
|
||||||
|
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||||
|
),
|
||||||
|
skipMultilineEncoding: createdSecret.skipMultilineEncoding,
|
||||||
|
key: createdSecret.secretKey,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
createdSecrets.forEach(({ tagIds, secretKey }) => {
|
||||||
|
if (tagIds?.length) commitTagIds[secretKey] = tagIds;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// not secret approval for update operations
|
||||||
|
const secretsToUpdate = data[SecretOperations.Update];
|
||||||
|
if (secretsToUpdate && secretsToUpdate?.length) {
|
||||||
|
const secretsToUpdateStoredInDB = await secretV2BridgeDAL.findBySecretKeys(
|
||||||
|
folderId,
|
||||||
|
secretsToUpdate.map((el) => ({
|
||||||
|
key: el.secretKey,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
if (secretsToUpdateStoredInDB.length !== secretsToUpdate.length)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Secret not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
||||||
|
});
|
||||||
|
|
||||||
|
// now find any secret that needs to update its name
|
||||||
|
// same process as above
|
||||||
|
const secretsWithNewName = secretsToUpdate.filter(({ newSecretName }) => Boolean(newSecretName));
|
||||||
|
if (secretsWithNewName.length) {
|
||||||
|
const secrets = await secretV2BridgeDAL.findBySecretKeys(
|
||||||
|
folderId,
|
||||||
|
secretsWithNewName.map((el) => ({
|
||||||
|
key: el.secretKey,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
if (secrets.length)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Secret not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatingSecretsGroupByKey = groupBy(secretsToUpdateStoredInDB, (el) => el.key);
|
||||||
|
const latestSecretVersions = await secretVersionV2BridgeDAL.findLatestVersionMany(
|
||||||
|
folderId,
|
||||||
|
secretsToUpdateStoredInDB.map(({ id }) => id)
|
||||||
|
);
|
||||||
|
commits.push(
|
||||||
|
...secretsToUpdate.map(
|
||||||
|
({
|
||||||
|
newSecretName,
|
||||||
|
secretKey,
|
||||||
|
tagIds,
|
||||||
|
secretValue,
|
||||||
|
reminderRepeatDays,
|
||||||
|
reminderNote,
|
||||||
|
secretComment,
|
||||||
|
metadata,
|
||||||
|
skipMultilineEncoding
|
||||||
|
}) => {
|
||||||
|
const secretId = updatingSecretsGroupByKey[secretKey][0].id;
|
||||||
|
if (tagIds?.length) commitTagIds[secretKey] = tagIds;
|
||||||
|
return {
|
||||||
|
...latestSecretVersions[secretId],
|
||||||
|
key: newSecretName || secretKey,
|
||||||
|
encryptedComment: setKnexStringValue(
|
||||||
|
secretComment,
|
||||||
|
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||||
|
),
|
||||||
|
encryptedValue: setKnexStringValue(
|
||||||
|
secretValue,
|
||||||
|
(value) => secretManagerEncryptor({ plainText: Buffer.from(value) }).cipherTextBlob
|
||||||
|
),
|
||||||
|
reminderRepeatDays,
|
||||||
|
reminderNote,
|
||||||
|
metadata,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
op: SecretOperations.Update as const,
|
||||||
|
secret: secretId,
|
||||||
|
secretVersion: latestSecretVersions[secretId].id,
|
||||||
|
version: updatingSecretsGroupByKey[secretKey][0].version || 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// deleted secrets
|
||||||
|
const deletedSecrets = data[SecretOperations.Delete];
|
||||||
|
if (deletedSecrets && deletedSecrets.length) {
|
||||||
|
const secretsToDeleteInDB = await secretV2BridgeDAL.findBySecretKeys(
|
||||||
|
folderId,
|
||||||
|
deletedSecrets.map((el) => ({
|
||||||
|
key: el.secretKey,
|
||||||
|
type: SecretType.Shared
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
if (secretsToDeleteInDB.length !== deletedSecrets.length)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Secret not exist: ${secretsToDeleteInDB.map((el) => el.key).join(",")}`
|
||||||
|
});
|
||||||
|
const secretsGroupedByKey = groupBy(secretsToDeleteInDB, (i) => i.key);
|
||||||
|
const deletedSecretIds = deletedSecrets.map((el) => secretsGroupedByKey[el.secretKey][0].id);
|
||||||
|
const latestSecretVersions = await secretVersionV2BridgeDAL.findLatestVersionMany(folderId, deletedSecretIds);
|
||||||
|
commits.push(
|
||||||
|
...deletedSecrets.map(({ secretKey }) => {
|
||||||
|
const secretId = secretsGroupedByKey[secretKey][0].id;
|
||||||
|
return {
|
||||||
|
op: SecretOperations.Delete as const,
|
||||||
|
...latestSecretVersions[secretId],
|
||||||
|
key: secretKey,
|
||||||
|
secret: secretId,
|
||||||
|
secretVersion: latestSecretVersions[secretId].id
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commits.length) throw new BadRequestError({ message: "Empty commits" });
|
||||||
|
|
||||||
|
const tagIds = unique(Object.values(commitTagIds).flat());
|
||||||
|
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
|
||||||
|
if (tagIds.length !== tags.length) throw new BadRequestError({ message: "Tag not found" });
|
||||||
|
|
||||||
|
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
|
const doc = await secretApprovalRequestDAL.create(
|
||||||
|
{
|
||||||
|
folderId,
|
||||||
|
slug: alphaNumericNanoId(),
|
||||||
|
policyId: policy.id,
|
||||||
|
status: "open",
|
||||||
|
hasMerged: false,
|
||||||
|
committerUserId: actorId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
const approvalCommits = await secretApprovalRequestSecretDAL.insertV2Bridge(
|
||||||
|
commits.map(
|
||||||
|
({
|
||||||
|
version,
|
||||||
|
op,
|
||||||
|
key,
|
||||||
|
encryptedComment,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
metadata,
|
||||||
|
reminderNote,
|
||||||
|
reminderRepeatDays,
|
||||||
|
encryptedValue,
|
||||||
|
secretId,
|
||||||
|
secretVersion
|
||||||
|
}) => ({
|
||||||
|
version,
|
||||||
|
requestId: doc.id,
|
||||||
|
op,
|
||||||
|
secretId,
|
||||||
|
metadata,
|
||||||
|
secretVersion,
|
||||||
|
skipMultilineEncoding,
|
||||||
|
encryptedValue,
|
||||||
|
reminderRepeatDays,
|
||||||
|
reminderNote,
|
||||||
|
encryptedComment,
|
||||||
|
key
|
||||||
|
})
|
||||||
|
),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
const commitsGroupByKey = groupBy(approvalCommits, (i) => i.key);
|
||||||
|
if (tagIds.length) {
|
||||||
|
await secretApprovalRequestSecretDAL.insertApprovalSecretV2Tags(
|
||||||
|
Object.keys(commitTagIds).flatMap((blindIndex) =>
|
||||||
|
commitTagIds[blindIndex]
|
||||||
|
? commitTagIds[blindIndex].map((tagId) => ({
|
||||||
|
secretId: commitsGroupByKey[blindIndex][0].id,
|
||||||
|
tagId
|
||||||
|
}))
|
||||||
|
: []
|
||||||
|
),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { ...doc, commits: approvalCommits };
|
||||||
|
});
|
||||||
|
return secretApprovalRequest;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generateSecretApprovalRequest,
|
generateSecretApprovalRequest,
|
||||||
|
generateSecretApprovalRequestV2Bridge,
|
||||||
mergeSecretApprovalRequest,
|
mergeSecretApprovalRequest,
|
||||||
reviewApproval,
|
reviewApproval,
|
||||||
updateApprovalStatus,
|
updateApprovalStatus,
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user