mirror of
https://github.com/Infisical/infisical.git
synced 2025-06-29 04:31:59 +00:00
Compare commits
265 Commits
help-fix-f
...
misc/add-s
Author | SHA1 | Date | |
---|---|---|---|
f5238598aa | |||
982aa80092 | |||
f85efdc6f8 | |||
8680c52412 | |||
0ad3c67f82 | |||
f75fff0565 | |||
1fa1d0a15a | |||
8d6712aa58 | |||
a767870ad6 | |||
a0c432628a | |||
08a74a63b5 | |||
8329240822 | |||
ec3cbb9460 | |||
f167ba0fb8 | |||
f291aa1c01 | |||
72131373ec | |||
16c48de031 | |||
436a5afab5 | |||
9445f717f4 | |||
251e83a3fb | |||
66df285245 | |||
73fe2659b5 | |||
091f02d1cd | |||
66140dc151 | |||
a8c54d27ef | |||
9ac4453523 | |||
a6a9c2404d | |||
e5352e7aa8 | |||
c52180c890 | |||
20f0eeed35 | |||
7581300a67 | |||
7d90d183fb | |||
f27d4ee973 | |||
7473e3e21e | |||
6720217cee | |||
f385386a4b | |||
62a0d6e614 | |||
8c64c731f9 | |||
d51f6ca4fd | |||
5abcbe36ca | |||
7a13c27055 | |||
e7ac783b10 | |||
0a509e5033 | |||
d0c01755fe | |||
41e65775ab | |||
e3f4a2e604 | |||
f6e6bdb691 | |||
819a021e9c | |||
80113c2cea | |||
1f1fb3f3d1 | |||
d35331b0a8 | |||
ff6d94cbd0 | |||
01ef498397 | |||
59ac14380a | |||
7b5c86f4ef | |||
a745be2546 | |||
02f311515c | |||
e8cb3f8b4a | |||
4c8063c532 | |||
6a9b2d3d48 | |||
0a39e138a1 | |||
0dce2045ec | |||
b4c118d246 | |||
90e675de1e | |||
741e0ec78f | |||
3f654e115d | |||
1921346b4f | |||
76c95ace63 | |||
f4ae40cb86 | |||
b790dbb36f | |||
14449b8b41 | |||
489bd124d2 | |||
bcdcaa33a4 | |||
e8a8542757 | |||
e61d35d824 | |||
714d6831bd | |||
956f75eb43 | |||
73902c3ad6 | |||
da792d144d | |||
f7b09f5fc2 | |||
bfee34f38d | |||
840b64a049 | |||
c2612f242c | |||
092b89c59e | |||
3d76ae3399 | |||
23aa97feff | |||
0c5155f8e6 | |||
796d6bfc85 | |||
4afe2f2377 | |||
6eaa16bd07 | |||
1e07c2fe23 | |||
149f98a1b7 | |||
14745b560c | |||
dcfa0a2386 | |||
199339ac32 | |||
2aeb02b74a | |||
fe75627ab7 | |||
191486519f | |||
cab8fb0d8e | |||
8bfd728ce4 | |||
c9eab0af18 | |||
d7dfc531fc | |||
a89bd08c08 | |||
4bfb9e8e74 | |||
da5f054a65 | |||
9b13619efa | |||
c076a900dc | |||
8a5279cf0d | |||
d45c29cd23 | |||
77fe2ffb3b | |||
edf4e75e55 | |||
de917a5d74 | |||
46f9927cf1 | |||
92508d19e6 | |||
a73c0c05af | |||
c12bfa766c | |||
3432a16d4f | |||
19a403f467 | |||
7a00ade119 | |||
35127db635 | |||
1b9eecc8f4 | |||
f0b8c1537c | |||
4e60cff4bd | |||
ed1100bc90 | |||
dabe7e42ec | |||
c8ca6710ba | |||
7adac40756 | |||
400dc75656 | |||
4ecb2eb383 | |||
23a7c1b8cc | |||
e51278c276 | |||
c014c12ecb | |||
097b04afee | |||
f304024235 | |||
63ccfc40ac | |||
5311daed64 | |||
d5e9ac82d0 | |||
b43ecef112 | |||
f9c012387c | |||
5b51ab3216 | |||
b26e56c97e | |||
7cced29c74 | |||
06a7e804eb | |||
0f00474243 | |||
3df010f266 | |||
333ce9d164 | |||
9621df4f8b | |||
3f2de2c5ef | |||
b2b1c13393 | |||
ee98992d9e | |||
1fb0c638d6 | |||
c1ad49a532 | |||
d1fcc739c9 | |||
8c0287681b | |||
c7458d94aa | |||
93570df318 | |||
e798b4a7ba | |||
36c93f47d9 | |||
dbbcb157ef | |||
d5f0b4dad9 | |||
bdc23d22e7 | |||
0fd1b1c9d7 | |||
79df946f02 | |||
da2fa7f3ca | |||
08c1740afc | |||
3cac4ef927 | |||
2667f8f0f2 | |||
b39537472b | |||
6b60b2562d | |||
c2a7827080 | |||
64e09b0dcd | |||
a7176d44dd | |||
09d4cdc634 | |||
547ef17c10 | |||
841408042e | |||
e5fb1ac808 | |||
8a93c0bd59 | |||
c0f8f50981 | |||
fec47ef81c | |||
348f4b9787 | |||
aa577b095c | |||
f515cc83d7 | |||
17bbdbe7bb | |||
427de068d5 | |||
dbf7ecc9b6 | |||
1ef9885062 | |||
de48c3e161 | |||
852664e2cb | |||
fbc8264732 | |||
4303547d8c | |||
f1c8a66d31 | |||
baa05714ab | |||
0c21c19c95 | |||
c487614c38 | |||
a55c8cacea | |||
62308fb0a3 | |||
55aa1e87c0 | |||
c5c7adbc42 | |||
f686882ce6 | |||
e35417e11b | |||
ff0f4cf46a | |||
2d4476f99c | |||
81df491d5e | |||
d2c5603664 | |||
096930cb8f | |||
f9c00cf442 | |||
d32b6ad41d | |||
53968e07d0 | |||
64093e9175 | |||
c315eed4d4 | |||
78fd852588 | |||
0c1f761a9a | |||
c363f485eb | |||
433d83641d | |||
35bb7f299c | |||
160e2b773b | |||
f0a70e23ac | |||
a6271a6187 | |||
b2fbec740f | |||
86e5f46d89 | |||
720789025c | |||
811b3d5934 | |||
cac702415f | |||
dbe7acdc80 | |||
b33985b338 | |||
670376336e | |||
c59eddb00a | |||
fe40ba497b | |||
c5b7e3d8be | |||
47e778a0b8 | |||
8b443e0957 | |||
f7fb015bd8 | |||
0d7cd357c3 | |||
e40f65836f | |||
2be56f6a70 | |||
1ff1f3fad3 | |||
0ae96dfff4 | |||
8ad6488bd9 | |||
e264b68b7e | |||
9e881534ec | |||
2832ff5c76 | |||
4c6cca0864 | |||
c06bbf0b9b | |||
69392a4a51 | |||
130f1a167e | |||
8ab710817d | |||
ca39e75434 | |||
265b25a4c6 | |||
54f6e0b5c6 | |||
f2cdefaeec | |||
2d588d87ac | |||
5ee2eb1aa2 | |||
ff5f66a75f | |||
bf72638600 | |||
d9bc4da6f1 | |||
7f8d5ec11a | |||
141d0ede2d | |||
ab78a79415 | |||
8fa6af9ba4 | |||
f0a2845637 | |||
8ffc88ba28 | |||
05d132a1bb | |||
bd7c4fc4eb | |||
45c84d4936 | |||
8e8e2e0dfe |
@ -3,7 +3,62 @@ name: Release Infisical Core Helm chart
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
test-helm:
|
||||
name: Test Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
with:
|
||||
version: v3.17.0
|
||||
|
||||
- uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
check-latest: true
|
||||
|
||||
- name: Add Helm repositories
|
||||
run: |
|
||||
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||
helm repo update
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.7.0
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config ct.yaml --charts helm-charts/infisical-standalone-postgres
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
|
||||
- name: Create namespace
|
||||
run: kubectl create namespace infisical-standalone-postgres
|
||||
|
||||
- name: Create Infisical secrets
|
||||
run: |
|
||||
kubectl create secret generic infisical-secrets \
|
||||
--namespace infisical-standalone-postgres \
|
||||
--from-literal=AUTH_SECRET=6c1fe4e407b8911c104518103505b218 \
|
||||
--from-literal=ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 \
|
||||
--from-literal=SITE_URL=http://localhost:8080
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: |
|
||||
ct install \
|
||||
--config ct.yaml \
|
||||
--charts helm-charts/infisical-standalone-postgres \
|
||||
--helm-extra-args="--timeout=300s" \
|
||||
--helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \
|
||||
--namespace infisical-standalone-postgres
|
||||
|
||||
release:
|
||||
needs: test-helm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -19,4 +74,4 @@ jobs:
|
||||
- name: Build and push helm package to Cloudsmith
|
||||
run: cd helm-charts && sh upload-infisical-core-helm-cloudsmith.sh
|
||||
env:
|
||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
|
70
.github/workflows/release-k8-operator-helm.yml
vendored
70
.github/workflows/release-k8-operator-helm.yml
vendored
@ -1,27 +1,59 @@
|
||||
name: Release K8 Operator Helm Chart
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release-helm:
|
||||
name: Release Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
test-helm:
|
||||
name: Test Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: v3.10.0
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
with:
|
||||
version: v3.17.0
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
check-latest: true
|
||||
|
||||
- name: Install Cloudsmith CLI
|
||||
run: pip install --upgrade cloudsmith-cli
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.7.0
|
||||
|
||||
- name: Build and push helm package to CloudSmith
|
||||
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
|
||||
env:
|
||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config ct.yaml --charts helm-charts/secrets-operator
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: ct install --config ct.yaml --charts helm-charts/secrets-operator
|
||||
|
||||
release-helm:
|
||||
name: Release Helm Chart
|
||||
needs: test-helm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: v3.10.0
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
|
||||
- name: Install Cloudsmith CLI
|
||||
run: pip install --upgrade cloudsmith-cli
|
||||
|
||||
- name: Build and push helm package to CloudSmith
|
||||
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
|
||||
env:
|
||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
|
81
.github/workflows/release_helm_gateway.yaml
vendored
81
.github/workflows/release_helm_gateway.yaml
vendored
@ -1,27 +1,70 @@
|
||||
name: Release Gateway Helm Chart
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
release-helm:
|
||||
name: Release Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
test-helm:
|
||||
name: Test Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: v3.10.0
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
with:
|
||||
version: v3.17.0
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
- uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
check-latest: true
|
||||
|
||||
- name: Install Cloudsmith CLI
|
||||
run: pip install --upgrade cloudsmith-cli
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.7.0
|
||||
|
||||
- name: Build and push helm package to CloudSmith
|
||||
run: cd helm-charts && sh upload-gateway-cloudsmith.sh
|
||||
env:
|
||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config ct.yaml --charts helm-charts/infisical-gateway
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
|
||||
- name: Create namespace
|
||||
run: kubectl create namespace infisical-gateway
|
||||
|
||||
- name: Create gateway secret
|
||||
run: kubectl create secret generic infisical-gateway-environment --from-literal=TOKEN=my-test-token -n infisical-gateway
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: |
|
||||
ct install \
|
||||
--config ct.yaml \
|
||||
--charts helm-charts/infisical-gateway \
|
||||
--helm-extra-args="--timeout=300s" \
|
||||
--namespace infisical-gateway
|
||||
|
||||
release-helm:
|
||||
name: Release Helm Chart
|
||||
needs: test-helm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: v3.10.0
|
||||
|
||||
- name: Install python
|
||||
uses: actions/setup-python@v4
|
||||
|
||||
- name: Install Cloudsmith CLI
|
||||
run: pip install --upgrade cloudsmith-cli
|
||||
|
||||
- name: Build and push helm package to CloudSmith
|
||||
run: cd helm-charts && sh upload-gateway-cloudsmith.sh
|
||||
env:
|
||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||
|
49
.github/workflows/run-helm-chart-tests-infisical-gateway.yml
vendored
Normal file
49
.github/workflows/run-helm-chart-tests-infisical-gateway.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: Run Helm Chart Tests for Gateway
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "helm-charts/infisical-gateway/**"
|
||||
- ".github/workflows/run-helm-chart-tests-infisical-gateway.yml"
|
||||
|
||||
jobs:
|
||||
test-helm:
|
||||
name: Test Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
with:
|
||||
version: v3.17.0
|
||||
|
||||
- uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
check-latest: true
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.7.0
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config ct.yaml --charts helm-charts/infisical-gateway
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
|
||||
- name: Create namespace
|
||||
run: kubectl create namespace infisical-gateway
|
||||
|
||||
- name: Create gateway secret
|
||||
run: kubectl create secret generic infisical-gateway-environment --from-literal=TOKEN=my-test-token -n infisical-gateway
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: |
|
||||
ct install \
|
||||
--config ct.yaml \
|
||||
--charts helm-charts/infisical-gateway \
|
||||
--helm-extra-args="--timeout=300s" \
|
||||
--namespace infisical-gateway
|
61
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml
vendored
Normal file
61
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
name: Run Helm Chart Tests for Infisical Standalone Postgres
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "helm-charts/infisical-standalone-postgres/**"
|
||||
- ".github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml"
|
||||
|
||||
jobs:
|
||||
test-helm:
|
||||
name: Test Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
with:
|
||||
version: v3.17.0
|
||||
|
||||
- uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
check-latest: true
|
||||
|
||||
- name: Add Helm repositories
|
||||
run: |
|
||||
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
|
||||
helm repo add bitnami https://charts.bitnami.com/bitnami
|
||||
helm repo update
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.7.0
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config ct.yaml --charts helm-charts/infisical-standalone-postgres
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
|
||||
- name: Create namespace
|
||||
run: kubectl create namespace infisical-standalone-postgres
|
||||
|
||||
- name: Create Infisical secrets
|
||||
run: |
|
||||
kubectl create secret generic infisical-secrets \
|
||||
--namespace infisical-standalone-postgres \
|
||||
--from-literal=AUTH_SECRET=6c1fe4e407b8911c104518103505b218 \
|
||||
--from-literal=ENCRYPTION_KEY=6c1fe4e407b8911c104518103505b218 \
|
||||
--from-literal=SITE_URL=http://localhost:8080
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: |
|
||||
ct install \
|
||||
--config ct.yaml \
|
||||
--charts helm-charts/infisical-standalone-postgres \
|
||||
--helm-extra-args="--timeout=300s" \
|
||||
--helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \
|
||||
--namespace infisical-standalone-postgres
|
38
.github/workflows/run-helm-chart-tests-secret-operator.yml
vendored
Normal file
38
.github/workflows/run-helm-chart-tests-secret-operator.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
name: Run Helm Chart Tests for Secret Operator
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "helm-charts/secrets-operator/**"
|
||||
- ".github/workflows/run-helm-chart-tests-secret-operator.yml"
|
||||
|
||||
jobs:
|
||||
test-helm:
|
||||
name: Test Helm Chart
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Helm
|
||||
uses: azure/setup-helm@v4.2.0
|
||||
with:
|
||||
version: v3.17.0
|
||||
|
||||
- uses: actions/setup-python@v5.3.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
check-latest: true
|
||||
|
||||
- name: Set up chart-testing
|
||||
uses: helm/chart-testing-action@v2.7.0
|
||||
|
||||
- name: Run chart-testing (lint)
|
||||
run: ct lint --config ct.yaml --charts helm-charts/secrets-operator
|
||||
|
||||
- name: Create kind cluster
|
||||
uses: helm/kind-action@v1.12.0
|
||||
|
||||
- name: Run chart-testing (install)
|
||||
run: ct install --config ct.yaml --charts helm-charts/secrets-operator
|
@ -40,4 +40,8 @@ cli/detect/config/gitleaks.toml:gcp-api-key:578
|
||||
cli/detect/config/gitleaks.toml:gcp-api-key:579
|
||||
cli/detect/config/gitleaks.toml:gcp-api-key:581
|
||||
cli/detect/config/gitleaks.toml:gcp-api-key:582
|
||||
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml:generic-api-key:51
|
||||
.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml:generic-api-key:50
|
||||
.github/workflows/helm-release-infisical-core.yml:generic-api-key:48
|
||||
.github/workflows/helm-release-infisical-core.yml:generic-api-key:47
|
||||
backend/src/services/smtp/smtp-service.ts:generic-api-key:79
|
||||
|
30
backend/src/@types/fastify.d.ts
vendored
30
backend/src/@types/fastify.d.ts
vendored
@ -3,13 +3,12 @@ import "fastify";
|
||||
import { Redis } from "ioredis";
|
||||
|
||||
import { TUsers } from "@app/db/schemas";
|
||||
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
|
||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
||||
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-service";
|
||||
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-types";
|
||||
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-types";
|
||||
import { TAuditLogServiceFactory, TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-types";
|
||||
import { TCertificateAuthorityCrlServiceFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-types";
|
||||
import { TCertificateEstServiceFactory } from "@app/ee/services/certificate-est/certificate-est-service";
|
||||
import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dynamic-secret-service";
|
||||
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
||||
@ -25,14 +24,13 @@ import { TKmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
|
||||
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { TPitServiceFactory } from "@app/ee/services/pit/pit-service";
|
||||
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-service";
|
||||
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-service";
|
||||
import { TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-service";
|
||||
import { RateLimitConfiguration } from "@app/ee/services/rate-limit/rate-limit-types";
|
||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-service";
|
||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-service";
|
||||
import { TProjectTemplateServiceFactory } from "@app/ee/services/project-template/project-template-types";
|
||||
import { TProjectUserAdditionalPrivilegeServiceFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||
import { RateLimitConfiguration, TRateLimitServiceFactory } from "@app/ee/services/rate-limit/rate-limit-types";
|
||||
import { TSamlConfigServiceFactory } from "@app/ee/services/saml-config/saml-config-types";
|
||||
import { TScimServiceFactory } from "@app/ee/services/scim/scim-types";
|
||||
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
|
||||
import { TSecretApprovalRequestServiceFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-service";
|
||||
import { TSecretRotationServiceFactory } from "@app/ee/services/secret-rotation/secret-rotation-service";
|
||||
@ -44,7 +42,7 @@ import { TSshCertificateAuthorityServiceFactory } from "@app/ee/services/ssh/ssh
|
||||
import { TSshCertificateTemplateServiceFactory } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-service";
|
||||
import { TSshHostServiceFactory } from "@app/ee/services/ssh-host/ssh-host-service";
|
||||
import { TSshHostGroupServiceFactory } from "@app/ee/services/ssh-host-group/ssh-host-group-service";
|
||||
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
||||
import { TTrustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-types";
|
||||
import { TAuthMode } from "@app/server/plugins/auth/inject-identity";
|
||||
import { TApiKeyServiceFactory } from "@app/services/api-key/api-key-service";
|
||||
import { TAppConnectionServiceFactory } from "@app/services/app-connection/app-connection-service";
|
||||
@ -65,6 +63,7 @@ import { TGroupProjectServiceFactory } from "@app/services/group-project/group-p
|
||||
import { THsmServiceFactory } from "@app/services/hsm/hsm-service";
|
||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||
import { TIdentityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
|
||||
import { TIdentityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
||||
import { TIdentityAzureAuthServiceFactory } from "@app/services/identity-azure-auth/identity-azure-auth-service";
|
||||
import { TIdentityGcpAuthServiceFactory } from "@app/services/identity-gcp-auth/identity-gcp-auth-service";
|
||||
@ -218,6 +217,7 @@ declare module "fastify" {
|
||||
identityUa: TIdentityUaServiceFactory;
|
||||
identityKubernetesAuth: TIdentityKubernetesAuthServiceFactory;
|
||||
identityGcpAuth: TIdentityGcpAuthServiceFactory;
|
||||
identityAliCloudAuth: TIdentityAliCloudAuthServiceFactory;
|
||||
identityAwsAuth: TIdentityAwsAuthServiceFactory;
|
||||
identityAzureAuth: TIdentityAzureAuthServiceFactory;
|
||||
identityOciAuth: TIdentityOciAuthServiceFactory;
|
||||
|
8
backend/src/@types/knex.d.ts
vendored
8
backend/src/@types/knex.d.ts
vendored
@ -125,6 +125,9 @@ import {
|
||||
TIdentityAccessTokens,
|
||||
TIdentityAccessTokensInsert,
|
||||
TIdentityAccessTokensUpdate,
|
||||
TIdentityAlicloudAuths,
|
||||
TIdentityAlicloudAuthsInsert,
|
||||
TIdentityAlicloudAuthsUpdate,
|
||||
TIdentityAwsAuths,
|
||||
TIdentityAwsAuthsInsert,
|
||||
TIdentityAwsAuthsUpdate,
|
||||
@ -786,6 +789,11 @@ declare module "knex/types/tables" {
|
||||
TIdentityGcpAuthsInsert,
|
||||
TIdentityGcpAuthsUpdate
|
||||
>;
|
||||
[TableName.IdentityAliCloudAuth]: KnexOriginal.CompositeTableType<
|
||||
TIdentityAlicloudAuths,
|
||||
TIdentityAlicloudAuthsInsert,
|
||||
TIdentityAlicloudAuthsUpdate
|
||||
>;
|
||||
[TableName.IdentityAwsAuth]: KnexOriginal.CompositeTableType<
|
||||
TIdentityAwsAuths,
|
||||
TIdentityAwsAuthsInsert,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import knex, { Knex } from "knex";
|
||||
|
||||
export type TDbClient = ReturnType<typeof initDbConnection>;
|
||||
export type TDbClient = Knex;
|
||||
export const initDbConnection = ({
|
||||
dbConnectionUri,
|
||||
dbRootCert,
|
||||
@ -50,6 +50,8 @@ export const initDbConnection = ({
|
||||
}
|
||||
: false
|
||||
},
|
||||
// https://knexjs.org/guide/#pool
|
||||
pool: { min: 0, max: 10 },
|
||||
migrations: {
|
||||
tableName: "infisical_migrations"
|
||||
}
|
||||
@ -70,7 +72,8 @@ export const initDbConnection = ({
|
||||
},
|
||||
migrations: {
|
||||
tableName: "infisical_migrations"
|
||||
}
|
||||
},
|
||||
pool: { min: 0, max: 10 }
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,44 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasStepColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "sequence");
|
||||
const hasApprovalRequiredColumn = await knex.schema.hasColumn(
|
||||
TableName.AccessApprovalPolicyApprover,
|
||||
"approvalsRequired"
|
||||
);
|
||||
if (!hasStepColumn || !hasApprovalRequiredColumn) {
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (t) => {
|
||||
if (!hasStepColumn) t.integer("sequence").defaultTo(1);
|
||||
if (!hasApprovalRequiredColumn) t.integer("approvalsRequired").nullable();
|
||||
});
|
||||
}
|
||||
|
||||
// set rejected status for all access request that was rejected and still has status pending
|
||||
const subquery = knex(TableName.AccessApprovalRequest)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalRequestReviewer,
|
||||
`${TableName.AccessApprovalRequestReviewer}.requestId`,
|
||||
`${TableName.AccessApprovalRequest}.id`
|
||||
)
|
||||
.where(`${TableName.AccessApprovalRequest}.status` as "status", "pending")
|
||||
.where(`${TableName.AccessApprovalRequestReviewer}.status` as "status", "rejected")
|
||||
.select(`${TableName.AccessApprovalRequest}.id`);
|
||||
|
||||
await knex(TableName.AccessApprovalRequest).where("id", "in", subquery).update("status", "rejected");
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasStepColumn = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "sequence");
|
||||
const hasApprovalRequiredColumn = await knex.schema.hasColumn(
|
||||
TableName.AccessApprovalPolicyApprover,
|
||||
"approvalsRequired"
|
||||
);
|
||||
if (hasStepColumn || hasApprovalRequiredColumn) {
|
||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (t) => {
|
||||
if (hasStepColumn) t.dropColumn("sequence");
|
||||
if (hasApprovalRequiredColumn) t.dropColumn("approvalsRequired");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasConfigColumn = await knex.schema.hasColumn(TableName.DynamicSecretLease, "config");
|
||||
if (!hasConfigColumn) {
|
||||
await knex.schema.alterTable(TableName.DynamicSecretLease, (table) => {
|
||||
table.jsonb("config");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasConfigColumn = await knex.schema.hasColumn(TableName.DynamicSecretLease, "config");
|
||||
if (hasConfigColumn) {
|
||||
await knex.schema.alterTable(TableName.DynamicSecretLease, (table) => {
|
||||
table.dropColumn("config");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
const BATCH_SIZE = 1000;
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasKubernetesHostColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "kubernetesHost");
|
||||
|
||||
if (hasKubernetesHostColumn) {
|
||||
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||
table.string("kubernetesHost").nullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasKubernetesHostColumn = await knex.schema.hasColumn(TableName.IdentityKubernetesAuth, "kubernetesHost");
|
||||
|
||||
// find all rows where kubernetesHost is null
|
||||
const rows = await knex(TableName.IdentityKubernetesAuth)
|
||||
.whereNull("kubernetesHost")
|
||||
.select(selectAllTableCols(TableName.IdentityKubernetesAuth));
|
||||
|
||||
if (rows.length > 0) {
|
||||
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
|
||||
const batch = rows.slice(i, i + BATCH_SIZE);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await knex(TableName.IdentityKubernetesAuth)
|
||||
.whereIn(
|
||||
"id",
|
||||
batch.map((row) => row.id)
|
||||
)
|
||||
.update({ kubernetesHost: "" });
|
||||
}
|
||||
}
|
||||
|
||||
if (hasKubernetesHostColumn) {
|
||||
await knex.schema.alterTable(TableName.IdentityKubernetesAuth, (table) => {
|
||||
table.string("kubernetesHost").notNullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
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.IdentityAliCloudAuth))) {
|
||||
await knex.schema.createTable(TableName.IdentityAliCloudAuth, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.bigInteger("accessTokenTTL").defaultTo(7200).notNullable();
|
||||
t.bigInteger("accessTokenMaxTTL").defaultTo(7200).notNullable();
|
||||
t.bigInteger("accessTokenNumUsesLimit").defaultTo(0).notNullable();
|
||||
t.jsonb("accessTokenTrustedIps").notNullable();
|
||||
t.timestamps(true, true, true);
|
||||
t.uuid("identityId").notNullable().unique();
|
||||
t.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||
t.string("type").notNullable();
|
||||
|
||||
t.string("allowedArns").notNullable();
|
||||
});
|
||||
}
|
||||
|
||||
await createOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.dropTableIfExists(TableName.IdentityAliCloudAuth);
|
||||
await dropOnUpdateTrigger(knex, TableName.IdentityAliCloudAuth);
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasCol = await knex.schema.hasColumn(TableName.Identity, "hasDeleteProtection");
|
||||
if (!hasCol) {
|
||||
await knex.schema.alterTable(TableName.Identity, (t) => {
|
||||
t.boolean("hasDeleteProtection").notNullable().defaultTo(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasCol = await knex.schema.hasColumn(TableName.Identity, "hasDeleteProtection");
|
||||
if (hasCol) {
|
||||
await knex.schema.alterTable(TableName.Identity, (t) => {
|
||||
t.dropColumn("hasDeleteProtection");
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
|
||||
if (hasColumn) {
|
||||
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
|
||||
t.string("allowedPrincipalArns", 2048).notNullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasColumn = await knex.schema.hasColumn(TableName.IdentityAwsAuth, "allowedPrincipalArns");
|
||||
if (hasColumn) {
|
||||
await knex.schema.alterTable(TableName.IdentityAwsAuth, (t) => {
|
||||
t.string("allowedPrincipalArns", 255).notNullable().alter();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasEncryptedGithubAppConnectionClientIdColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionClientId"
|
||||
);
|
||||
const hasEncryptedGithubAppConnectionClientSecretColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionClientSecret"
|
||||
);
|
||||
|
||||
const hasEncryptedGithubAppConnectionSlugColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionSlug"
|
||||
);
|
||||
|
||||
const hasEncryptedGithubAppConnectionAppIdColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionId"
|
||||
);
|
||||
|
||||
const hasEncryptedGithubAppConnectionAppPrivateKeyColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionPrivateKey"
|
||||
);
|
||||
|
||||
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||
if (!hasEncryptedGithubAppConnectionClientIdColumn) {
|
||||
t.binary("encryptedGitHubAppConnectionClientId").nullable();
|
||||
}
|
||||
if (!hasEncryptedGithubAppConnectionClientSecretColumn) {
|
||||
t.binary("encryptedGitHubAppConnectionClientSecret").nullable();
|
||||
}
|
||||
if (!hasEncryptedGithubAppConnectionSlugColumn) {
|
||||
t.binary("encryptedGitHubAppConnectionSlug").nullable();
|
||||
}
|
||||
if (!hasEncryptedGithubAppConnectionAppIdColumn) {
|
||||
t.binary("encryptedGitHubAppConnectionId").nullable();
|
||||
}
|
||||
if (!hasEncryptedGithubAppConnectionAppPrivateKeyColumn) {
|
||||
t.binary("encryptedGitHubAppConnectionPrivateKey").nullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasEncryptedGithubAppConnectionClientIdColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionClientId"
|
||||
);
|
||||
const hasEncryptedGithubAppConnectionClientSecretColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionClientSecret"
|
||||
);
|
||||
|
||||
const hasEncryptedGithubAppConnectionSlugColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionSlug"
|
||||
);
|
||||
|
||||
const hasEncryptedGithubAppConnectionAppIdColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionId"
|
||||
);
|
||||
|
||||
const hasEncryptedGithubAppConnectionAppPrivateKeyColumn = await knex.schema.hasColumn(
|
||||
TableName.SuperAdmin,
|
||||
"encryptedGitHubAppConnectionPrivateKey"
|
||||
);
|
||||
|
||||
await knex.schema.alterTable(TableName.SuperAdmin, (t) => {
|
||||
if (hasEncryptedGithubAppConnectionClientIdColumn) {
|
||||
t.dropColumn("encryptedGitHubAppConnectionClientId");
|
||||
}
|
||||
if (hasEncryptedGithubAppConnectionClientSecretColumn) {
|
||||
t.dropColumn("encryptedGitHubAppConnectionClientSecret");
|
||||
}
|
||||
if (hasEncryptedGithubAppConnectionSlugColumn) {
|
||||
t.dropColumn("encryptedGitHubAppConnectionSlug");
|
||||
}
|
||||
if (hasEncryptedGithubAppConnectionAppIdColumn) {
|
||||
t.dropColumn("encryptedGitHubAppConnectionId");
|
||||
}
|
||||
if (hasEncryptedGithubAppConnectionAppPrivateKeyColumn) {
|
||||
t.dropColumn("encryptedGitHubAppConnectionPrivateKey");
|
||||
}
|
||||
});
|
||||
}
|
@ -13,7 +13,9 @@ export const AccessApprovalPoliciesApproversSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
approverUserId: z.string().uuid().nullable().optional(),
|
||||
approverGroupId: z.string().uuid().nullable().optional()
|
||||
approverGroupId: z.string().uuid().nullable().optional(),
|
||||
sequence: z.number().default(0).nullable().optional(),
|
||||
approvalsRequired: z.number().default(1).nullable().optional()
|
||||
});
|
||||
|
||||
export type TAccessApprovalPoliciesApprovers = z.infer<typeof AccessApprovalPoliciesApproversSchema>;
|
||||
|
@ -16,7 +16,8 @@ export const DynamicSecretLeasesSchema = z.object({
|
||||
statusDetails: z.string().nullable().optional(),
|
||||
dynamicSecretId: z.string().uuid(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
config: z.unknown().nullable().optional()
|
||||
});
|
||||
|
||||
export type TDynamicSecretLeases = z.infer<typeof DynamicSecretLeasesSchema>;
|
||||
|
@ -12,7 +12,8 @@ export const IdentitiesSchema = z.object({
|
||||
name: z.string(),
|
||||
authMethod: z.string().nullable().optional(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
hasDeleteProtection: z.boolean().default(false)
|
||||
});
|
||||
|
||||
export type TIdentities = z.infer<typeof IdentitiesSchema>;
|
||||
|
25
backend/src/db/schemas/identity-alicloud-auths.ts
Normal file
25
backend/src/db/schemas/identity-alicloud-auths.ts
Normal file
@ -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 IdentityAlicloudAuthsSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
accessTokenTTL: z.coerce.number().default(7200),
|
||||
accessTokenMaxTTL: z.coerce.number().default(7200),
|
||||
accessTokenNumUsesLimit: z.coerce.number().default(0),
|
||||
accessTokenTrustedIps: z.unknown(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
identityId: z.string().uuid(),
|
||||
type: z.string(),
|
||||
allowedArns: z.string()
|
||||
});
|
||||
|
||||
export type TIdentityAlicloudAuths = z.infer<typeof IdentityAlicloudAuthsSchema>;
|
||||
export type TIdentityAlicloudAuthsInsert = Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>;
|
||||
export type TIdentityAlicloudAuthsUpdate = Partial<Omit<z.input<typeof IdentityAlicloudAuthsSchema>, TImmutableDBKeys>>;
|
@ -18,7 +18,7 @@ export const IdentityKubernetesAuthsSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
identityId: z.string().uuid(),
|
||||
kubernetesHost: z.string(),
|
||||
kubernetesHost: z.string().nullable().optional(),
|
||||
encryptedCaCert: z.string().nullable().optional(),
|
||||
caCertIV: z.string().nullable().optional(),
|
||||
caCertTag: z.string().nullable().optional(),
|
||||
|
@ -39,6 +39,7 @@ export * from "./group-project-memberships";
|
||||
export * from "./groups";
|
||||
export * from "./identities";
|
||||
export * from "./identity-access-tokens";
|
||||
export * from "./identity-alicloud-auths";
|
||||
export * from "./identity-aws-auths";
|
||||
export * from "./identity-azure-auths";
|
||||
export * from "./identity-gcp-auths";
|
||||
|
@ -80,6 +80,7 @@ export enum TableName {
|
||||
IdentityGcpAuth = "identity_gcp_auths",
|
||||
IdentityAzureAuth = "identity_azure_auths",
|
||||
IdentityUaClientSecret = "identity_ua_client_secrets",
|
||||
IdentityAliCloudAuth = "identity_alicloud_auths",
|
||||
IdentityAwsAuth = "identity_aws_auths",
|
||||
IdentityOciAuth = "identity_oci_auths",
|
||||
IdentityOidcAuth = "identity_oidc_auths",
|
||||
@ -247,6 +248,7 @@ export enum IdentityAuthMethod {
|
||||
UNIVERSAL_AUTH = "universal-auth",
|
||||
KUBERNETES_AUTH = "kubernetes-auth",
|
||||
GCP_AUTH = "gcp-auth",
|
||||
ALICLOUD_AUTH = "alicloud-auth",
|
||||
AWS_AUTH = "aws-auth",
|
||||
AZURE_AUTH = "azure-auth",
|
||||
OCI_AUTH = "oci-auth",
|
||||
|
@ -29,7 +29,12 @@ export const SuperAdminSchema = z.object({
|
||||
adminIdentityIds: z.string().array().nullable().optional(),
|
||||
encryptedMicrosoftTeamsAppId: zodBuffer.nullable().optional(),
|
||||
encryptedMicrosoftTeamsClientSecret: zodBuffer.nullable().optional(),
|
||||
encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional()
|
||||
encryptedMicrosoftTeamsBotId: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionClientId: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionClientSecret: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionSlug: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionId: zodBuffer.nullable().optional(),
|
||||
encryptedGitHubAppConnectionPrivateKey: zodBuffer.nullable().optional()
|
||||
});
|
||||
|
||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||
|
@ -23,12 +23,26 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
environment: z.string(),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
||||
z.object({
|
||||
type: z.literal(ApproverType.Group),
|
||||
id: z.string(),
|
||||
sequence: z.number().int().default(1)
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(ApproverType.User),
|
||||
id: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
sequence: z.number().int().default(1)
|
||||
})
|
||||
])
|
||||
.array()
|
||||
.max(100, "Cannot have more than 100 approvers")
|
||||
.min(1, { message: "At least one approver should be provided" }),
|
||||
.min(1, { message: "At least one approver should be provided" })
|
||||
.refine(
|
||||
// @ts-expect-error this is ok
|
||||
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
|
||||
"Must provide either username or id"
|
||||
),
|
||||
bypassers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||
@ -37,6 +51,13 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.array()
|
||||
.max(100, "Cannot have more than 100 bypassers")
|
||||
.optional(),
|
||||
approvalsRequired: z
|
||||
.object({
|
||||
numberOfApprovals: z.number().int(),
|
||||
stepNumber: z.number().int()
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
approvals: z.number().min(1).default(1),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||
allowedSelfApprovals: z.boolean().default(true)
|
||||
@ -78,7 +99,12 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
approvals: sapPubSchema
|
||||
.extend({
|
||||
approvers: z
|
||||
.object({ type: z.nativeEnum(ApproverType), id: z.string().nullable().optional() })
|
||||
.object({
|
||||
type: z.nativeEnum(ApproverType),
|
||||
id: z.string().nullable().optional(),
|
||||
sequence: z.number().nullable().optional(),
|
||||
approvalsRequired: z.number().nullable().optional()
|
||||
})
|
||||
.array()
|
||||
.nullable()
|
||||
.optional(),
|
||||
@ -152,12 +178,26 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.transform((val) => (val === "" ? "/" : val)),
|
||||
approvers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), username: z.string().optional() })
|
||||
z.object({
|
||||
type: z.literal(ApproverType.Group),
|
||||
id: z.string(),
|
||||
sequence: z.number().int().default(1)
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(ApproverType.User),
|
||||
id: z.string().optional(),
|
||||
username: z.string().optional(),
|
||||
sequence: z.number().int().default(1)
|
||||
})
|
||||
])
|
||||
.array()
|
||||
.min(1, { message: "At least one approver should be provided" })
|
||||
.max(100, "Cannot have more than 100 approvers"),
|
||||
.max(100, "Cannot have more than 100 approvers")
|
||||
.refine(
|
||||
// @ts-expect-error this is ok
|
||||
(el) => el.every((i) => Boolean(i?.id) || Boolean(i?.username)),
|
||||
"Must provide either username or id"
|
||||
),
|
||||
bypassers: z
|
||||
.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(BypasserType.Group), id: z.string() }),
|
||||
@ -168,7 +208,14 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.optional(),
|
||||
approvals: z.number().min(1).optional(),
|
||||
enforcementLevel: z.nativeEnum(EnforcementLevel).default(EnforcementLevel.Hard),
|
||||
allowedSelfApprovals: z.boolean().default(true)
|
||||
allowedSelfApprovals: z.boolean().default(true),
|
||||
approvalsRequired: z
|
||||
.object({
|
||||
numberOfApprovals: z.number().int(),
|
||||
stepNumber: z.number().int()
|
||||
})
|
||||
.array()
|
||||
.optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@ -235,7 +282,8 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
||||
.object({
|
||||
type: z.nativeEnum(ApproverType),
|
||||
id: z.string().nullable().optional(),
|
||||
name: z.string().nullable().optional()
|
||||
name: z.string().nullable().optional(),
|
||||
approvalsRequired: z.number().nullable().optional()
|
||||
})
|
||||
.array()
|
||||
.nullable()
|
||||
|
@ -112,7 +112,15 @@ export const registerAccessApprovalRequestRouter = async (server: FastifyZodProv
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
approvals: z.number(),
|
||||
approvers: z.string().array(),
|
||||
approvers: z
|
||||
.object({
|
||||
userId: z.string().nullable().optional(),
|
||||
sequence: z.number().nullable().optional(),
|
||||
approvalsRequired: z.number().nullable().optional(),
|
||||
email: z.string().nullable().optional(),
|
||||
username: z.string().nullable().optional()
|
||||
})
|
||||
.array(),
|
||||
bypassers: z.string().array(),
|
||||
secretPath: z.string().nullish(),
|
||||
envId: z.string(),
|
||||
|
@ -0,0 +1,17 @@
|
||||
import {
|
||||
CreateOracleDBConnectionSchema,
|
||||
SanitizedOracleDBConnectionSchema,
|
||||
UpdateOracleDBConnectionSchema
|
||||
} from "@app/ee/services/app-connections/oracledb";
|
||||
import { registerAppConnectionEndpoints } from "@app/server/routes/v1/app-connection-routers/app-connection-endpoints";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
export const registerOracleDBConnectionRouter = async (server: FastifyZodProvider) => {
|
||||
registerAppConnectionEndpoints({
|
||||
app: AppConnection.OracleDB,
|
||||
server,
|
||||
sanitizedResponseSchema: SanitizedOracleDBConnectionSchema,
|
||||
createSchema: CreateOracleDBConnectionSchema,
|
||||
updateSchema: UpdateOracleDBConnectionSchema
|
||||
});
|
||||
};
|
@ -36,7 +36,8 @@ export const registerDynamicSecretLeaseRouter = async (server: FastifyZodProvide
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
}),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.path)
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.environmentSlug),
|
||||
config: z.any().optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
@ -0,0 +1,67 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { DynamicSecretLeasesSchema } from "@app/db/schemas";
|
||||
import { ApiDocsTags, DYNAMIC_SECRET_LEASES } from "@app/lib/api-docs";
|
||||
import { daysToMillisecond } from "@app/lib/dates";
|
||||
import { removeTrailingSlash } from "@app/lib/fn";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { SanitizedDynamicSecretSchema } from "@app/server/routes/sanitizedSchemas";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
|
||||
export const registerKubernetesDynamicSecretLeaseRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.DynamicSecrets],
|
||||
body: z.object({
|
||||
dynamicSecretName: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.dynamicSecretName).toLowerCase(),
|
||||
projectSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.projectSlug),
|
||||
ttl: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(DYNAMIC_SECRET_LEASES.CREATE.ttl)
|
||||
.superRefine((val, ctx) => {
|
||||
if (!val) return;
|
||||
const valMs = ms(val);
|
||||
if (valMs < 60 * 1000)
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be greater than 1min" });
|
||||
if (valMs > daysToMillisecond(1))
|
||||
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
|
||||
}),
|
||||
path: z.string().trim().default("/").transform(removeTrailingSlash).describe(DYNAMIC_SECRET_LEASES.CREATE.path),
|
||||
environmentSlug: z.string().min(1).describe(DYNAMIC_SECRET_LEASES.CREATE.environmentSlug),
|
||||
config: z
|
||||
.object({
|
||||
namespace: z.string().min(1).optional().describe(DYNAMIC_SECRET_LEASES.KUBERNETES.CREATE.config.namespace)
|
||||
})
|
||||
.optional()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
lease: DynamicSecretLeasesSchema,
|
||||
dynamicSecret: SanitizedDynamicSecretSchema,
|
||||
data: z.unknown()
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { data, lease, dynamicSecret } = await server.services.dynamicSecretLease.create({
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
name: req.body.dynamicSecretName,
|
||||
...req.body
|
||||
});
|
||||
return { lease, data, dynamicSecret };
|
||||
}
|
||||
});
|
||||
};
|
@ -48,7 +48,9 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
||||
id: z.string().trim().describe(GROUPS.GET_BY_ID.id)
|
||||
}),
|
||||
response: {
|
||||
200: GroupsSchema
|
||||
200: GroupsSchema.extend({
|
||||
customRoleSlug: z.string().nullable()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
|
@ -6,6 +6,7 @@ import { registerAssumePrivilegeRouter } from "./assume-privilege-router";
|
||||
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
||||
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||
import { registerKubernetesDynamicSecretLeaseRouter } from "./dynamic-secret-lease-routers/kubernetes-lease-router";
|
||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||
import { registerExternalKmsRouter } from "./external-kms-router";
|
||||
import { registerGatewayRouter } from "./gateway-router";
|
||||
@ -71,6 +72,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
async (dynamicSecretRouter) => {
|
||||
await dynamicSecretRouter.register(registerDynamicSecretRouter);
|
||||
await dynamicSecretRouter.register(registerDynamicSecretLeaseRouter, { prefix: "/leases" });
|
||||
await dynamicSecretRouter.register(registerKubernetesDynamicSecretLeaseRouter, { prefix: "/leases/kubernetes" });
|
||||
},
|
||||
{ prefix: "/dynamic-secrets" }
|
||||
);
|
||||
|
@ -270,7 +270,6 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
}),
|
||||
body: z.object({
|
||||
schemas: z.array(z.string()),
|
||||
id: z.string().trim(),
|
||||
userName: z.string().trim(),
|
||||
name: z
|
||||
.object({
|
||||
@ -278,7 +277,6 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
||||
givenName: z.string().trim().optional()
|
||||
})
|
||||
.optional(),
|
||||
displayName: z.string().trim(),
|
||||
emails: z
|
||||
.array(
|
||||
z.object({
|
||||
|
@ -285,6 +285,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
commits: secretRawSchema
|
||||
.omit({ _id: true, environment: true, workspace: true, type: true, version: true, secretValue: true })
|
||||
.extend({
|
||||
secretValueHidden: z.boolean(),
|
||||
secretValue: z.string().optional(),
|
||||
isRotatedSecret: z.boolean().optional(),
|
||||
op: z.string(),
|
||||
@ -296,6 +297,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
version: z.number(),
|
||||
secretKey: z.string(),
|
||||
secretValue: z.string().optional(),
|
||||
secretValueHidden: z.boolean(),
|
||||
secretComment: z.string().optional()
|
||||
})
|
||||
.optional()
|
||||
@ -306,6 +308,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
||||
version: z.number(),
|
||||
secretKey: z.string(),
|
||||
secretValue: z.string().optional(),
|
||||
secretValueHidden: z.boolean(),
|
||||
secretComment: z.string().optional(),
|
||||
tags: SanitizedTagSchema.array().optional(),
|
||||
secretMetadata: ResourceMetadataSchema.nullish()
|
||||
|
@ -6,6 +6,7 @@ import { registerAzureClientSecretRotationRouter } from "./azure-client-secret-r
|
||||
import { registerLdapPasswordRotationRouter } from "./ldap-password-rotation-router";
|
||||
import { registerMsSqlCredentialsRotationRouter } from "./mssql-credentials-rotation-router";
|
||||
import { registerMySqlCredentialsRotationRouter } from "./mysql-credentials-rotation-router";
|
||||
import { registerOracleDBCredentialsRotationRouter } from "./oracledb-credentials-rotation-router";
|
||||
import { registerPostgresCredentialsRotationRouter } from "./postgres-credentials-rotation-router";
|
||||
|
||||
export * from "./secret-rotation-v2-router";
|
||||
@ -17,6 +18,7 @@ export const SECRET_ROTATION_REGISTER_ROUTER_MAP: Record<
|
||||
[SecretRotation.PostgresCredentials]: registerPostgresCredentialsRotationRouter,
|
||||
[SecretRotation.MsSqlCredentials]: registerMsSqlCredentialsRotationRouter,
|
||||
[SecretRotation.MySqlCredentials]: registerMySqlCredentialsRotationRouter,
|
||||
[SecretRotation.OracleDBCredentials]: registerOracleDBCredentialsRotationRouter,
|
||||
[SecretRotation.Auth0ClientSecret]: registerAuth0ClientSecretRotationRouter,
|
||||
[SecretRotation.AzureClientSecret]: registerAzureClientSecretRotationRouter,
|
||||
[SecretRotation.AwsIamUserSecret]: registerAwsIamUserSecretRotationRouter,
|
||||
|
@ -0,0 +1,19 @@
|
||||
import {
|
||||
CreateOracleDBCredentialsRotationSchema,
|
||||
OracleDBCredentialsRotationSchema,
|
||||
UpdateOracleDBCredentialsRotationSchema
|
||||
} from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||
import { SecretRotation } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-enums";
|
||||
import { SqlCredentialsRotationGeneratedCredentialsSchema } from "@app/ee/services/secret-rotation-v2/shared/sql-credentials";
|
||||
|
||||
import { registerSecretRotationEndpoints } from "./secret-rotation-v2-endpoints";
|
||||
|
||||
export const registerOracleDBCredentialsRotationRouter = async (server: FastifyZodProvider) =>
|
||||
registerSecretRotationEndpoints({
|
||||
type: SecretRotation.OracleDBCredentials,
|
||||
server,
|
||||
responseSchema: OracleDBCredentialsRotationSchema,
|
||||
createSchema: CreateOracleDBCredentialsRotationSchema,
|
||||
updateSchema: UpdateOracleDBCredentialsRotationSchema,
|
||||
generatedCredentialsSchema: SqlCredentialsRotationGeneratedCredentialsSchema
|
||||
});
|
@ -7,6 +7,7 @@ import { AzureClientSecretRotationListItemSchema } from "@app/ee/services/secret
|
||||
import { LdapPasswordRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/ldap-password";
|
||||
import { MsSqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mssql-credentials";
|
||||
import { MySqlCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/mysql-credentials";
|
||||
import { OracleDBCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/oracledb-credentials";
|
||||
import { PostgresCredentialsRotationListItemSchema } from "@app/ee/services/secret-rotation-v2/postgres-credentials";
|
||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||
import { ApiDocsTags, SecretRotations } from "@app/lib/api-docs";
|
||||
@ -18,6 +19,7 @@ const SecretRotationV2OptionsSchema = z.discriminatedUnion("type", [
|
||||
PostgresCredentialsRotationListItemSchema,
|
||||
MsSqlCredentialsRotationListItemSchema,
|
||||
MySqlCredentialsRotationListItemSchema,
|
||||
OracleDBCredentialsRotationListItemSchema,
|
||||
Auth0ClientSecretRotationListItemSchema,
|
||||
AzureClientSecretRotationListItemSchema,
|
||||
AwsIamUserSecretRotationListItemSchema,
|
||||
|
@ -187,6 +187,56 @@ export const registerSecretScanningV2Router = async (server: FastifyZodProvider)
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PATCH",
|
||||
url: "/findings",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.SecretScanning],
|
||||
description: "Update one or more Secret Scanning Findings in a batch.",
|
||||
body: z
|
||||
.object({
|
||||
findingId: z.string().trim().min(1, "Finding ID required").describe(SecretScanningFindings.UPDATE.findingId),
|
||||
status: z.nativeEnum(SecretScanningFindingStatus).optional().describe(SecretScanningFindings.UPDATE.status),
|
||||
remarks: z.string().nullish().describe(SecretScanningFindings.UPDATE.remarks)
|
||||
})
|
||||
.array()
|
||||
.max(500),
|
||||
response: {
|
||||
200: z.object({ findings: SecretScanningFindingSchema.array() })
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const { body, permission } = req;
|
||||
|
||||
const updatedFindingPromises = body.map(async (findingUpdatePayload) => {
|
||||
const { finding, projectId } = await server.services.secretScanningV2.updateSecretScanningFindingById(
|
||||
findingUpdatePayload,
|
||||
permission
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.SECRET_SCANNING_FINDING_UPDATE,
|
||||
metadata: findingUpdatePayload
|
||||
}
|
||||
});
|
||||
|
||||
return finding;
|
||||
});
|
||||
|
||||
const findings = await Promise.all(updatedFindingPromises);
|
||||
|
||||
return { findings };
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/configs",
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TAccessApprovalPolicyApproverDALFactory = ReturnType<typeof accessApprovalPolicyApproverDALFactory>;
|
||||
export type TAccessApprovalPolicyApproverDALFactory = TOrmify<TableName.AccessApprovalPolicyApprover>;
|
||||
|
||||
export const accessApprovalPolicyApproverDALFactory = (db: TDbClient) => {
|
||||
const accessApprovalPolicyApproverOrm = ormify(db, TableName.AccessApprovalPolicyApprover);
|
||||
return { ...accessApprovalPolicyApproverOrm };
|
||||
};
|
||||
|
||||
export type TAccessApprovalPolicyBypasserDALFactory = ReturnType<typeof accessApprovalPolicyBypasserDALFactory>;
|
||||
export type TAccessApprovalPolicyBypasserDALFactory = TOrmify<TableName.AccessApprovalPolicyBypasser>;
|
||||
|
||||
export const accessApprovalPolicyBypasserDALFactory = (db: TDbClient) => {
|
||||
const accessApprovalPolicyBypasserOrm = ormify(db, TableName.AccessApprovalPolicyBypasser);
|
||||
|
@ -3,13 +3,363 @@ import { Knex } from "knex";
|
||||
import { TDbClient } from "@app/db";
|
||||
import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies, TUsers } from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||
import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter, TOrmify } from "@app/lib/knex";
|
||||
|
||||
import { ApproverType, BypasserType } from "./access-approval-policy-types";
|
||||
import {
|
||||
ApproverType,
|
||||
BypasserType,
|
||||
TCreateAccessApprovalPolicy,
|
||||
TDeleteAccessApprovalPolicy,
|
||||
TGetAccessApprovalPolicyByIdDTO,
|
||||
TGetAccessPolicyCountByEnvironmentDTO,
|
||||
TListAccessApprovalPoliciesDTO,
|
||||
TUpdateAccessApprovalPolicy
|
||||
} from "./access-approval-policy-types";
|
||||
|
||||
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
|
||||
export interface TAccessApprovalPolicyDALFactory
|
||||
extends Omit<TOrmify<TableName.AccessApprovalPolicy>, "findById" | "find"> {
|
||||
find: (
|
||||
filter: TFindFilter<
|
||||
TAccessApprovalPolicies & {
|
||||
projectId: string;
|
||||
}
|
||||
>,
|
||||
customFilter?: {
|
||||
policyId?: string;
|
||||
},
|
||||
tx?: Knex
|
||||
) => Promise<
|
||||
{
|
||||
approvers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType.User;
|
||||
name: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType.Group;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
)[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
bypassers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType.User;
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType.Group;
|
||||
}
|
||||
)[];
|
||||
}[]
|
||||
>;
|
||||
findById: (
|
||||
policyId: string,
|
||||
tx?: Knex
|
||||
) => Promise<
|
||||
| {
|
||||
approvers: {
|
||||
id: string | null | undefined;
|
||||
type: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
softDeleteById: (
|
||||
policyId: string,
|
||||
tx?: Knex
|
||||
) => Promise<{
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
findLastValidPolicy: (
|
||||
{
|
||||
envId,
|
||||
secretPath
|
||||
}: {
|
||||
envId: string;
|
||||
secretPath: string;
|
||||
},
|
||||
tx?: Knex
|
||||
) => Promise<
|
||||
| {
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
}
|
||||
|
||||
export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
export interface TAccessApprovalPolicyServiceFactory {
|
||||
getAccessPolicyCountByEnvSlug: ({
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectSlug,
|
||||
actorId,
|
||||
envSlug
|
||||
}: TGetAccessPolicyCountByEnvironmentDTO) => Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
createAccessApprovalPolicy: ({
|
||||
name,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
secretPath,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
approvers,
|
||||
bypassers,
|
||||
projectSlug,
|
||||
environment,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
}: TCreateAccessApprovalPolicy) => Promise<{
|
||||
environment: {
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
position: number;
|
||||
};
|
||||
projectId: string;
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
deleteAccessApprovalPolicy: ({
|
||||
policyId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TDeleteAccessApprovalPolicy) => Promise<{
|
||||
approvers: {
|
||||
id: string | null | undefined;
|
||||
type: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
}>;
|
||||
updateAccessApprovalPolicy: ({
|
||||
policyId,
|
||||
approvers,
|
||||
bypassers,
|
||||
secretPath,
|
||||
name,
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
}: TUpdateAccessApprovalPolicy) => Promise<{
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
getAccessApprovalPolicyByProjectSlug: ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectSlug
|
||||
}: TListAccessApprovalPoliciesDTO) => Promise<
|
||||
{
|
||||
approvers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType;
|
||||
name: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
)[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
bypassers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType;
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType;
|
||||
}
|
||||
)[];
|
||||
}[]
|
||||
>;
|
||||
getAccessApprovalPolicyById: ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
policyId
|
||||
}: TGetAccessApprovalPolicyByIdDTO) => Promise<{
|
||||
approvers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType.User;
|
||||
name: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType.Group;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
)[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
bypassers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType.User;
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType.Group;
|
||||
}
|
||||
)[];
|
||||
}>;
|
||||
}
|
||||
|
||||
export const accessApprovalPolicyDALFactory = (db: TDbClient): TAccessApprovalPolicyDALFactory => {
|
||||
const accessApprovalPolicyOrm = ormify(db, TableName.AccessApprovalPolicy);
|
||||
|
||||
const accessApprovalPolicyFindQuery = async (
|
||||
@ -48,6 +398,8 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
.select(tx.ref("username").withSchema("bypasserUsers").as("bypasserUsername"))
|
||||
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(tx.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"))
|
||||
.select(tx.ref("approvalsRequired").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(tx.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||
.select(tx.ref("bypasserGroupId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||
@ -59,7 +411,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
const findById = async (policyId: string, tx?: Knex) => {
|
||||
const findById: TAccessApprovalPolicyDALFactory["findById"] = async (policyId, tx) => {
|
||||
try {
|
||||
const doc = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), {
|
||||
[`${TableName.AccessApprovalPolicy}.id` as "id"]: policyId
|
||||
@ -80,35 +432,37 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "approverUserId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({ approverUserId: id }) => ({
|
||||
mapper: ({ approverUserId: id, approverSequence, approvalsRequired }) => ({
|
||||
id,
|
||||
type: "user"
|
||||
type: "user",
|
||||
sequence: approverSequence,
|
||||
approvalsRequired
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "approverGroupId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({ approverGroupId: id }) => ({
|
||||
mapper: ({ approverGroupId: id, approverSequence, approvalsRequired }) => ({
|
||||
id,
|
||||
type: "group"
|
||||
type: "group",
|
||||
sequence: approverSequence,
|
||||
approvalsRequired
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
if (!formattedDoc?.[0]) return;
|
||||
|
||||
return formattedDoc?.[0];
|
||||
return {
|
||||
...formattedDoc?.[0],
|
||||
approvers: formattedDoc?.[0]?.approvers.sort((a, b) => (a.sequence || 1) - (b.sequence || 1))
|
||||
};
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindById" });
|
||||
}
|
||||
};
|
||||
|
||||
const find = async (
|
||||
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
||||
customFilter?: {
|
||||
policyId?: string;
|
||||
},
|
||||
tx?: Knex
|
||||
) => {
|
||||
const find: TAccessApprovalPolicyDALFactory["find"] = async (filter, customFilter, tx) => {
|
||||
try {
|
||||
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter, customFilter);
|
||||
|
||||
@ -129,18 +483,22 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
{
|
||||
key: "approverUserId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({ approverUserId: id, approverUsername }) => ({
|
||||
mapper: ({ approverUserId: id, approverUsername, approverSequence, approvalsRequired }) => ({
|
||||
id,
|
||||
type: ApproverType.User,
|
||||
name: approverUsername
|
||||
type: ApproverType.User as const,
|
||||
name: approverUsername,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "approverGroupId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({ approverGroupId: id }) => ({
|
||||
mapper: ({ approverGroupId: id, approverSequence, approvalsRequired }) => ({
|
||||
id,
|
||||
type: ApproverType.Group
|
||||
type: ApproverType.Group as const,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired
|
||||
})
|
||||
},
|
||||
{
|
||||
@ -148,7 +506,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
label: "bypassers" as const,
|
||||
mapper: ({ bypasserUserId: id, bypasserUsername }) => ({
|
||||
id,
|
||||
type: BypasserType.User,
|
||||
type: BypasserType.User as const,
|
||||
name: bypasserUsername
|
||||
})
|
||||
},
|
||||
@ -157,24 +515,30 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||
label: "bypassers" as const,
|
||||
mapper: ({ bypasserGroupId: id }) => ({
|
||||
id,
|
||||
type: BypasserType.Group
|
||||
type: BypasserType.Group as const
|
||||
})
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return formattedDocs;
|
||||
return formattedDocs.map((el) => ({
|
||||
...el,
|
||||
approvers: el?.approvers.sort((a, b) => (a.sequence || 1) - (b.sequence || 1))
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find" });
|
||||
}
|
||||
};
|
||||
|
||||
const softDeleteById = async (policyId: string, tx?: Knex) => {
|
||||
const softDeleteById: TAccessApprovalPolicyDALFactory["softDeleteById"] = async (policyId, tx) => {
|
||||
const softDeletedPolicy = await accessApprovalPolicyOrm.updateById(policyId, { deletedAt: new Date() }, tx);
|
||||
return softDeletedPolicy;
|
||||
};
|
||||
|
||||
const findLastValidPolicy = async ({ envId, secretPath }: { envId: string; secretPath: string }, tx?: Knex) => {
|
||||
const findLastValidPolicy: TAccessApprovalPolicyDALFactory["findLastValidPolicy"] = async (
|
||||
{ envId, secretPath },
|
||||
tx
|
||||
) => {
|
||||
try {
|
||||
const result = await (tx || db.replicaNode())(TableName.AccessApprovalPolicy)
|
||||
.where(
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||
@ -23,9 +24,8 @@ import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
||||
import {
|
||||
ApproverType,
|
||||
BypasserType,
|
||||
TCreateAccessApprovalPolicy,
|
||||
TAccessApprovalPolicyServiceFactory,
|
||||
TDeleteAccessApprovalPolicy,
|
||||
TGetAccessApprovalPolicyByIdDTO,
|
||||
TGetAccessPolicyCountByEnvironmentDTO,
|
||||
TListAccessApprovalPoliciesDTO,
|
||||
TUpdateAccessApprovalPolicy
|
||||
@ -41,14 +41,12 @@ type TAccessApprovalPolicyServiceFactoryDep = {
|
||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
|
||||
groupDAL: TGroupDALFactory;
|
||||
userDAL: Pick<TUserDALFactory, "find">;
|
||||
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find">;
|
||||
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update" | "find" | "resetReviewByPolicyId">;
|
||||
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update">;
|
||||
accessApprovalRequestReviewerDAL: Pick<TAccessApprovalRequestReviewerDALFactory, "update" | "delete">;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find">;
|
||||
};
|
||||
|
||||
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
|
||||
|
||||
export const accessApprovalPolicyServiceFactory = ({
|
||||
accessApprovalPolicyDAL,
|
||||
accessApprovalPolicyApproverDAL,
|
||||
@ -62,8 +60,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
additionalPrivilegeDAL,
|
||||
accessApprovalRequestReviewerDAL,
|
||||
orgMembershipDAL
|
||||
}: TAccessApprovalPolicyServiceFactoryDep) => {
|
||||
const createAccessApprovalPolicy = async ({
|
||||
}: TAccessApprovalPolicyServiceFactoryDep): TAccessApprovalPolicyServiceFactory => {
|
||||
const createAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["createAccessApprovalPolicy"] = async ({
|
||||
name,
|
||||
actor,
|
||||
actorId,
|
||||
@ -76,27 +74,23 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
projectSlug,
|
||||
environment,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
}: TCreateAccessApprovalPolicy) => {
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
}) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
|
||||
// If there is a group approver people might be added to the group later to meet the approvers quota
|
||||
const groupApprovers = approvers
|
||||
.filter((approver) => approver.type === ApproverType.Group)
|
||||
.map((approver) => approver.id) as string[];
|
||||
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
||||
|
||||
const userApprovers = approvers
|
||||
.filter((approver) => approver.type === ApproverType.User)
|
||||
.map((approver) => approver.id)
|
||||
.filter(Boolean) as string[];
|
||||
const userApprovers = approvers.filter((approver) => approver.type === ApproverType.User && approver.id) as {
|
||||
id: string;
|
||||
sequence?: number;
|
||||
}[];
|
||||
|
||||
const userApproverNames = approvers
|
||||
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||
const userApproverNames = approvers.filter(
|
||||
(approver) => approver.type === ApproverType.User && approver.username
|
||||
) as { username: string; sequence?: number }[];
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
@ -116,14 +110,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
|
||||
let approverUserIds = userApprovers;
|
||||
if (userApproverNames.length) {
|
||||
const approverUsers = await userDAL.find({
|
||||
const approverUsersInDB = await userDAL.find({
|
||||
$in: {
|
||||
username: userApproverNames
|
||||
username: userApproverNames.map((el) => el.username)
|
||||
}
|
||||
});
|
||||
|
||||
const approverNamesFromDb = approverUsers.map((user) => user.username);
|
||||
const invalidUsernames = userApproverNames.filter((username) => !approverNamesFromDb.includes(username));
|
||||
const approverUsersInDBGroupByUsername = groupBy(approverUsersInDB, (i) => i.username);
|
||||
const invalidUsernames = userApproverNames.filter((el) => !approverUsersInDBGroupByUsername?.[el.username]?.[0]);
|
||||
|
||||
if (invalidUsernames.length) {
|
||||
throw new BadRequestError({
|
||||
@ -131,32 +124,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
approverUserIds = approverUserIds.concat(approverUsers.map((user) => user.id));
|
||||
}
|
||||
|
||||
const usersPromises: Promise<
|
||||
{
|
||||
id: string;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
isPartOfGroup: boolean;
|
||||
}[]
|
||||
>[] = [];
|
||||
const verifyAllApprovers = [...approverUserIds];
|
||||
|
||||
for (const groupId of groupApprovers) {
|
||||
usersPromises.push(
|
||||
groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }).then((group) => group.members)
|
||||
approverUserIds = approverUserIds.concat(
|
||||
userApproverNames.map((el) => ({
|
||||
id: approverUsersInDBGroupByUsername[el.username]?.[0].id,
|
||||
sequence: el.sequence
|
||||
}))
|
||||
);
|
||||
}
|
||||
const verifyGroupApprovers = (await Promise.all(usersPromises))
|
||||
.flat()
|
||||
.filter((user) => user.isPartOfGroup)
|
||||
.map((user) => user.id);
|
||||
verifyAllApprovers.push(...verifyGroupApprovers);
|
||||
|
||||
let groupBypassers: string[] = [];
|
||||
let bypasserUserIds: string[] = [];
|
||||
|
||||
@ -195,6 +169,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
const approvalsRequiredGroupByStepNumber = groupBy(approvalsRequired || [], (i) => i.stepNumber);
|
||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||
const doc = await accessApprovalPolicyDAL.create(
|
||||
{
|
||||
@ -210,9 +185,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
|
||||
if (approverUserIds.length) {
|
||||
await accessApprovalPolicyApproverDAL.insertMany(
|
||||
approverUserIds.map((userId) => ({
|
||||
approverUserId: userId,
|
||||
policyId: doc.id
|
||||
approverUserIds.map((el) => ({
|
||||
approverUserId: el.id,
|
||||
policyId: doc.id,
|
||||
sequence: el.sequence,
|
||||
approvalsRequired: el.sequence
|
||||
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
|
||||
: approvals
|
||||
})),
|
||||
tx
|
||||
);
|
||||
@ -220,9 +199,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
|
||||
if (groupApprovers) {
|
||||
await accessApprovalPolicyApproverDAL.insertMany(
|
||||
groupApprovers.map((groupId) => ({
|
||||
approverGroupId: groupId,
|
||||
policyId: doc.id
|
||||
groupApprovers.map((el) => ({
|
||||
approverGroupId: el.id,
|
||||
policyId: doc.id,
|
||||
sequence: el.sequence,
|
||||
approvalsRequired: el.sequence
|
||||
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
|
||||
: approvals
|
||||
})),
|
||||
tx
|
||||
);
|
||||
@ -254,31 +237,26 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
return { ...accessApproval, environment: env, projectId: project.id };
|
||||
};
|
||||
|
||||
const getAccessApprovalPolicyByProjectSlug = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectSlug
|
||||
}: TListAccessApprovalPoliciesDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
const getAccessApprovalPolicyByProjectSlug: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyByProjectSlug"] =
|
||||
async ({ actorId, actor, actorOrgId, actorAuthMethod, projectSlug }: TListAccessApprovalPoliciesDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
|
||||
// Anyone in the project should be able to get the policies.
|
||||
await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
// Anyone in the project should be able to get the policies.
|
||||
await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: project.id,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.SecretManager
|
||||
});
|
||||
|
||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||
return accessApprovalPolicies;
|
||||
};
|
||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||
return accessApprovalPolicies;
|
||||
};
|
||||
|
||||
const updateAccessApprovalPolicy = async ({
|
||||
const updateAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["updateAccessApprovalPolicy"] = async ({
|
||||
policyId,
|
||||
approvers,
|
||||
bypassers,
|
||||
@ -290,22 +268,22 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
}: TUpdateAccessApprovalPolicy) => {
|
||||
const groupApprovers = approvers
|
||||
.filter((approver) => approver.type === ApproverType.Group)
|
||||
.map((approver) => approver.id) as string[];
|
||||
const groupApprovers = approvers.filter((approver) => approver.type === ApproverType.Group);
|
||||
|
||||
const userApprovers = approvers
|
||||
.filter((approver) => approver.type === ApproverType.User)
|
||||
.map((approver) => approver.id)
|
||||
.filter(Boolean) as string[];
|
||||
|
||||
const userApproverNames = approvers
|
||||
.map((approver) => (approver.type === ApproverType.User ? approver.username : undefined))
|
||||
.filter(Boolean) as string[];
|
||||
const userApprovers = approvers.filter((approver) => approver.type === ApproverType.User && approver.id) as {
|
||||
id: string;
|
||||
sequence?: number;
|
||||
}[];
|
||||
const userApproverNames = approvers.filter(
|
||||
(approver) => approver.type === ApproverType.User && approver.username
|
||||
) as { username: string; sequence?: number }[];
|
||||
|
||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Approval policy not found" });
|
||||
|
||||
const currentApprovals = approvals || accessApprovalPolicy.approvals;
|
||||
if (
|
||||
groupApprovers?.length === 0 &&
|
||||
@ -401,6 +379,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
const approvalsRequiredGroupByStepNumber = groupBy(approvalsRequired || [], (i) => i.stepNumber);
|
||||
const updatedPolicy = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||
const doc = await accessApprovalPolicyDAL.updateById(
|
||||
accessApprovalPolicy.id,
|
||||
@ -417,16 +396,18 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
||||
|
||||
if (userApprovers.length || userApproverNames.length) {
|
||||
let userApproverIds = userApprovers;
|
||||
let approverUserIds = userApprovers;
|
||||
if (userApproverNames.length) {
|
||||
const approverUsers = await userDAL.find({
|
||||
const approverUsersInDB = await userDAL.find({
|
||||
$in: {
|
||||
username: userApproverNames
|
||||
username: userApproverNames.map((el) => el.username)
|
||||
}
|
||||
});
|
||||
const approverUsersInDBGroupByUsername = groupBy(approverUsersInDB, (i) => i.username);
|
||||
|
||||
const approverNamesFromDb = approverUsers.map((user) => user.username);
|
||||
const invalidUsernames = userApproverNames.filter((username) => !approverNamesFromDb.includes(username));
|
||||
const invalidUsernames = userApproverNames.filter(
|
||||
(el) => !approverUsersInDBGroupByUsername?.[el.username]?.[0]
|
||||
);
|
||||
|
||||
if (invalidUsernames.length) {
|
||||
throw new BadRequestError({
|
||||
@ -434,13 +415,21 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
||||
approverUserIds = approverUserIds.concat(
|
||||
userApproverNames.map((el) => ({
|
||||
id: approverUsersInDBGroupByUsername[el.username]?.[0].id,
|
||||
sequence: el.sequence
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
await accessApprovalPolicyApproverDAL.insertMany(
|
||||
userApproverIds.map((userId) => ({
|
||||
approverUserId: userId,
|
||||
policyId: doc.id
|
||||
approverUserIds.map((el) => ({
|
||||
approverUserId: el.id,
|
||||
policyId: doc.id,
|
||||
sequence: el.sequence,
|
||||
approvalsRequired: el.sequence
|
||||
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
|
||||
: approvals
|
||||
})),
|
||||
tx
|
||||
);
|
||||
@ -448,9 +437,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
|
||||
if (groupApprovers) {
|
||||
await accessApprovalPolicyApproverDAL.insertMany(
|
||||
groupApprovers.map((groupId) => ({
|
||||
approverGroupId: groupId,
|
||||
policyId: doc.id
|
||||
groupApprovers.map((el) => ({
|
||||
approverGroupId: el.id,
|
||||
policyId: doc.id,
|
||||
sequence: el.sequence,
|
||||
approvalsRequired: el.sequence
|
||||
? approvalsRequiredGroupByStepNumber?.[el.sequence]?.[0]?.numberOfApprovals
|
||||
: approvals
|
||||
})),
|
||||
tx
|
||||
);
|
||||
@ -478,8 +471,11 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
);
|
||||
}
|
||||
|
||||
await accessApprovalRequestDAL.resetReviewByPolicyId(doc.id, tx);
|
||||
|
||||
return doc;
|
||||
});
|
||||
|
||||
return {
|
||||
...updatedPolicy,
|
||||
environment: accessApprovalPolicy.environment,
|
||||
@ -487,7 +483,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const deleteAccessApprovalPolicy = async ({
|
||||
const deleteAccessApprovalPolicy: TAccessApprovalPolicyServiceFactory["deleteAccessApprovalPolicy"] = async ({
|
||||
policyId,
|
||||
actor,
|
||||
actorId,
|
||||
@ -536,7 +532,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
return policy;
|
||||
};
|
||||
|
||||
const getAccessPolicyCountByEnvSlug = async ({
|
||||
const getAccessPolicyCountByEnvSlug: TAccessApprovalPolicyServiceFactory["getAccessPolicyCountByEnvSlug"] = async ({
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
@ -573,13 +569,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
||||
return { count: policies.length };
|
||||
};
|
||||
|
||||
const getAccessApprovalPolicyById = async ({
|
||||
const getAccessApprovalPolicyById: TAccessApprovalPolicyServiceFactory["getAccessApprovalPolicyById"] = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
policyId
|
||||
}: TGetAccessApprovalPolicyByIdDTO) => {
|
||||
}) => {
|
||||
const [policy] = await accessApprovalPolicyDAL.find({}, { policyId });
|
||||
|
||||
if (!policy) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { EnforcementLevel, TProjectPermission } from "@app/lib/types";
|
||||
import { ActorAuthMethod } from "@app/services/auth/auth-type";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
|
||||
export type TIsApproversValid = {
|
||||
userIds: string[];
|
||||
@ -27,7 +27,10 @@ export type TCreateAccessApprovalPolicy = {
|
||||
approvals: number;
|
||||
secretPath: string;
|
||||
environment: string;
|
||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||
approvers: (
|
||||
| { type: ApproverType.Group; id: string; sequence?: number }
|
||||
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
|
||||
)[];
|
||||
bypassers?: (
|
||||
| { type: BypasserType.Group; id: string }
|
||||
| { type: BypasserType.User; id?: string; username?: string }
|
||||
@ -36,12 +39,16 @@ export type TCreateAccessApprovalPolicy = {
|
||||
name: string;
|
||||
enforcementLevel: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateAccessApprovalPolicy = {
|
||||
policyId: string;
|
||||
approvals?: number;
|
||||
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; username?: string })[];
|
||||
approvers: (
|
||||
| { type: ApproverType.Group; id: string; sequence?: number }
|
||||
| { type: ApproverType.User; id?: string; username?: string; sequence?: number }
|
||||
)[];
|
||||
bypassers?: (
|
||||
| { type: BypasserType.Group; id: string }
|
||||
| { type: BypasserType.User; id?: string; username?: string }
|
||||
@ -50,6 +57,7 @@ export type TUpdateAccessApprovalPolicy = {
|
||||
name?: string;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
approvalsRequired?: { numberOfApprovals: number; stepNumber: number }[];
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteAccessApprovalPolicy = {
|
||||
@ -68,3 +76,217 @@ export type TGetAccessApprovalPolicyByIdDTO = {
|
||||
export type TListAccessApprovalPoliciesDTO = {
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export interface TAccessApprovalPolicyServiceFactory {
|
||||
getAccessPolicyCountByEnvSlug: ({
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectSlug,
|
||||
actorId,
|
||||
envSlug
|
||||
}: TGetAccessPolicyCountByEnvironmentDTO) => Promise<{
|
||||
count: number;
|
||||
}>;
|
||||
createAccessApprovalPolicy: ({
|
||||
name,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
secretPath,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
approvers,
|
||||
bypassers,
|
||||
projectSlug,
|
||||
environment,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
}: TCreateAccessApprovalPolicy) => Promise<{
|
||||
environment: {
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
position: number;
|
||||
};
|
||||
projectId: string;
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
deleteAccessApprovalPolicy: ({
|
||||
policyId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
}: TDeleteAccessApprovalPolicy) => Promise<{
|
||||
approvers: {
|
||||
id: string | null | undefined;
|
||||
type: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
}>;
|
||||
updateAccessApprovalPolicy: ({
|
||||
policyId,
|
||||
approvers,
|
||||
bypassers,
|
||||
secretPath,
|
||||
name,
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
approvals,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
}: TUpdateAccessApprovalPolicy) => Promise<{
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
}>;
|
||||
getAccessApprovalPolicyByProjectSlug: ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
projectSlug
|
||||
}: TListAccessApprovalPoliciesDTO) => Promise<
|
||||
{
|
||||
approvers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType;
|
||||
name: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
)[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
bypassers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType;
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType;
|
||||
}
|
||||
)[];
|
||||
}[]
|
||||
>;
|
||||
getAccessApprovalPolicyById: ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
policyId
|
||||
}: TGetAccessApprovalPolicyByIdDTO) => Promise<{
|
||||
approvers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType.User;
|
||||
name: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: ApproverType.Group;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
)[];
|
||||
name: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
approvals: number;
|
||||
envId: string;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
secretPath?: string | null | undefined;
|
||||
deletedAt?: Date | null | undefined;
|
||||
environment: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
projectId: string;
|
||||
bypassers: (
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType.User;
|
||||
name: string;
|
||||
}
|
||||
| {
|
||||
id: string | null | undefined;
|
||||
type: BypasserType.Group;
|
||||
}
|
||||
)[];
|
||||
}>;
|
||||
}
|
||||
|
@ -9,195 +9,442 @@ import {
|
||||
TUsers
|
||||
} from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter, TOrmify } from "@app/lib/knex";
|
||||
|
||||
import { ApprovalStatus } from "./access-approval-request-types";
|
||||
|
||||
export type TAccessApprovalRequestDALFactory = ReturnType<typeof accessApprovalRequestDALFactory>;
|
||||
export interface TAccessApprovalRequestDALFactory extends Omit<TOrmify<TableName.AccessApprovalRequest>, "findById"> {
|
||||
findById: (
|
||||
id: string,
|
||||
tx?: Knex
|
||||
) => Promise<
|
||||
| {
|
||||
policy: {
|
||||
approvers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
)[];
|
||||
bypassers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
)[];
|
||||
id: string;
|
||||
name: string;
|
||||
approvals: number;
|
||||
secretPath: string | null | undefined;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
deletedAt: Date | null | undefined;
|
||||
};
|
||||
projectId: string;
|
||||
environment: string;
|
||||
requestedByUser: {
|
||||
userId: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
};
|
||||
status: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
policyId: string;
|
||||
isTemporary: boolean;
|
||||
requestedByUserId: string;
|
||||
privilegeId?: string | null | undefined;
|
||||
requestedBy?: string | null | undefined;
|
||||
temporaryRange?: string | null | undefined;
|
||||
permissions?: unknown;
|
||||
note?: string | null | undefined;
|
||||
privilegeDeletedAt?: Date | null | undefined;
|
||||
reviewers: {
|
||||
userId: string;
|
||||
status: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
}[];
|
||||
approvers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
}
|
||||
)[];
|
||||
bypassers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
)[];
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
findRequestsWithPrivilegeByPolicyIds: (policyIds: string[]) => Promise<
|
||||
{
|
||||
policy: {
|
||||
approvers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
id: string;
|
||||
name: string;
|
||||
approvals: number;
|
||||
secretPath: string | null | undefined;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
envId: string;
|
||||
deletedAt: Date | null | undefined;
|
||||
};
|
||||
projectId: string;
|
||||
environment: string;
|
||||
environmentName: string;
|
||||
requestedByUser: {
|
||||
userId: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
};
|
||||
privilege: {
|
||||
membershipId: string;
|
||||
userId: string;
|
||||
projectId: string;
|
||||
isTemporary: boolean;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
permissions: unknown;
|
||||
} | null;
|
||||
isApproved: boolean;
|
||||
status: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
policyId: string;
|
||||
isTemporary: boolean;
|
||||
requestedByUserId: string;
|
||||
privilegeId?: string | null | undefined;
|
||||
requestedBy?: string | null | undefined;
|
||||
temporaryRange?: string | null | undefined;
|
||||
permissions?: unknown;
|
||||
note?: string | null | undefined;
|
||||
privilegeDeletedAt?: Date | null | undefined;
|
||||
reviewers: {
|
||||
userId: string;
|
||||
status: string;
|
||||
}[];
|
||||
approvers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
}[]
|
||||
>;
|
||||
getCount: ({ projectId }: { projectId: string }) => Promise<{
|
||||
pendingCount: number;
|
||||
finalizedCount: number;
|
||||
}>;
|
||||
resetReviewByPolicyId: (policyId: string, tx?: Knex) => Promise<void>;
|
||||
}
|
||||
|
||||
export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
export const accessApprovalRequestDALFactory = (db: TDbClient): TAccessApprovalRequestDALFactory => {
|
||||
const accessApprovalRequestOrm = ormify(db, TableName.AccessApprovalRequest);
|
||||
|
||||
const findRequestsWithPrivilegeByPolicyIds = async (policyIds: string[]) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.AccessApprovalRequest)
|
||||
.whereIn(`${TableName.AccessApprovalRequest}.policyId`, policyIds)
|
||||
const findRequestsWithPrivilegeByPolicyIds: TAccessApprovalRequestDALFactory["findRequestsWithPrivilegeByPolicyIds"] =
|
||||
async (policyIds) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.AccessApprovalRequest)
|
||||
.whereIn(`${TableName.AccessApprovalRequest}.policyId`, policyIds)
|
||||
|
||||
.leftJoin(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
`${TableName.AccessApprovalRequest}.privilegeId`,
|
||||
`${TableName.ProjectUserAdditionalPrivilege}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalPolicy,
|
||||
`${TableName.AccessApprovalRequest}.policyId`,
|
||||
`${TableName.AccessApprovalPolicy}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalRequestReviewer,
|
||||
`${TableName.AccessApprovalRequest}.id`,
|
||||
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.ProjectUserAdditionalPrivilege,
|
||||
`${TableName.AccessApprovalRequest}.privilegeId`,
|
||||
`${TableName.ProjectUserAdditionalPrivilege}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalPolicy,
|
||||
`${TableName.AccessApprovalRequest}.policyId`,
|
||||
`${TableName.AccessApprovalPolicy}.id`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalRequestReviewer,
|
||||
`${TableName.AccessApprovalRequest}.id`,
|
||||
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalPolicyApprover,
|
||||
`${TableName.AccessApprovalPolicy}.id`,
|
||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||
)
|
||||
.leftJoin<TUsers>(
|
||||
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
|
||||
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
|
||||
"accessApprovalPolicyApproverUser.id"
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.UserGroupMembership,
|
||||
`${TableName.AccessApprovalPolicyApprover}.approverGroupId`,
|
||||
`${TableName.UserGroupMembership}.groupId`
|
||||
)
|
||||
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalPolicyApprover,
|
||||
`${TableName.AccessApprovalPolicy}.id`,
|
||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||
)
|
||||
.leftJoin(
|
||||
TableName.UserGroupMembership,
|
||||
`${TableName.AccessApprovalPolicyApprover}.approverGroupId`,
|
||||
`${TableName.UserGroupMembership}.groupId`
|
||||
)
|
||||
.leftJoin(TableName.Users, `${TableName.UserGroupMembership}.userId`, `${TableName.Users}.id`)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalPolicyBypasser,
|
||||
`${TableName.AccessApprovalPolicy}.id`,
|
||||
`${TableName.AccessApprovalPolicyBypasser}.policyId`
|
||||
)
|
||||
.leftJoin<TUserGroupMembership>(
|
||||
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
|
||||
`bypasserUserGroupMembership.groupId`
|
||||
)
|
||||
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalPolicyBypasser,
|
||||
`${TableName.AccessApprovalPolicy}.id`,
|
||||
`${TableName.AccessApprovalPolicyBypasser}.policyId`
|
||||
)
|
||||
.leftJoin<TUserGroupMembership>(
|
||||
db(TableName.UserGroupMembership).as("bypasserUserGroupMembership"),
|
||||
`${TableName.AccessApprovalPolicyBypasser}.bypasserGroupId`,
|
||||
`bypasserUserGroupMembership.groupId`
|
||||
)
|
||||
.join<TUsers>(
|
||||
db(TableName.Users).as("requestedByUser"),
|
||||
`${TableName.AccessApprovalRequest}.requestedByUserId`,
|
||||
`requestedByUser.id`
|
||||
)
|
||||
|
||||
.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(
|
||||
db.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
|
||||
db.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
|
||||
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
|
||||
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
||||
)
|
||||
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(db.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"))
|
||||
.select(db.ref("approvalsRequired").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"))
|
||||
.select(db.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||
.select(db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"))
|
||||
.select(
|
||||
db.ref("email").withSchema("accessApprovalPolicyApproverUser").as("approverEmail"),
|
||||
db.ref("email").withSchema(TableName.Users).as("approverGroupEmail"),
|
||||
db.ref("username").withSchema("accessApprovalPolicyApproverUser").as("approverUsername"),
|
||||
db.ref("username").withSchema(TableName.Users).as("approverGroupUsername")
|
||||
)
|
||||
.select(
|
||||
db.ref("projectId").withSchema(TableName.Environment),
|
||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||
db.ref("name").withSchema(TableName.Environment).as("envName")
|
||||
)
|
||||
|
||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
|
||||
db.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
|
||||
db.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
|
||||
db.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
|
||||
db.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
|
||||
db.ref("allowedSelfApprovals").withSchema(TableName.AccessApprovalPolicy).as("policyAllowedSelfApprovals"),
|
||||
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId"),
|
||||
db.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
||||
)
|
||||
.select(
|
||||
db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
|
||||
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
|
||||
)
|
||||
|
||||
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||
.select(db.ref("userId").withSchema(TableName.UserGroupMembership).as("approverGroupUserId"))
|
||||
// TODO: ADD SUPPORT FOR GROUPS!!!!
|
||||
.select(
|
||||
db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
|
||||
db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
|
||||
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
|
||||
db.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
|
||||
|
||||
.select(db.ref("bypasserUserId").withSchema(TableName.AccessApprovalPolicyBypasser))
|
||||
.select(db.ref("userId").withSchema("bypasserUserGroupMembership").as("bypasserGroupUserId"))
|
||||
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeUserId"),
|
||||
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeMembershipId"),
|
||||
|
||||
.select(
|
||||
db.ref("projectId").withSchema(TableName.Environment),
|
||||
db.ref("slug").withSchema(TableName.Environment).as("envSlug"),
|
||||
db.ref("name").withSchema(TableName.Environment).as("envName")
|
||||
)
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
|
||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("privilegeTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("privilegeTemporaryAccessEndTime"),
|
||||
|
||||
.select(
|
||||
db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
|
||||
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
|
||||
)
|
||||
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegePermissions")
|
||||
)
|
||||
.orderBy(`${TableName.AccessApprovalRequest}.createdAt`, "desc");
|
||||
|
||||
// TODO: ADD SUPPORT FOR GROUPS!!!!
|
||||
.select(
|
||||
db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
|
||||
db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
|
||||
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
|
||||
db.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
|
||||
const formattedDocs = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (doc) => ({
|
||||
...AccessApprovalRequestsSchema.parse(doc),
|
||||
projectId: doc.projectId,
|
||||
environment: doc.envSlug,
|
||||
environmentName: doc.envName,
|
||||
policy: {
|
||||
id: doc.policyId,
|
||||
name: doc.policyName,
|
||||
approvals: doc.policyApprovals,
|
||||
secretPath: doc.policySecretPath,
|
||||
enforcementLevel: doc.policyEnforcementLevel,
|
||||
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
|
||||
envId: doc.policyEnvId,
|
||||
deletedAt: doc.policyDeletedAt
|
||||
},
|
||||
requestedByUser: {
|
||||
userId: doc.requestedByUserId,
|
||||
email: doc.requestedByUserEmail,
|
||||
firstName: doc.requestedByUserFirstName,
|
||||
lastName: doc.requestedByUserLastName,
|
||||
username: doc.requestedByUserUsername
|
||||
},
|
||||
privilege: doc.privilegeId
|
||||
? {
|
||||
membershipId: doc.privilegeMembershipId,
|
||||
userId: doc.privilegeUserId,
|
||||
projectId: doc.projectId,
|
||||
isTemporary: doc.privilegeIsTemporary,
|
||||
temporaryMode: doc.privilegeTemporaryMode,
|
||||
temporaryRange: doc.privilegeTemporaryRange,
|
||||
temporaryAccessStartTime: doc.privilegeTemporaryAccessStartTime,
|
||||
temporaryAccessEndTime: doc.privilegeTemporaryAccessEndTime,
|
||||
permissions: doc.privilegePermissions
|
||||
}
|
||||
: null,
|
||||
isApproved: doc.status === ApprovalStatus.APPROVED
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "reviewerUserId",
|
||||
label: "reviewers" as const,
|
||||
mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined)
|
||||
},
|
||||
{
|
||||
key: "approverUserId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({ approverUserId, approverSequence, approvalsRequired, approverUsername, approverEmail }) => ({
|
||||
userId: approverUserId,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired,
|
||||
email: approverEmail,
|
||||
username: approverUsername
|
||||
})
|
||||
},
|
||||
{
|
||||
key: "approverGroupUserId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({
|
||||
approverGroupUserId,
|
||||
approverSequence,
|
||||
approvalsRequired,
|
||||
approverGroupEmail,
|
||||
approverGroupUsername
|
||||
}) => ({
|
||||
userId: approverGroupUserId,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired,
|
||||
email: approverGroupEmail,
|
||||
username: approverGroupUsername
|
||||
})
|
||||
},
|
||||
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
|
||||
{
|
||||
key: "bypasserGroupUserId",
|
||||
label: "bypassers" as const,
|
||||
mapper: ({ bypasserGroupUserId }) => bypasserGroupUserId
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeUserId"),
|
||||
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeMembershipId"),
|
||||
if (!formattedDocs) return [];
|
||||
|
||||
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
|
||||
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
|
||||
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
|
||||
db
|
||||
.ref("temporaryAccessStartTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("privilegeTemporaryAccessStartTime"),
|
||||
db
|
||||
.ref("temporaryAccessEndTime")
|
||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||
.as("privilegeTemporaryAccessEndTime"),
|
||||
|
||||
db.ref("permissions").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegePermissions")
|
||||
)
|
||||
.orderBy(`${TableName.AccessApprovalRequest}.createdAt`, "desc");
|
||||
|
||||
const formattedDocs = sqlNestRelationships({
|
||||
data: docs,
|
||||
key: "id",
|
||||
parentMapper: (doc) => ({
|
||||
...AccessApprovalRequestsSchema.parse(doc),
|
||||
projectId: doc.projectId,
|
||||
environment: doc.envSlug,
|
||||
environmentName: doc.envName,
|
||||
return formattedDocs.map((doc) => ({
|
||||
...doc,
|
||||
policy: {
|
||||
id: doc.policyId,
|
||||
name: doc.policyName,
|
||||
approvals: doc.policyApprovals,
|
||||
secretPath: doc.policySecretPath,
|
||||
enforcementLevel: doc.policyEnforcementLevel,
|
||||
allowedSelfApprovals: doc.policyAllowedSelfApprovals,
|
||||
envId: doc.policyEnvId,
|
||||
deletedAt: doc.policyDeletedAt
|
||||
},
|
||||
requestedByUser: {
|
||||
userId: doc.requestedByUserId,
|
||||
email: doc.requestedByUserEmail,
|
||||
firstName: doc.requestedByUserFirstName,
|
||||
lastName: doc.requestedByUserLastName,
|
||||
username: doc.requestedByUserUsername
|
||||
},
|
||||
privilege: doc.privilegeId
|
||||
? {
|
||||
membershipId: doc.privilegeMembershipId,
|
||||
userId: doc.privilegeUserId,
|
||||
projectId: doc.projectId,
|
||||
isTemporary: doc.privilegeIsTemporary,
|
||||
temporaryMode: doc.privilegeTemporaryMode,
|
||||
temporaryRange: doc.privilegeTemporaryRange,
|
||||
temporaryAccessStartTime: doc.privilegeTemporaryAccessStartTime,
|
||||
temporaryAccessEndTime: doc.privilegeTemporaryAccessEndTime,
|
||||
permissions: doc.privilegePermissions
|
||||
}
|
||||
: null,
|
||||
|
||||
isApproved: !!doc.policyDeletedAt || !!doc.privilegeId || doc.status !== ApprovalStatus.PENDING
|
||||
}),
|
||||
childrenMapper: [
|
||||
{
|
||||
key: "reviewerUserId",
|
||||
label: "reviewers" as const,
|
||||
mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined)
|
||||
},
|
||||
{ key: "approverUserId", label: "approvers" as const, mapper: ({ approverUserId }) => approverUserId },
|
||||
{
|
||||
key: "approverGroupUserId",
|
||||
label: "approvers" as const,
|
||||
mapper: ({ approverGroupUserId }) => approverGroupUserId
|
||||
},
|
||||
{ key: "bypasserUserId", label: "bypassers" as const, mapper: ({ bypasserUserId }) => bypasserUserId },
|
||||
{
|
||||
key: "bypasserGroupUserId",
|
||||
label: "bypassers" as const,
|
||||
mapper: ({ bypasserGroupUserId }) => bypasserGroupUserId
|
||||
...doc.policy,
|
||||
approvers: doc.approvers.filter((el) => el.userId).sort((a, b) => (a.sequence || 0) - (b.sequence || 0)),
|
||||
bypassers: doc.bypassers
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
if (!formattedDocs) return [];
|
||||
|
||||
return formattedDocs.map((doc) => ({
|
||||
...doc,
|
||||
policy: { ...doc.policy, approvers: doc.approvers, bypassers: doc.bypassers }
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
|
||||
}
|
||||
};
|
||||
}));
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "FindRequestsWithPrivilege" });
|
||||
}
|
||||
};
|
||||
|
||||
const findQuery = (filter: TFindFilter<TAccessApprovalRequests>, tx: Knex) =>
|
||||
tx(TableName.AccessApprovalRequest)
|
||||
@ -272,6 +519,8 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
.select(selectAllTableCols(TableName.AccessApprovalRequest))
|
||||
.select(
|
||||
tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
|
||||
tx.ref("sequence").withSchema(TableName.AccessApprovalPolicyApprover).as("approverSequence"),
|
||||
tx.ref("approvalsRequired").withSchema(TableName.AccessApprovalPolicyApprover),
|
||||
tx.ref("userId").withSchema(TableName.UserGroupMembership),
|
||||
tx.ref("email").withSchema("accessApprovalPolicyApproverUser").as("approverEmail"),
|
||||
tx.ref("email").withSchema("accessApprovalPolicyGroupApproverUser").as("approverGroupEmail"),
|
||||
@ -318,7 +567,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
tx.ref("deletedAt").withSchema(TableName.AccessApprovalPolicy).as("policyDeletedAt")
|
||||
);
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
const findById: TAccessApprovalRequestDALFactory["findById"] = async (id, tx) => {
|
||||
try {
|
||||
const sql = findQuery({ [`${TableName.AccessApprovalRequest}.id` as "id"]: id }, tx || db.replicaNode());
|
||||
const docs = await sql;
|
||||
@ -367,13 +616,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
approverEmail: email,
|
||||
approverUsername: username,
|
||||
approverLastName: lastName,
|
||||
approverFirstName: firstName
|
||||
approverFirstName: firstName,
|
||||
approverSequence,
|
||||
approvalsRequired
|
||||
}) => ({
|
||||
userId: approverUserId,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
username
|
||||
username,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired
|
||||
})
|
||||
},
|
||||
{
|
||||
@ -384,13 +637,17 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
approverGroupEmail: email,
|
||||
approverGroupUsername: username,
|
||||
approverGroupLastName: lastName,
|
||||
approverFirstName: firstName
|
||||
approverFirstName: firstName,
|
||||
approverSequence,
|
||||
approvalsRequired
|
||||
}) => ({
|
||||
userId,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
username
|
||||
username,
|
||||
sequence: approverSequence,
|
||||
approvalsRequired
|
||||
})
|
||||
},
|
||||
{
|
||||
@ -434,7 +691,9 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
...formattedDoc[0],
|
||||
policy: {
|
||||
...formattedDoc[0].policy,
|
||||
approvers: formattedDoc[0].approvers,
|
||||
approvers: formattedDoc[0].approvers
|
||||
.filter((el) => el.userId)
|
||||
.sort((a, b) => (a.sequence || 0) - (b.sequence || 0)),
|
||||
bypassers: formattedDoc[0].bypassers
|
||||
}
|
||||
};
|
||||
@ -443,7 +702,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getCount = async ({ projectId }: { projectId: string }) => {
|
||||
const getCount: TAccessApprovalRequestDALFactory["getCount"] = async ({ projectId }) => {
|
||||
try {
|
||||
const accessRequests = await db
|
||||
.replicaNode()(TableName.AccessApprovalRequest)
|
||||
@ -495,7 +754,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
req.status === ApprovalStatus.PENDING
|
||||
);
|
||||
|
||||
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required
|
||||
// an approval is finalized if there are any rejections, a privilege ID is set or the number of approvals is equal to the number of approvals required.
|
||||
const finalizedApprovals = formattedRequests.filter(
|
||||
(req) =>
|
||||
req.privilegeId ||
|
||||
@ -509,5 +768,27 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
return { ...accessApprovalRequestOrm, findById, findRequestsWithPrivilegeByPolicyIds, getCount };
|
||||
const resetReviewByPolicyId: TAccessApprovalRequestDALFactory["resetReviewByPolicyId"] = async (policyId, tx) => {
|
||||
try {
|
||||
await (tx || db)(TableName.AccessApprovalRequestReviewer)
|
||||
.leftJoin(
|
||||
TableName.AccessApprovalRequest,
|
||||
`${TableName.AccessApprovalRequest}.id`,
|
||||
`${TableName.AccessApprovalRequestReviewer}.requestId`
|
||||
)
|
||||
.where(`${TableName.AccessApprovalRequest}.status` as "status", ApprovalStatus.PENDING)
|
||||
.where(`${TableName.AccessApprovalRequest}.policyId` as "policyId", policyId)
|
||||
.del();
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "ResetReviewByPolicyId" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...accessApprovalRequestOrm,
|
||||
findById,
|
||||
findRequestsWithPrivilegeByPolicyIds,
|
||||
getCount,
|
||||
resetReviewByPolicyId
|
||||
};
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TAccessApprovalRequestReviewerDALFactory = ReturnType<typeof accessApprovalRequestReviewerDALFactory>;
|
||||
export type TAccessApprovalRequestReviewerDALFactory = TOrmify<TableName.AccessApprovalRequestReviewer>;
|
||||
|
||||
export const accessApprovalRequestReviewerDALFactory = (db: TDbClient) => {
|
||||
export const accessApprovalRequestReviewerDALFactory = (db: TDbClient): TAccessApprovalRequestReviewerDALFactory => {
|
||||
const secretApprovalRequestReviewerOrm = ormify(db, TableName.AccessApprovalRequestReviewer);
|
||||
return secretApprovalRequestReviewerOrm;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@ import msFn from "ms";
|
||||
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { groupBy } from "@app/lib/fn";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { EnforcementLevel } from "@app/lib/types";
|
||||
@ -22,19 +23,13 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal";
|
||||
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
|
||||
import { TGroupDALFactory } from "../group/group-dal";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "../project-user-additional-privilege/project-user-additional-privilege-types";
|
||||
import { TAccessApprovalRequestDALFactory } from "./access-approval-request-dal";
|
||||
import { verifyRequestedPermissions } from "./access-approval-request-fns";
|
||||
import { TAccessApprovalRequestReviewerDALFactory } from "./access-approval-request-reviewer-dal";
|
||||
import {
|
||||
ApprovalStatus,
|
||||
TCreateAccessApprovalRequestDTO,
|
||||
TGetAccessRequestCountDTO,
|
||||
TListApprovalRequestsDTO,
|
||||
TReviewAccessRequestDTO
|
||||
} from "./access-approval-request-types";
|
||||
import { ApprovalStatus, TAccessApprovalRequestServiceFactory } from "./access-approval-request-types";
|
||||
|
||||
type TSecretApprovalRequestServiceFactoryDep = {
|
||||
additionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "create" | "findById">;
|
||||
@ -74,8 +69,6 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
||||
projectMicrosoftTeamsConfigDAL: Pick<TProjectMicrosoftTeamsConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||
};
|
||||
|
||||
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
||||
|
||||
export const accessApprovalRequestServiceFactory = ({
|
||||
groupDAL,
|
||||
projectDAL,
|
||||
@ -92,8 +85,8 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
microsoftTeamsService,
|
||||
projectMicrosoftTeamsConfigDAL,
|
||||
projectSlackConfigDAL
|
||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||
const createAccessApprovalRequest = async ({
|
||||
}: TSecretApprovalRequestServiceFactoryDep): TAccessApprovalRequestServiceFactory => {
|
||||
const createAccessApprovalRequest: TAccessApprovalRequestServiceFactory["createAccessApprovalRequest"] = async ({
|
||||
isTemporary,
|
||||
temporaryRange,
|
||||
actorId,
|
||||
@ -103,7 +96,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
projectSlug,
|
||||
note
|
||||
}: TCreateAccessApprovalRequestDTO) => {
|
||||
}) => {
|
||||
const cfg = getConfig();
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
@ -280,7 +273,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
return { request: approval };
|
||||
};
|
||||
|
||||
const listApprovalRequests = async ({
|
||||
const listApprovalRequests: TAccessApprovalRequestServiceFactory["listApprovalRequests"] = async ({
|
||||
projectSlug,
|
||||
authorProjectMembershipId,
|
||||
envSlug,
|
||||
@ -288,7 +281,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorOrgId,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TListApprovalRequestsDTO) => {
|
||||
}) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
|
||||
@ -318,7 +311,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
return { requests };
|
||||
};
|
||||
|
||||
const reviewAccessRequest = async ({
|
||||
const reviewAccessRequest: TAccessApprovalRequestServiceFactory["reviewAccessRequest"] = async ({
|
||||
requestId,
|
||||
actor,
|
||||
status,
|
||||
@ -326,7 +319,7 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
bypassReason
|
||||
}: TReviewAccessRequestDTO) => {
|
||||
}) => {
|
||||
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
|
||||
if (!accessApprovalRequest) {
|
||||
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
|
||||
@ -358,7 +351,6 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
const cannotBypassUnderSoftEnforcement = !(isSoftEnforcement && canBypass);
|
||||
|
||||
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
|
||||
|
||||
// If user is (not an approver OR cant self approve) AND can't bypass policy
|
||||
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
|
||||
throw new BadRequestError({
|
||||
@ -380,8 +372,44 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
}
|
||||
|
||||
const existingReviews = await accessApprovalRequestReviewerDAL.find({ requestId: accessApprovalRequest.id });
|
||||
if (existingReviews.some((review) => review.status === ApprovalStatus.REJECTED)) {
|
||||
throw new BadRequestError({ message: "The request has already been rejected by another reviewer" });
|
||||
if (accessApprovalRequest.status !== ApprovalStatus.PENDING) {
|
||||
throw new BadRequestError({ message: "The request has been closed" });
|
||||
}
|
||||
|
||||
const reviewsGroupById = groupBy(
|
||||
existingReviews.filter((review) => review.status === ApprovalStatus.APPROVED),
|
||||
(i) => i.reviewerUserId
|
||||
);
|
||||
|
||||
const approvedSequences = policy.approvers.reduce(
|
||||
(acc, curr) => {
|
||||
const hasApproved = reviewsGroupById?.[curr.userId as string]?.[0];
|
||||
if (acc?.[acc.length - 1]?.step === curr.sequence) {
|
||||
if (hasApproved) {
|
||||
acc[acc.length - 1].approvals += 1;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.push({
|
||||
step: curr.sequence || 1,
|
||||
approvals: hasApproved ? 1 : 0,
|
||||
requiredApprovals: curr.approvalsRequired || 1
|
||||
});
|
||||
return acc;
|
||||
},
|
||||
[] as { step: number; approvals: number; requiredApprovals: number }[]
|
||||
);
|
||||
const presentSequence = approvedSequences.find((el) => el.approvals < el.requiredApprovals) || {
|
||||
step: 1,
|
||||
approvals: 0,
|
||||
requiredApprovals: 1
|
||||
};
|
||||
if (presentSequence) {
|
||||
const isApproverOfTheSequence = policy.approvers.find(
|
||||
(el) => el.sequence === presentSequence.step && el.userId === actorId
|
||||
);
|
||||
if (!isApproverOfTheSequence) throw new BadRequestError({ message: "You are not reviewer in this step" });
|
||||
}
|
||||
|
||||
const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => {
|
||||
@ -426,11 +454,14 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
);
|
||||
}
|
||||
|
||||
const otherReviews = existingReviews.filter((er) => er.reviewerUserId !== actorId);
|
||||
const allUniqueReviews = [...otherReviews, reviewForThisActorProcessing];
|
||||
if (status === ApprovalStatus.REJECTED) {
|
||||
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { status: ApprovalStatus.REJECTED }, tx);
|
||||
return reviewForThisActorProcessing;
|
||||
}
|
||||
|
||||
const approvedReviews = allUniqueReviews.filter((r) => r.status === ApprovalStatus.APPROVED);
|
||||
const meetsStandardApprovalThreshold = approvedReviews.length >= policy.approvals;
|
||||
const meetsStandardApprovalThreshold =
|
||||
(presentSequence?.approvals || 0) + 1 >= presentSequence.requiredApprovals &&
|
||||
approvedSequences.at(-1)?.step === presentSequence?.step;
|
||||
|
||||
if (
|
||||
reviewForThisActorProcessing.status === ApprovalStatus.APPROVED &&
|
||||
@ -527,7 +558,13 @@ export const accessApprovalRequestServiceFactory = ({
|
||||
return reviewStatus;
|
||||
};
|
||||
|
||||
const getCount = async ({ projectSlug, actor, actorAuthMethod, actorId, actorOrgId }: TGetAccessRequestCountDTO) => {
|
||||
const getCount: TAccessApprovalRequestServiceFactory["getCount"] = async ({
|
||||
projectSlug,
|
||||
actor,
|
||||
actorAuthMethod,
|
||||
actorId,
|
||||
actorOrgId
|
||||
}) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||
|
||||
|
@ -34,3 +34,124 @@ export type TListApprovalRequestsDTO = {
|
||||
authorProjectMembershipId?: string;
|
||||
envSlug?: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export interface TAccessApprovalRequestServiceFactory {
|
||||
createAccessApprovalRequest: (arg: TCreateAccessApprovalRequestDTO) => Promise<{
|
||||
request: {
|
||||
status: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
policyId: string;
|
||||
isTemporary: boolean;
|
||||
requestedByUserId: string;
|
||||
privilegeId?: string | null | undefined;
|
||||
requestedBy?: string | null | undefined;
|
||||
temporaryRange?: string | null | undefined;
|
||||
permissions?: unknown;
|
||||
note?: string | null | undefined;
|
||||
privilegeDeletedAt?: Date | null | undefined;
|
||||
};
|
||||
}>;
|
||||
listApprovalRequests: (arg: TListApprovalRequestsDTO) => Promise<{
|
||||
requests: {
|
||||
policy: {
|
||||
approvers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
id: string;
|
||||
name: string;
|
||||
approvals: number;
|
||||
secretPath: string | null | undefined;
|
||||
enforcementLevel: string;
|
||||
allowedSelfApprovals: boolean;
|
||||
envId: string;
|
||||
deletedAt: Date | null | undefined;
|
||||
};
|
||||
projectId: string;
|
||||
environment: string;
|
||||
environmentName: string;
|
||||
requestedByUser: {
|
||||
userId: string;
|
||||
email: string | null | undefined;
|
||||
firstName: string | null | undefined;
|
||||
lastName: string | null | undefined;
|
||||
username: string;
|
||||
};
|
||||
privilege: {
|
||||
membershipId: string;
|
||||
userId: string;
|
||||
projectId: string;
|
||||
isTemporary: boolean;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
permissions: unknown;
|
||||
} | null;
|
||||
isApproved: boolean;
|
||||
status: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
policyId: string;
|
||||
isTemporary: boolean;
|
||||
requestedByUserId: string;
|
||||
privilegeId?: string | null | undefined;
|
||||
requestedBy?: string | null | undefined;
|
||||
temporaryRange?: string | null | undefined;
|
||||
permissions?: unknown;
|
||||
note?: string | null | undefined;
|
||||
privilegeDeletedAt?: Date | null | undefined;
|
||||
reviewers: {
|
||||
userId: string;
|
||||
status: string;
|
||||
}[];
|
||||
approvers: (
|
||||
| {
|
||||
userId: string | null | undefined;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
| {
|
||||
userId: string;
|
||||
sequence: number | null | undefined;
|
||||
approvalsRequired: number | null | undefined;
|
||||
email: string | null | undefined;
|
||||
username: string;
|
||||
}
|
||||
)[];
|
||||
bypassers: string[];
|
||||
}[];
|
||||
}>;
|
||||
reviewAccessRequest: (arg: TReviewAccessRequestDTO) => Promise<{
|
||||
id: string;
|
||||
requestId: string;
|
||||
reviewerUserId: string;
|
||||
status: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}>;
|
||||
getCount: (arg: TGetAccessRequestCountDTO) => Promise<{
|
||||
count: {
|
||||
pendingCount: number;
|
||||
finalizedCount: number;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
export * from "./oracledb-connection-enums";
|
||||
export * from "./oracledb-connection-fns";
|
||||
export * from "./oracledb-connection-schemas";
|
||||
export * from "./oracledb-connection-types";
|
@ -0,0 +1,3 @@
|
||||
export enum OracleDBConnectionMethod {
|
||||
UsernameAndPassword = "username-and-password"
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
import { OracleDBConnectionMethod } from "./oracledb-connection-enums";
|
||||
|
||||
export const getOracleDBConnectionListItem = () => {
|
||||
return {
|
||||
name: "OracleDB" as const,
|
||||
app: AppConnection.OracleDB as const,
|
||||
methods: Object.values(OracleDBConnectionMethod) as [OracleDBConnectionMethod.UsernameAndPassword],
|
||||
supportsPlatformManagement: true as const
|
||||
};
|
||||
};
|
@ -0,0 +1,64 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnections } from "@app/lib/api-docs";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import {
|
||||
BaseAppConnectionSchema,
|
||||
GenericCreateAppConnectionFieldsSchema,
|
||||
GenericUpdateAppConnectionFieldsSchema
|
||||
} from "@app/services/app-connection/app-connection-schemas";
|
||||
import { BaseSqlUsernameAndPasswordConnectionSchema } from "@app/services/app-connection/shared/sql";
|
||||
|
||||
import { OracleDBConnectionMethod } from "./oracledb-connection-enums";
|
||||
|
||||
export const OracleDBConnectionCredentialsSchema = BaseSqlUsernameAndPasswordConnectionSchema;
|
||||
|
||||
const BaseOracleDBConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.OracleDB) });
|
||||
|
||||
export const OracleDBConnectionSchema = BaseOracleDBConnectionSchema.extend({
|
||||
method: z.literal(OracleDBConnectionMethod.UsernameAndPassword),
|
||||
credentials: OracleDBConnectionCredentialsSchema
|
||||
});
|
||||
|
||||
export const SanitizedOracleDBConnectionSchema = z.discriminatedUnion("method", [
|
||||
BaseOracleDBConnectionSchema.extend({
|
||||
method: z.literal(OracleDBConnectionMethod.UsernameAndPassword),
|
||||
credentials: OracleDBConnectionCredentialsSchema.pick({
|
||||
host: true,
|
||||
database: true,
|
||||
port: true,
|
||||
username: true,
|
||||
sslEnabled: true,
|
||||
sslRejectUnauthorized: true,
|
||||
sslCertificate: true
|
||||
})
|
||||
})
|
||||
]);
|
||||
|
||||
export const ValidateOracleDBConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||
z.object({
|
||||
method: z
|
||||
.literal(OracleDBConnectionMethod.UsernameAndPassword)
|
||||
.describe(AppConnections.CREATE(AppConnection.OracleDB).method),
|
||||
credentials: OracleDBConnectionCredentialsSchema.describe(AppConnections.CREATE(AppConnection.OracleDB).credentials)
|
||||
})
|
||||
]);
|
||||
|
||||
export const CreateOracleDBConnectionSchema = ValidateOracleDBConnectionCredentialsSchema.and(
|
||||
GenericCreateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true })
|
||||
);
|
||||
|
||||
export const UpdateOracleDBConnectionSchema = z
|
||||
.object({
|
||||
credentials: OracleDBConnectionCredentialsSchema.optional().describe(
|
||||
AppConnections.UPDATE(AppConnection.OracleDB).credentials
|
||||
)
|
||||
})
|
||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.OracleDB, { supportsPlatformManagedCredentials: true }));
|
||||
|
||||
export const OracleDBConnectionListItemSchema = z.object({
|
||||
name: z.literal("OracleDB"),
|
||||
app: z.literal(AppConnection.OracleDB),
|
||||
methods: z.nativeEnum(OracleDBConnectionMethod).array(),
|
||||
supportsPlatformManagement: z.literal(true)
|
||||
});
|
@ -0,0 +1,17 @@
|
||||
import z from "zod";
|
||||
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
|
||||
import {
|
||||
CreateOracleDBConnectionSchema,
|
||||
OracleDBConnectionSchema,
|
||||
ValidateOracleDBConnectionCredentialsSchema
|
||||
} from "./oracledb-connection-schemas";
|
||||
|
||||
export type TOracleDBConnection = z.infer<typeof OracleDBConnectionSchema>;
|
||||
|
||||
export type TOracleDBConnectionInput = z.infer<typeof CreateOracleDBConnectionSchema> & {
|
||||
app: AppConnection.OracleDB;
|
||||
};
|
||||
|
||||
export type TValidateOracleDBConnectionCredentialsSchema = typeof ValidateOracleDBConnectionCredentialsSchema;
|
@ -7,29 +7,30 @@ import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSub
|
||||
} from "../permission/project-permission";
|
||||
import { TAssumeProjectPrivilegeDTO } from "./assume-privilege-types";
|
||||
import { TAssumePrivilegeServiceFactory } from "./assume-privilege-types";
|
||||
|
||||
type TAssumePrivilegeServiceFactoryDep = {
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export type TAssumePrivilegeServiceFactory = ReturnType<typeof assumePrivilegeServiceFactory>;
|
||||
|
||||
export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }: TAssumePrivilegeServiceFactoryDep) => {
|
||||
const assumeProjectPrivileges = async ({
|
||||
export const assumePrivilegeServiceFactory = ({
|
||||
projectDAL,
|
||||
permissionService
|
||||
}: TAssumePrivilegeServiceFactoryDep): TAssumePrivilegeServiceFactory => {
|
||||
const assumeProjectPrivileges: TAssumePrivilegeServiceFactory["assumeProjectPrivileges"] = async ({
|
||||
targetActorType,
|
||||
targetActorId,
|
||||
projectId,
|
||||
actorPermissionDetails,
|
||||
tokenVersionId
|
||||
}: TAssumeProjectPrivilegeDTO) => {
|
||||
}) => {
|
||||
const project = await projectDAL.findById(projectId);
|
||||
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
@ -79,7 +80,10 @@ export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }:
|
||||
return { actorType: targetActorType, actorId: targetActorId, projectId, assumePrivilegesToken };
|
||||
};
|
||||
|
||||
const verifyAssumePrivilegeToken = (token: string, tokenVersionId: string) => {
|
||||
const verifyAssumePrivilegeToken: TAssumePrivilegeServiceFactory["verifyAssumePrivilegeToken"] = (
|
||||
token,
|
||||
tokenVersionId
|
||||
) => {
|
||||
const appCfg = getConfig();
|
||||
const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as {
|
||||
tokenVersionId: string;
|
||||
|
@ -8,3 +8,28 @@ export type TAssumeProjectPrivilegeDTO = {
|
||||
tokenVersionId: string;
|
||||
actorPermissionDetails: OrgServiceActor;
|
||||
};
|
||||
|
||||
export interface TAssumePrivilegeServiceFactory {
|
||||
assumeProjectPrivileges: ({
|
||||
targetActorType,
|
||||
targetActorId,
|
||||
projectId,
|
||||
actorPermissionDetails,
|
||||
tokenVersionId
|
||||
}: TAssumeProjectPrivilegeDTO) => Promise<{
|
||||
actorType: ActorType.USER | ActorType.IDENTITY;
|
||||
actorId: string;
|
||||
projectId: string;
|
||||
assumePrivilegesToken: string;
|
||||
}>;
|
||||
verifyAssumePrivilegeToken: (
|
||||
token: string,
|
||||
tokenVersionId: string
|
||||
) => {
|
||||
tokenVersionId: string;
|
||||
projectId: string;
|
||||
requesterId: string;
|
||||
actorType: ActorType;
|
||||
actorId: string;
|
||||
};
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TAuditLogStreamDALFactory = ReturnType<typeof auditLogStreamDALFactory>;
|
||||
export type TAuditLogStreamDALFactory = TOrmify<TableName.AuditLogStream>;
|
||||
|
||||
export const auditLogStreamDALFactory = (db: TDbClient) => {
|
||||
export const auditLogStreamDALFactory = (db: TDbClient): TAuditLogStreamDALFactory => {
|
||||
const orm = ormify(db, TableName.AuditLogStream);
|
||||
|
||||
return orm;
|
||||
|
@ -11,16 +11,9 @@ import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { AUDIT_LOG_STREAM_TIMEOUT } from "../audit-log/audit-log-queue";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TAuditLogStreamDALFactory } from "./audit-log-stream-dal";
|
||||
import {
|
||||
LogStreamHeaders,
|
||||
TCreateAuditLogStreamDTO,
|
||||
TDeleteAuditLogStreamDTO,
|
||||
TGetDetailsAuditLogStreamDTO,
|
||||
TListAuditLogStreamDTO,
|
||||
TUpdateAuditLogStreamDTO
|
||||
} from "./audit-log-stream-types";
|
||||
import { LogStreamHeaders, TAuditLogStreamServiceFactory } from "./audit-log-stream-types";
|
||||
|
||||
type TAuditLogStreamServiceFactoryDep = {
|
||||
auditLogStreamDAL: TAuditLogStreamDALFactory;
|
||||
@ -28,21 +21,19 @@ type TAuditLogStreamServiceFactoryDep = {
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TAuditLogStreamServiceFactory = ReturnType<typeof auditLogStreamServiceFactory>;
|
||||
|
||||
export const auditLogStreamServiceFactory = ({
|
||||
auditLogStreamDAL,
|
||||
permissionService,
|
||||
licenseService
|
||||
}: TAuditLogStreamServiceFactoryDep) => {
|
||||
const create = async ({
|
||||
}: TAuditLogStreamServiceFactoryDep): TAuditLogStreamServiceFactory => {
|
||||
const create: TAuditLogStreamServiceFactory["create"] = async ({
|
||||
url,
|
||||
actor,
|
||||
headers = [],
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TCreateAuditLogStreamDTO) => {
|
||||
}) => {
|
||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
@ -110,7 +101,7 @@ export const auditLogStreamServiceFactory = ({
|
||||
return logStream;
|
||||
};
|
||||
|
||||
const updateById = async ({
|
||||
const updateById: TAuditLogStreamServiceFactory["updateById"] = async ({
|
||||
id,
|
||||
url,
|
||||
actor,
|
||||
@ -118,7 +109,7 @@ export const auditLogStreamServiceFactory = ({
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}: TUpdateAuditLogStreamDTO) => {
|
||||
}) => {
|
||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
|
||||
|
||||
const plan = await licenseService.getPlan(actorOrgId);
|
||||
@ -175,7 +166,13 @@ export const auditLogStreamServiceFactory = ({
|
||||
return updatedLogStream;
|
||||
};
|
||||
|
||||
const deleteById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TDeleteAuditLogStreamDTO) => {
|
||||
const deleteById: TAuditLogStreamServiceFactory["deleteById"] = async ({
|
||||
id,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}) => {
|
||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
|
||||
|
||||
const logStream = await auditLogStreamDAL.findById(id);
|
||||
@ -189,7 +186,13 @@ export const auditLogStreamServiceFactory = ({
|
||||
return deletedLogStream;
|
||||
};
|
||||
|
||||
const getById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetDetailsAuditLogStreamDTO) => {
|
||||
const getById: TAuditLogStreamServiceFactory["getById"] = async ({
|
||||
id,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod
|
||||
}) => {
|
||||
const logStream = await auditLogStreamDAL.findById(id);
|
||||
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
|
||||
|
||||
@ -212,7 +215,7 @@ export const auditLogStreamServiceFactory = ({
|
||||
return { ...logStream, headers };
|
||||
};
|
||||
|
||||
const list = async ({ actor, actorId, actorOrgId, actorAuthMethod }: TListAuditLogStreamDTO) => {
|
||||
const list: TAuditLogStreamServiceFactory["list"] = async ({ actor, actorId, actorOrgId, actorAuthMethod }) => {
|
||||
const { permission } = await permissionService.getOrgPermission(
|
||||
actor,
|
||||
actorId,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { TAuditLogStreams } from "@app/db/schemas";
|
||||
import { TOrgPermission } from "@app/lib/types";
|
||||
|
||||
export type LogStreamHeaders = {
|
||||
@ -25,3 +26,23 @@ export type TListAuditLogStreamDTO = Omit<TOrgPermission, "orgId">;
|
||||
export type TGetDetailsAuditLogStreamDTO = Omit<TOrgPermission, "orgId"> & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type TAuditLogStreamServiceFactory = {
|
||||
create: (arg: TCreateAuditLogStreamDTO) => Promise<TAuditLogStreams>;
|
||||
updateById: (arg: TUpdateAuditLogStreamDTO) => Promise<TAuditLogStreams>;
|
||||
deleteById: (arg: TDeleteAuditLogStreamDTO) => Promise<TAuditLogStreams>;
|
||||
getById: (arg: TGetDetailsAuditLogStreamDTO) => Promise<{
|
||||
headers: LogStreamHeaders[] | undefined;
|
||||
orgId: string;
|
||||
url: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
encryptedHeadersCiphertext?: string | null | undefined;
|
||||
encryptedHeadersIV?: string | null | undefined;
|
||||
encryptedHeadersTag?: string | null | undefined;
|
||||
encryptedHeadersAlgorithm?: string | null | undefined;
|
||||
encryptedHeadersKeyEncoding?: string | null | undefined;
|
||||
}>;
|
||||
list: (arg: TListAuditLogStreamDTO) => Promise<TAuditLogStreams[]>;
|
||||
};
|
||||
|
@ -2,16 +2,29 @@
|
||||
import knex from "knex";
|
||||
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { TableName, TAuditLogs } from "@app/db/schemas";
|
||||
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
|
||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
import { ormify, selectAllTableCols, TOrmify } from "@app/lib/knex";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { QueueName } from "@app/queue";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
import { EventType, filterableSecretEvents } from "./audit-log-types";
|
||||
|
||||
export type TAuditLogDALFactory = ReturnType<typeof auditLogDALFactory>;
|
||||
export interface TAuditLogDALFactory extends Omit<TOrmify<TableName.AuditLog>, "find"> {
|
||||
pruneAuditLog: (tx?: knex.Knex) => Promise<void>;
|
||||
find: (
|
||||
arg: Omit<TFindQuery, "actor" | "eventType"> & {
|
||||
actorId?: string | undefined;
|
||||
actorType?: ActorType | undefined;
|
||||
secretPath?: string | undefined;
|
||||
secretKey?: string | undefined;
|
||||
eventType?: EventType[] | undefined;
|
||||
eventMetadata?: Record<string, string> | undefined;
|
||||
},
|
||||
tx?: knex.Knex
|
||||
) => Promise<TAuditLogs[]>;
|
||||
}
|
||||
|
||||
type TFindQuery = {
|
||||
actor?: string;
|
||||
@ -29,7 +42,7 @@ type TFindQuery = {
|
||||
export const auditLogDALFactory = (db: TDbClient) => {
|
||||
const auditLogOrm = ormify(db, TableName.AuditLog);
|
||||
|
||||
const find = async (
|
||||
const find: TAuditLogDALFactory["find"] = async (
|
||||
{
|
||||
orgId,
|
||||
projectId,
|
||||
@ -45,15 +58,8 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
secretKey,
|
||||
eventType,
|
||||
eventMetadata
|
||||
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
||||
actorId?: string;
|
||||
actorType?: ActorType;
|
||||
secretPath?: string;
|
||||
secretKey?: string;
|
||||
eventType?: EventType[];
|
||||
eventMetadata?: Record<string, string>;
|
||||
},
|
||||
tx?: knex.Knex
|
||||
tx
|
||||
) => {
|
||||
if (!orgId && !projectId) {
|
||||
throw new Error("Either orgId or projectId must be provided");
|
||||
@ -154,7 +160,7 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
||||
};
|
||||
|
||||
// delete all audit log that have expired
|
||||
const pruneAuditLog = async (tx?: knex.Knex) => {
|
||||
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => {
|
||||
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
|
||||
const MAX_RETRY_ON_FAILURE = 3;
|
||||
|
||||
|
@ -21,7 +21,9 @@ type TAuditLogQueueServiceFactoryDep = {
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
};
|
||||
|
||||
export type TAuditLogQueueServiceFactory = Awaited<ReturnType<typeof auditLogQueueServiceFactory>>;
|
||||
export type TAuditLogQueueServiceFactory = {
|
||||
pushToLog: (data: TCreateAuditLogDTO) => Promise<void>;
|
||||
};
|
||||
|
||||
// keep this timeout 5s it must be fast because else the queue will take time to finish
|
||||
// audit log is a crowded queue thus needs to be fast
|
||||
@ -33,7 +35,7 @@ export const auditLogQueueServiceFactory = async ({
|
||||
projectDAL,
|
||||
licenseService,
|
||||
auditLogStreamDAL
|
||||
}: TAuditLogQueueServiceFactoryDep) => {
|
||||
}: TAuditLogQueueServiceFactoryDep): Promise<TAuditLogQueueServiceFactory> => {
|
||||
const appCfg = getConfig();
|
||||
|
||||
const pushToLog = async (data: TCreateAuditLogDTO) => {
|
||||
|
@ -7,11 +7,11 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TAuditLogDALFactory } from "./audit-log-dal";
|
||||
import { TAuditLogQueueServiceFactory } from "./audit-log-queue";
|
||||
import { EventType, TCreateAuditLogDTO, TListProjectAuditLogDTO } from "./audit-log-types";
|
||||
import { EventType, TAuditLogServiceFactory } from "./audit-log-types";
|
||||
|
||||
type TAuditLogServiceFactoryDep = {
|
||||
auditLogDAL: TAuditLogDALFactory;
|
||||
@ -19,14 +19,18 @@ type TAuditLogServiceFactoryDep = {
|
||||
auditLogQueue: TAuditLogQueueServiceFactory;
|
||||
};
|
||||
|
||||
export type TAuditLogServiceFactory = ReturnType<typeof auditLogServiceFactory>;
|
||||
|
||||
export const auditLogServiceFactory = ({
|
||||
auditLogDAL,
|
||||
auditLogQueue,
|
||||
permissionService
|
||||
}: TAuditLogServiceFactoryDep) => {
|
||||
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
||||
}: TAuditLogServiceFactoryDep): TAuditLogServiceFactory => {
|
||||
const listAuditLogs: TAuditLogServiceFactory["listAuditLogs"] = async ({
|
||||
actorAuthMethod,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
filter
|
||||
}) => {
|
||||
// Filter logs for specific project
|
||||
if (filter.projectId) {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
@ -75,7 +79,7 @@ export const auditLogServiceFactory = ({
|
||||
}));
|
||||
};
|
||||
|
||||
const createAuditLog = async (data: TCreateAuditLogDTO) => {
|
||||
const createAuditLog: TAuditLogServiceFactory["createAuditLog"] = async (data) => {
|
||||
const appCfg = getConfig();
|
||||
if (appCfg.DISABLE_AUDIT_LOG_GENERATION) {
|
||||
return;
|
||||
|
@ -82,6 +82,32 @@ export type TCreateAuditLogDTO = {
|
||||
projectId?: string;
|
||||
} & BaseAuthData;
|
||||
|
||||
export type TAuditLogServiceFactory = {
|
||||
createAuditLog: (data: TCreateAuditLogDTO) => Promise<void>;
|
||||
listAuditLogs: (arg: TListProjectAuditLogDTO) => Promise<
|
||||
{
|
||||
event: {
|
||||
type: string;
|
||||
metadata: unknown;
|
||||
};
|
||||
actor: {
|
||||
type: string;
|
||||
metadata: unknown;
|
||||
};
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
orgId?: string | null | undefined;
|
||||
userAgent?: string | null | undefined;
|
||||
expiresAt?: Date | null | undefined;
|
||||
ipAddress?: string | null | undefined;
|
||||
userAgentType?: string | null | undefined;
|
||||
projectId?: string | null | undefined;
|
||||
projectName?: string | null | undefined;
|
||||
}[]
|
||||
>;
|
||||
};
|
||||
|
||||
export type AuditLogInfo = Pick<TCreateAuditLogDTO, "userAgent" | "userAgentType" | "ipAddress" | "actor">;
|
||||
|
||||
interface BaseAuthData {
|
||||
@ -170,6 +196,12 @@ export enum EventType {
|
||||
REVOKE_IDENTITY_GCP_AUTH = "revoke-identity-gcp-auth",
|
||||
GET_IDENTITY_GCP_AUTH = "get-identity-gcp-auth",
|
||||
|
||||
LOGIN_IDENTITY_ALICLOUD_AUTH = "login-identity-alicloud-auth",
|
||||
ADD_IDENTITY_ALICLOUD_AUTH = "add-identity-alicloud-auth",
|
||||
UPDATE_IDENTITY_ALICLOUD_AUTH = "update-identity-alicloud-auth",
|
||||
REVOKE_IDENTITY_ALICLOUD_AUTH = "revoke-identity-alicloud-auth",
|
||||
GET_IDENTITY_ALICLOUD_AUTH = "get-identity-alicloud-auth",
|
||||
|
||||
LOGIN_IDENTITY_AWS_AUTH = "login-identity-aws-auth",
|
||||
ADD_IDENTITY_AWS_AUTH = "add-identity-aws-auth",
|
||||
UPDATE_IDENTITY_AWS_AUTH = "update-identity-aws-auth",
|
||||
@ -748,6 +780,7 @@ interface CreateIdentityEvent {
|
||||
metadata: {
|
||||
identityId: string;
|
||||
name: string;
|
||||
hasDeleteProtection: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@ -756,6 +789,7 @@ interface UpdateIdentityEvent {
|
||||
metadata: {
|
||||
identityId: string;
|
||||
name?: string;
|
||||
hasDeleteProtection?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
@ -1060,6 +1094,53 @@ interface GetIdentityAwsAuthEvent {
|
||||
};
|
||||
}
|
||||
|
||||
interface LoginIdentityAliCloudAuthEvent {
|
||||
type: EventType.LOGIN_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
identityAliCloudAuthId: string;
|
||||
identityAccessTokenId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AddIdentityAliCloudAuthEvent {
|
||||
type: EventType.ADD_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL: number;
|
||||
accessTokenMaxTTL: number;
|
||||
accessTokenNumUsesLimit: number;
|
||||
accessTokenTrustedIps: Array<TIdentityTrustedIp>;
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteIdentityAliCloudAuthEvent {
|
||||
type: EventType.REVOKE_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface UpdateIdentityAliCloudAuthEvent {
|
||||
type: EventType.UPDATE_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
allowedArns: string;
|
||||
accessTokenTTL?: number;
|
||||
accessTokenMaxTTL?: number;
|
||||
accessTokenNumUsesLimit?: number;
|
||||
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||
};
|
||||
}
|
||||
|
||||
interface GetIdentityAliCloudAuthEvent {
|
||||
type: EventType.GET_IDENTITY_ALICLOUD_AUTH;
|
||||
metadata: {
|
||||
identityId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface LoginIdentityOciAuthEvent {
|
||||
type: EventType.LOGIN_IDENTITY_OCI_AUTH;
|
||||
metadata: {
|
||||
@ -3272,6 +3353,11 @@ export type Event =
|
||||
| UpdateIdentityAwsAuthEvent
|
||||
| GetIdentityAwsAuthEvent
|
||||
| DeleteIdentityAwsAuthEvent
|
||||
| LoginIdentityAliCloudAuthEvent
|
||||
| AddIdentityAliCloudAuthEvent
|
||||
| UpdateIdentityAliCloudAuthEvent
|
||||
| GetIdentityAliCloudAuthEvent
|
||||
| DeleteIdentityAliCloudAuthEvent
|
||||
| LoginIdentityOciAuthEvent
|
||||
| AddIdentityOciAuthEvent
|
||||
| UpdateIdentityOciAuthEvent
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TCertificateAuthorityCrlDALFactory = ReturnType<typeof certificateAuthorityCrlDALFactory>;
|
||||
export type TCertificateAuthorityCrlDALFactory = TOrmify<TableName.CertificateAuthorityCrl>;
|
||||
|
||||
export const certificateAuthorityCrlDALFactory = (db: TDbClient) => {
|
||||
export const certificateAuthorityCrlDALFactory = (db: TDbClient): TCertificateAuthorityCrlDALFactory => {
|
||||
const caCrlOrm = ormify(db, TableName.CertificateAuthorityCrl);
|
||||
return caCrlOrm;
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ import * as x509 from "@peculiar/x509";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||
import { NotFoundError } from "@app/lib/errors";
|
||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||
@ -12,7 +12,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||
|
||||
import { TGetCaCrlsDTO, TGetCrlById } from "./certificate-authority-crl-types";
|
||||
import { TCertificateAuthorityCrlServiceFactory } from "./certificate-authority-crl-types";
|
||||
|
||||
type TCertificateAuthorityCrlServiceFactoryDep = {
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
|
||||
@ -22,19 +22,17 @@ type TCertificateAuthorityCrlServiceFactoryDep = {
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
};
|
||||
|
||||
export type TCertificateAuthorityCrlServiceFactory = ReturnType<typeof certificateAuthorityCrlServiceFactory>;
|
||||
|
||||
export const certificateAuthorityCrlServiceFactory = ({
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthorityCrlDAL,
|
||||
projectDAL,
|
||||
kmsService,
|
||||
permissionService // licenseService
|
||||
}: TCertificateAuthorityCrlServiceFactoryDep) => {
|
||||
}: TCertificateAuthorityCrlServiceFactoryDep): TCertificateAuthorityCrlServiceFactory => {
|
||||
/**
|
||||
* Return CRL with id [crlId]
|
||||
*/
|
||||
const getCrlById = async (crlId: TGetCrlById) => {
|
||||
const getCrlById: TCertificateAuthorityCrlServiceFactory["getCrlById"] = async (crlId) => {
|
||||
const caCrl = await certificateAuthorityCrlDAL.findById(crlId);
|
||||
if (!caCrl) throw new NotFoundError({ message: `CRL with ID '${crlId}' not found` });
|
||||
|
||||
@ -65,7 +63,13 @@ export const certificateAuthorityCrlServiceFactory = ({
|
||||
/**
|
||||
* Returns a list of CRL ids for CA with id [caId]
|
||||
*/
|
||||
const getCaCrls = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => {
|
||||
const getCaCrls: TCertificateAuthorityCrlServiceFactory["getCaCrls"] = async ({
|
||||
caId,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actor,
|
||||
actorOrgId
|
||||
}) => {
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||
if (!ca?.internalCa?.id) throw new NotFoundError({ message: `Internal CA with ID '${caId}' not found` });
|
||||
|
||||
|
@ -5,3 +5,137 @@ export type TGetCrlById = string;
|
||||
export type TGetCaCrlsDTO = {
|
||||
caId: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TCertificateAuthorityCrlServiceFactory = {
|
||||
getCrlById: (crlId: TGetCrlById) => Promise<{
|
||||
ca: {
|
||||
readonly requireTemplateForIssuance: boolean;
|
||||
readonly internalCa:
|
||||
| {
|
||||
id: string;
|
||||
parentCaId: string | null | undefined;
|
||||
type: string;
|
||||
friendlyName: string;
|
||||
organization: string;
|
||||
ou: string;
|
||||
country: string;
|
||||
province: string;
|
||||
locality: string;
|
||||
commonName: string;
|
||||
dn: string;
|
||||
serialNumber: string | null | undefined;
|
||||
maxPathLength: number | null | undefined;
|
||||
keyAlgorithm: string;
|
||||
notBefore: string | undefined;
|
||||
notAfter: string | undefined;
|
||||
activeCaCertId: string | null | undefined;
|
||||
}
|
||||
| undefined;
|
||||
readonly externalCa:
|
||||
| {
|
||||
id: string;
|
||||
type: string;
|
||||
configuration: unknown;
|
||||
dnsAppConnectionId: string | null | undefined;
|
||||
appConnectionId: string | null | undefined;
|
||||
credentials: Buffer | null | undefined;
|
||||
}
|
||||
| undefined;
|
||||
readonly name: string;
|
||||
readonly status: string;
|
||||
readonly id: string;
|
||||
readonly createdAt: Date;
|
||||
readonly updatedAt: Date;
|
||||
readonly projectId: string;
|
||||
readonly enableDirectIssuance: boolean;
|
||||
readonly parentCaId: string | null | undefined;
|
||||
readonly type: string;
|
||||
readonly friendlyName: string;
|
||||
readonly organization: string;
|
||||
readonly ou: string;
|
||||
readonly country: string;
|
||||
readonly province: string;
|
||||
readonly locality: string;
|
||||
readonly commonName: string;
|
||||
readonly dn: string;
|
||||
readonly serialNumber: string | null | undefined;
|
||||
readonly maxPathLength: number | null | undefined;
|
||||
readonly keyAlgorithm: string;
|
||||
readonly notBefore: string | undefined;
|
||||
readonly notAfter: string | undefined;
|
||||
readonly activeCaCertId: string | null | undefined;
|
||||
};
|
||||
caCrl: {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
caId: string;
|
||||
caSecretId: string;
|
||||
encryptedCrl: Buffer;
|
||||
};
|
||||
crl: ArrayBuffer;
|
||||
}>;
|
||||
getCaCrls: ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => Promise<{
|
||||
ca: {
|
||||
readonly requireTemplateForIssuance: boolean;
|
||||
readonly internalCa:
|
||||
| {
|
||||
id: string;
|
||||
parentCaId: string | null | undefined;
|
||||
type: string;
|
||||
friendlyName: string;
|
||||
organization: string;
|
||||
ou: string;
|
||||
country: string;
|
||||
province: string;
|
||||
locality: string;
|
||||
commonName: string;
|
||||
dn: string;
|
||||
serialNumber: string | null | undefined;
|
||||
maxPathLength: number | null | undefined;
|
||||
keyAlgorithm: string;
|
||||
notBefore: string | undefined;
|
||||
notAfter: string | undefined;
|
||||
activeCaCertId: string | null | undefined;
|
||||
}
|
||||
| undefined;
|
||||
readonly externalCa:
|
||||
| {
|
||||
id: string;
|
||||
type: string;
|
||||
configuration: unknown;
|
||||
dnsAppConnectionId: string | null | undefined;
|
||||
appConnectionId: string | null | undefined;
|
||||
credentials: Buffer | null | undefined;
|
||||
}
|
||||
| undefined;
|
||||
readonly name: string;
|
||||
readonly status: string;
|
||||
readonly id: string;
|
||||
readonly createdAt: Date;
|
||||
readonly updatedAt: Date;
|
||||
readonly projectId: string;
|
||||
readonly enableDirectIssuance: boolean;
|
||||
readonly parentCaId: string | null | undefined;
|
||||
readonly type: string;
|
||||
readonly friendlyName: string;
|
||||
readonly organization: string;
|
||||
readonly ou: string;
|
||||
readonly country: string;
|
||||
readonly province: string;
|
||||
readonly locality: string;
|
||||
readonly commonName: string;
|
||||
readonly dn: string;
|
||||
readonly serialNumber: string | null | undefined;
|
||||
readonly maxPathLength: number | null | undefined;
|
||||
readonly keyAlgorithm: string;
|
||||
readonly notBefore: string | undefined;
|
||||
readonly notAfter: string | undefined;
|
||||
readonly activeCaCertId: string | null | undefined;
|
||||
};
|
||||
crls: {
|
||||
id: string;
|
||||
crl: string;
|
||||
}[];
|
||||
}>;
|
||||
};
|
||||
|
@ -10,6 +10,7 @@ import { TDynamicSecretDALFactory } from "../dynamic-secret/dynamic-secret-dal";
|
||||
import { DynamicSecretStatus } from "../dynamic-secret/dynamic-secret-types";
|
||||
import { DynamicSecretProviders, TDynamicProviderFns } from "../dynamic-secret/providers/models";
|
||||
import { TDynamicSecretLeaseDALFactory } from "./dynamic-secret-lease-dal";
|
||||
import { TDynamicSecretLeaseConfig } from "./dynamic-secret-lease-types";
|
||||
|
||||
type TDynamicSecretLeaseQueueServiceFactoryDep = {
|
||||
queueService: TQueueServiceFactory;
|
||||
@ -134,10 +135,15 @@ export const dynamicSecretLeaseQueueServiceFactory = ({
|
||||
|
||||
await Promise.all(dynamicSecretLeases.map(({ id }) => unsetLeaseRevocation(id)));
|
||||
await Promise.all(
|
||||
dynamicSecretLeases.map(({ externalEntityId }) =>
|
||||
selectedProvider.revoke(decryptedStoredInput, externalEntityId, {
|
||||
projectId: folder.projectId
|
||||
})
|
||||
dynamicSecretLeases.map(({ externalEntityId, config }) =>
|
||||
selectedProvider.revoke(
|
||||
decryptedStoredInput,
|
||||
externalEntityId,
|
||||
{
|
||||
projectId: folder.projectId
|
||||
},
|
||||
config as TDynamicSecretLeaseConfig
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import RE2 from "re2";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
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-types";
|
||||
import {
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionSub
|
||||
@ -29,6 +29,7 @@ import {
|
||||
TCreateDynamicSecretLeaseDTO,
|
||||
TDeleteDynamicSecretLeaseDTO,
|
||||
TDetailsDynamicSecretLeaseDTO,
|
||||
TDynamicSecretLeaseConfig,
|
||||
TListDynamicSecretLeasesDTO,
|
||||
TRenewDynamicSecretLeaseDTO
|
||||
} from "./dynamic-secret-lease-types";
|
||||
@ -77,7 +78,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
ttl
|
||||
ttl,
|
||||
config
|
||||
}: TCreateDynamicSecretLeaseDTO) => {
|
||||
const appCfg = getConfig();
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
@ -163,7 +165,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
expireAt: expireAt.getTime(),
|
||||
usernameTemplate: dynamicSecretCfg.usernameTemplate,
|
||||
identity,
|
||||
metadata: { projectId }
|
||||
metadata: { projectId },
|
||||
config
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error && typeof error === "object" && error !== null && "sqlMessage" in error) {
|
||||
@ -177,8 +180,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
expireAt,
|
||||
version: 1,
|
||||
dynamicSecretId: dynamicSecretCfg.id,
|
||||
externalEntityId: entityId
|
||||
externalEntityId: entityId,
|
||||
config
|
||||
});
|
||||
|
||||
await dynamicSecretQueueService.setLeaseRevocation(dynamicSecretLease.id, Number(expireAt) - Number(new Date()));
|
||||
return { lease: dynamicSecretLease, dynamicSecret: dynamicSecretCfg, data };
|
||||
};
|
||||
@ -259,7 +264,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
const expireAt = new Date(dynamicSecretLease.expireAt.getTime() + ms(selectedTTL));
|
||||
if (maxTTL) {
|
||||
const maxExpiryDate = new Date(dynamicSecretLease.createdAt.getTime() + ms(maxTTL));
|
||||
if (expireAt > maxExpiryDate) throw new BadRequestError({ message: "TTL cannot be larger than max ttl" });
|
||||
if (expireAt > maxExpiryDate)
|
||||
throw new BadRequestError({
|
||||
message: "The requested renewal would exceed the maximum allowed lease duration. Please choose a shorter TTL"
|
||||
});
|
||||
}
|
||||
|
||||
const { entityId } = await selectedProvider.renew(
|
||||
@ -342,7 +350,12 @@ export const dynamicSecretLeaseServiceFactory = ({
|
||||
) as object;
|
||||
|
||||
const revokeResponse = await selectedProvider
|
||||
.revoke(decryptedStoredInput, dynamicSecretLease.externalEntityId, { projectId })
|
||||
.revoke(
|
||||
decryptedStoredInput,
|
||||
dynamicSecretLease.externalEntityId,
|
||||
{ projectId },
|
||||
dynamicSecretLease.config as TDynamicSecretLeaseConfig
|
||||
)
|
||||
.catch(async (err) => {
|
||||
// only propogate this error if forced is false
|
||||
if (!isForced) return { error: err as Error };
|
||||
|
@ -10,6 +10,7 @@ export type TCreateDynamicSecretLeaseDTO = {
|
||||
environmentSlug: string;
|
||||
ttl?: string;
|
||||
projectSlug: string;
|
||||
config?: TDynamicSecretLeaseConfig;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDetailsDynamicSecretLeaseDTO = {
|
||||
@ -41,3 +42,9 @@ export type TRenewDynamicSecretLeaseDTO = {
|
||||
ttl?: string;
|
||||
projectSlug: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDynamicSecretKubernetesLeaseConfig = {
|
||||
namespace?: string;
|
||||
};
|
||||
|
||||
export type TDynamicSecretLeaseConfig = TDynamicSecretKubernetesLeaseConfig;
|
||||
|
@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
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-types";
|
||||
import {
|
||||
ProjectPermissionDynamicSecretActions,
|
||||
ProjectPermissionSub
|
||||
|
@ -128,11 +128,21 @@ export const AwsIamProvider = (): TDynamicProviderFns => {
|
||||
|
||||
const username = generateUsername(usernameTemplate, identity);
|
||||
const { policyArns, userGroups, policyDocument, awsPath, permissionBoundaryPolicyArn } = providerInputs;
|
||||
const awsTags = [{ Key: "createdBy", Value: "infisical-dynamic-secret" }];
|
||||
|
||||
if (providerInputs.tags && Array.isArray(providerInputs.tags)) {
|
||||
const additionalTags = providerInputs.tags.map((tag) => ({
|
||||
Key: tag.key,
|
||||
Value: tag.value
|
||||
}));
|
||||
awsTags.push(...additionalTags);
|
||||
}
|
||||
|
||||
const createUserRes = await client.send(
|
||||
new CreateUserCommand({
|
||||
Path: awsPath,
|
||||
PermissionsBoundary: permissionBoundaryPolicyArn || undefined,
|
||||
Tags: [{ Key: "createdBy", Value: "infisical-dynamic-secret" }],
|
||||
Tags: awsTags,
|
||||
UserName: username
|
||||
})
|
||||
);
|
||||
|
105
backend/src/ee/services/dynamic-secret/providers/gcp-iam.ts
Normal file
105
backend/src/ee/services/dynamic-secret/providers/gcp-iam.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { gaxios, Impersonated, JWT } from "google-auth-library";
|
||||
import { GetAccessTokenResponse } from "google-auth-library/build/src/auth/oauth2client";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
|
||||
import { DynamicSecretGcpIamSchema, TDynamicProviderFns } from "./models";
|
||||
|
||||
export const GcpIamProvider = (): TDynamicProviderFns => {
|
||||
const validateProviderInputs = async (inputs: unknown) => {
|
||||
const providerInputs = await DynamicSecretGcpIamSchema.parseAsync(inputs);
|
||||
return providerInputs;
|
||||
};
|
||||
|
||||
const $getToken = async (serviceAccountEmail: string, ttl: number): Promise<string> => {
|
||||
const appCfg = getConfig();
|
||||
if (!appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) {
|
||||
throw new InternalServerError({
|
||||
message: "Environment variable has not been configured: INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL"
|
||||
});
|
||||
}
|
||||
|
||||
const credJson = JSON.parse(appCfg.INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL) as {
|
||||
client_email: string;
|
||||
private_key: string;
|
||||
};
|
||||
|
||||
const sourceClient = new JWT({
|
||||
email: credJson.client_email,
|
||||
key: credJson.private_key,
|
||||
scopes: ["https://www.googleapis.com/auth/cloud-platform"]
|
||||
});
|
||||
|
||||
const impersonatedCredentials = new Impersonated({
|
||||
sourceClient,
|
||||
targetPrincipal: serviceAccountEmail,
|
||||
lifetime: ttl,
|
||||
delegates: [],
|
||||
targetScopes: ["https://www.googleapis.com/auth/iam", "https://www.googleapis.com/auth/cloud-platform"]
|
||||
});
|
||||
|
||||
let tokenResponse: GetAccessTokenResponse | undefined;
|
||||
try {
|
||||
tokenResponse = await impersonatedCredentials.getAccessToken();
|
||||
} catch (error) {
|
||||
let message = "Unable to validate connection";
|
||||
if (error instanceof gaxios.GaxiosError) {
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
throw new BadRequestError({
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
if (!tokenResponse || !tokenResponse.token) {
|
||||
throw new BadRequestError({
|
||||
message: "Unable to validate connection"
|
||||
});
|
||||
}
|
||||
|
||||
return tokenResponse.token;
|
||||
};
|
||||
|
||||
const validateConnection = async (inputs: unknown) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
await $getToken(providerInputs.serviceAccountEmail, 10);
|
||||
return true;
|
||||
};
|
||||
|
||||
const create = async (data: { inputs: unknown; expireAt: number }) => {
|
||||
const { inputs, expireAt } = data;
|
||||
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const ttl = Math.max(Math.floor(expireAt / 1000) - now, 0);
|
||||
|
||||
const token = await $getToken(providerInputs.serviceAccountEmail, ttl);
|
||||
const entityId = alphaNumericNanoId(32);
|
||||
|
||||
return { entityId, data: { SERVICE_ACCOUNT_EMAIL: providerInputs.serviceAccountEmail, TOKEN: token } };
|
||||
};
|
||||
|
||||
const revoke = async (_inputs: unknown, entityId: string) => {
|
||||
// There's no way to revoke GCP IAM access tokens
|
||||
return { entityId };
|
||||
};
|
||||
|
||||
const renew = async (inputs: unknown, entityId: string, expireAt: number) => {
|
||||
// To renew a token it must be re-created
|
||||
const data = await create({ inputs, expireAt });
|
||||
|
||||
return { ...data, entityId };
|
||||
};
|
||||
|
||||
return {
|
||||
validateProviderInputs,
|
||||
validateConnection,
|
||||
create,
|
||||
revoke,
|
||||
renew
|
||||
};
|
||||
};
|
133
backend/src/ee/services/dynamic-secret/providers/github.ts
Normal file
133
backend/src/ee/services/dynamic-secret/providers/github.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import axios from "axios";
|
||||
import * as jwt from "jsonwebtoken";
|
||||
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
||||
|
||||
import { DynamicSecretGithubSchema, TDynamicProviderFns } from "./models";
|
||||
|
||||
interface GitHubInstallationTokenResponse {
|
||||
token: string;
|
||||
expires_at: string; // ISO 8601 timestamp e.g., "2024-01-15T12:00:00Z"
|
||||
permissions?: Record<string, string>;
|
||||
repository_selection?: string;
|
||||
}
|
||||
|
||||
interface TGithubProviderInputs {
|
||||
appId: number;
|
||||
installationId: number;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
export const GithubProvider = (): TDynamicProviderFns => {
|
||||
const validateProviderInputs = async (inputs: unknown) => {
|
||||
const providerInputs = await DynamicSecretGithubSchema.parseAsync(inputs);
|
||||
return providerInputs;
|
||||
};
|
||||
|
||||
const $generateGitHubInstallationAccessToken = async (
|
||||
credentials: TGithubProviderInputs
|
||||
): Promise<GitHubInstallationTokenResponse> => {
|
||||
const { appId, installationId, privateKey } = credentials;
|
||||
|
||||
const nowInSeconds = Math.floor(Date.now() / 1000);
|
||||
const jwtPayload = {
|
||||
iat: nowInSeconds - 5,
|
||||
exp: nowInSeconds + 60,
|
||||
iss: String(appId)
|
||||
};
|
||||
|
||||
let appJwt: string;
|
||||
try {
|
||||
appJwt = jwt.sign(jwtPayload, privateKey, { algorithm: "RS256" });
|
||||
} catch (error) {
|
||||
let message = "Failed to sign JWT.";
|
||||
if (error instanceof jwt.JsonWebTokenError) {
|
||||
message += ` JsonWebTokenError: ${error.message}`;
|
||||
}
|
||||
throw new InternalServerError({
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
const tokenUrl = `${IntegrationUrls.GITHUB_API_URL}/app/installations/${String(installationId)}/access_tokens`;
|
||||
|
||||
try {
|
||||
const response = await axios.post<GitHubInstallationTokenResponse>(tokenUrl, undefined, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${appJwt}`,
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28"
|
||||
}
|
||||
});
|
||||
|
||||
if (response.status === 201 && response.data.token) {
|
||||
return response.data; // Includes token, expires_at, permissions, repository_selection
|
||||
}
|
||||
|
||||
throw new InternalServerError({
|
||||
message: `GitHub API responded with unexpected status ${response.status}: ${JSON.stringify(response.data)}`
|
||||
});
|
||||
} catch (error) {
|
||||
let message = "Failed to fetch GitHub installation access token.";
|
||||
if (axios.isAxiosError(error) && error.response) {
|
||||
const githubErrorMsg =
|
||||
(error.response.data as { message?: string })?.message || JSON.stringify(error.response.data);
|
||||
message += ` GitHub API Error: ${error.response.status} - ${githubErrorMsg}`;
|
||||
|
||||
// Classify as BadRequestError for auth-related issues (401, 403, 404) which might be due to user input
|
||||
if ([401, 403, 404].includes(error.response.status)) {
|
||||
throw new BadRequestError({ message });
|
||||
}
|
||||
}
|
||||
|
||||
throw new InternalServerError({ message });
|
||||
}
|
||||
};
|
||||
|
||||
const validateConnection = async (inputs: unknown) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
await $generateGitHubInstallationAccessToken(providerInputs);
|
||||
return true;
|
||||
};
|
||||
|
||||
const create = async (data: { inputs: unknown }) => {
|
||||
const { inputs } = data;
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
|
||||
const ghTokenData = await $generateGitHubInstallationAccessToken(providerInputs);
|
||||
const entityId = alphaNumericNanoId(32);
|
||||
|
||||
return {
|
||||
entityId,
|
||||
data: {
|
||||
TOKEN: ghTokenData.token,
|
||||
EXPIRES_AT: ghTokenData.expires_at,
|
||||
PERMISSIONS: ghTokenData.permissions,
|
||||
REPOSITORY_SELECTION: ghTokenData.repository_selection
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const revoke = async () => {
|
||||
// GitHub installation tokens cannot be revoked.
|
||||
throw new BadRequestError({
|
||||
message:
|
||||
"Github dynamic secret does not support revocation because GitHub itself cannot revoke installation tokens"
|
||||
});
|
||||
};
|
||||
|
||||
const renew = async () => {
|
||||
// No renewal
|
||||
throw new BadRequestError({ message: "Github dynamic secret does not support renewal" });
|
||||
};
|
||||
|
||||
return {
|
||||
validateProviderInputs,
|
||||
validateConnection,
|
||||
create,
|
||||
revoke,
|
||||
renew
|
||||
};
|
||||
};
|
@ -6,6 +6,8 @@ import { AwsIamProvider } from "./aws-iam";
|
||||
import { AzureEntraIDProvider } from "./azure-entra-id";
|
||||
import { CassandraProvider } from "./cassandra";
|
||||
import { ElasticSearchProvider } from "./elastic-search";
|
||||
import { GcpIamProvider } from "./gcp-iam";
|
||||
import { GithubProvider } from "./github";
|
||||
import { KubernetesProvider } from "./kubernetes";
|
||||
import { LdapProvider } from "./ldap";
|
||||
import { DynamicSecretProviders, TDynamicProviderFns } from "./models";
|
||||
@ -42,5 +44,7 @@ export const buildDynamicSecretProviders = ({
|
||||
[DynamicSecretProviders.Totp]: TotpProvider(),
|
||||
[DynamicSecretProviders.SapAse]: SapAseProvider(),
|
||||
[DynamicSecretProviders.Kubernetes]: KubernetesProvider({ gatewayService }),
|
||||
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService })
|
||||
[DynamicSecretProviders.Vertica]: VerticaProvider({ gatewayService }),
|
||||
[DynamicSecretProviders.GcpIam]: GcpIamProvider(),
|
||||
[DynamicSecretProviders.Github]: GithubProvider()
|
||||
});
|
||||
|
@ -1,13 +1,14 @@
|
||||
import axios from "axios";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import handlebars from "handlebars";
|
||||
import https from "https";
|
||||
|
||||
import { InternalServerError } from "@app/lib/errors";
|
||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
||||
import { GatewayHttpProxyActions, GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||
import { TKubernetesTokenRequest } from "@app/services/identity-kubernetes-auth/identity-kubernetes-auth-types";
|
||||
|
||||
import { TDynamicSecretKubernetesLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
|
||||
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
||||
import {
|
||||
DynamicSecretKubernetesSchema,
|
||||
@ -19,6 +20,9 @@ import {
|
||||
|
||||
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||
|
||||
// This value is just a placeholder. When using gateway auth method, the url is irrelevant.
|
||||
const GATEWAY_AUTH_DEFAULT_URL = "https://kubernetes.default.svc.cluster.local";
|
||||
|
||||
type TKubernetesProviderDTO = {
|
||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">;
|
||||
};
|
||||
@ -36,7 +40,7 @@ const generateUsername = (usernameTemplate?: string | null) => {
|
||||
export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO): TDynamicProviderFns => {
|
||||
const validateProviderInputs = async (inputs: unknown) => {
|
||||
const providerInputs = await DynamicSecretKubernetesSchema.parseAsync(inputs);
|
||||
if (!providerInputs.gatewayId) {
|
||||
if (!providerInputs.gatewayId && providerInputs.url) {
|
||||
await blockLocalAndPrivateIpAddresses(providerInputs.url);
|
||||
}
|
||||
|
||||
@ -103,135 +107,173 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
const serviceAccountName = generateUsername();
|
||||
const roleBindingName = `${serviceAccountName}-role-binding`;
|
||||
|
||||
// 1. Create a test service account
|
||||
await axios.post(
|
||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts`,
|
||||
{
|
||||
metadata: {
|
||||
name: serviceAccountName,
|
||||
namespace: providerInputs.namespace
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
}
|
||||
);
|
||||
const namespaces = providerInputs.namespace.split(",").map((namespace) => namespace.trim());
|
||||
|
||||
// 2. Create a test role binding
|
||||
const roleBindingUrl =
|
||||
providerInputs.roleType === KubernetesRoleType.ClusterRole
|
||||
? `${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`
|
||||
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings`;
|
||||
|
||||
const roleBindingMetadata = {
|
||||
name: roleBindingName,
|
||||
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace: providerInputs.namespace })
|
||||
};
|
||||
|
||||
await axios.post(
|
||||
roleBindingUrl,
|
||||
{
|
||||
metadata: roleBindingMetadata,
|
||||
roleRef: {
|
||||
kind: providerInputs.roleType === KubernetesRoleType.ClusterRole ? "ClusterRole" : "Role",
|
||||
name: providerInputs.role,
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
},
|
||||
subjects: [
|
||||
// Test each namespace sequentially instead of in parallel to simplify cleanup
|
||||
for await (const namespace of namespaces) {
|
||||
try {
|
||||
// 1. Create a test service account
|
||||
await axios.post(
|
||||
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts`,
|
||||
{
|
||||
kind: "ServiceAccount",
|
||||
name: serviceAccountName,
|
||||
namespace: providerInputs.namespace
|
||||
metadata: {
|
||||
name: serviceAccountName,
|
||||
namespace
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
// 3. Request a token for the test service account
|
||||
await axios.post(
|
||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${serviceAccountName}/token`,
|
||||
{
|
||||
spec: {
|
||||
expirationSeconds: 600, // 10 minutes
|
||||
...(providerInputs.audiences?.length ? { audiences: providerInputs.audiences } : {})
|
||||
// 2. Create a test role binding
|
||||
const roleBindingUrl =
|
||||
providerInputs.roleType === KubernetesRoleType.ClusterRole
|
||||
? `${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`
|
||||
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings`;
|
||||
|
||||
const roleBindingMetadata = {
|
||||
name: roleBindingName,
|
||||
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace })
|
||||
};
|
||||
|
||||
await axios.post(
|
||||
roleBindingUrl,
|
||||
{
|
||||
metadata: roleBindingMetadata,
|
||||
roleRef: {
|
||||
kind: providerInputs.roleType === KubernetesRoleType.ClusterRole ? "ClusterRole" : "Role",
|
||||
name: providerInputs.role,
|
||||
apiGroup: "rbac.authorization.k8s.io"
|
||||
},
|
||||
subjects: [
|
||||
{
|
||||
kind: "ServiceAccount",
|
||||
name: serviceAccountName,
|
||||
namespace
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
|
||||
// 3. Request a token for the test service account
|
||||
await axios.post(
|
||||
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${serviceAccountName}/token`,
|
||||
{
|
||||
spec: {
|
||||
expirationSeconds: 600, // 10 minutes
|
||||
...(providerInputs.audiences?.length ? { audiences: providerInputs.audiences } : {})
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
|
||||
// 4. Cleanup: delete role binding and service account
|
||||
if (providerInputs.roleType === KubernetesRoleType.Role) {
|
||||
await axios.delete(
|
||||
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings/${roleBindingName}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await axios.delete(`${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/${roleBindingName}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
}
|
||||
);
|
||||
|
||||
// 4. Cleanup: delete role binding and service account
|
||||
if (providerInputs.roleType === KubernetesRoleType.Role) {
|
||||
await axios.delete(
|
||||
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings/${roleBindingName}`,
|
||||
{
|
||||
await axios.delete(`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${serviceAccountName}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
});
|
||||
} catch (error) {
|
||||
const cleanupInfo = `You may need to manually clean up the following resources in namespace "${namespace}": Service Account - ${serviceAccountName}, ${providerInputs.roleType === KubernetesRoleType.Role ? "Role" : "Cluster Role"} Binding - ${roleBindingName}.`;
|
||||
let mainErrorMessage = "Unknown error";
|
||||
if (error instanceof AxiosError) {
|
||||
mainErrorMessage = (error.response?.data as { message: string })?.message;
|
||||
} else if (error instanceof Error) {
|
||||
mainErrorMessage = error.message;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await axios.delete(`${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings/${roleBindingName}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
});
|
||||
}
|
||||
|
||||
await axios.delete(
|
||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${serviceAccountName}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
throw new Error(`${mainErrorMessage}. ${cleanupInfo}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const serviceAccountStaticCallback = async (host: string, port: number, httpsAgent?: https.Agent) => {
|
||||
@ -247,17 +289,23 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const url = new URL(providerInputs.url);
|
||||
const rawUrl =
|
||||
providerInputs.authMethod === KubernetesAuthMethod.Gateway ? GATEWAY_AUTH_DEFAULT_URL : providerInputs.url || "";
|
||||
const url = new URL(rawUrl);
|
||||
const k8sGatewayHost = url.hostname;
|
||||
const k8sPort = url.port ? Number(url.port) : 443;
|
||||
const k8sHost = `${url.protocol}//${url.hostname}`;
|
||||
@ -315,11 +363,13 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
const create = async ({
|
||||
inputs,
|
||||
expireAt,
|
||||
usernameTemplate
|
||||
usernameTemplate,
|
||||
config
|
||||
}: {
|
||||
inputs: unknown;
|
||||
expireAt: number;
|
||||
usernameTemplate?: string | null;
|
||||
config?: TDynamicSecretKubernetesLeaseConfig;
|
||||
}) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
|
||||
@ -331,26 +381,44 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
const baseUrl = port ? `${host}:${port}` : host;
|
||||
const serviceAccountName = generateUsername(usernameTemplate);
|
||||
const roleBindingName = `${serviceAccountName}-role-binding`;
|
||||
const allowedNamespaces = providerInputs.namespace.split(",").map((namespace) => namespace.trim());
|
||||
|
||||
if (config?.namespace && !allowedNamespaces?.includes(config?.namespace)) {
|
||||
throw new BadRequestError({
|
||||
message: `Namespace ${config?.namespace} is not allowed. Allowed namespaces: ${allowedNamespaces?.join(", ")}`
|
||||
});
|
||||
}
|
||||
|
||||
const namespace = config?.namespace || allowedNamespaces[0];
|
||||
if (!namespace) {
|
||||
throw new BadRequestError({
|
||||
message: "No namespace provided"
|
||||
});
|
||||
}
|
||||
|
||||
// 1. Create the service account
|
||||
await axios.post(
|
||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts`,
|
||||
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts`,
|
||||
{
|
||||
metadata: {
|
||||
name: serviceAccountName,
|
||||
namespace: providerInputs.namespace
|
||||
namespace
|
||||
}
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
|
||||
@ -358,11 +426,11 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
const roleBindingUrl =
|
||||
providerInputs.roleType === KubernetesRoleType.ClusterRole
|
||||
? `${baseUrl}/apis/rbac.authorization.k8s.io/v1/clusterrolebindings`
|
||||
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings`;
|
||||
: `${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings`;
|
||||
|
||||
const roleBindingMetadata = {
|
||||
name: roleBindingName,
|
||||
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace: providerInputs.namespace })
|
||||
...(providerInputs.roleType !== KubernetesRoleType.ClusterRole && { namespace })
|
||||
};
|
||||
|
||||
await axios.post(
|
||||
@ -378,7 +446,7 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
{
|
||||
kind: "ServiceAccount",
|
||||
name: serviceAccountName,
|
||||
namespace: providerInputs.namespace
|
||||
namespace
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -386,18 +454,22 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
|
||||
// 3. Request a token for the service account
|
||||
const res = await axios.post<TKubernetesTokenRequest>(
|
||||
`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${serviceAccountName}/token`,
|
||||
`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${serviceAccountName}/token`,
|
||||
{
|
||||
spec: {
|
||||
expirationSeconds: Math.floor((expireAt - Date.now()) / 1000),
|
||||
@ -408,12 +480,16 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
|
||||
@ -425,6 +501,12 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
throw new Error("invalid callback");
|
||||
}
|
||||
|
||||
if (config?.namespace && config.namespace !== providerInputs.namespace) {
|
||||
throw new BadRequestError({
|
||||
message: `Namespace ${config?.namespace} is not allowed. Allowed namespace: ${providerInputs.namespace}.`
|
||||
});
|
||||
}
|
||||
|
||||
const baseUrl = port ? `${host}:${port}` : host;
|
||||
|
||||
const res = await axios.post<TKubernetesTokenRequest>(
|
||||
@ -439,19 +521,25 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
|
||||
return { ...res.data, serviceAccountName: providerInputs.serviceAccountName };
|
||||
};
|
||||
|
||||
const url = new URL(providerInputs.url);
|
||||
const rawUrl =
|
||||
providerInputs.authMethod === KubernetesAuthMethod.Gateway ? GATEWAY_AUTH_DEFAULT_URL : providerInputs.url || "";
|
||||
const url = new URL(rawUrl);
|
||||
const k8sHost = `${url.protocol}//${url.hostname}`;
|
||||
const k8sGatewayHost = url.hostname;
|
||||
const k8sPort = url.port ? Number(url.port) : 443;
|
||||
@ -511,7 +599,13 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
}
|
||||
};
|
||||
|
||||
const revoke = async (inputs: unknown, entityId: string) => {
|
||||
const revoke = async (
|
||||
inputs: unknown,
|
||||
entityId: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_metadata: { projectId: string },
|
||||
config?: TDynamicSecretKubernetesLeaseConfig
|
||||
) => {
|
||||
const providerInputs = await validateProviderInputs(inputs);
|
||||
|
||||
const serviceAccountDynamicCallback = async (host: string, port: number, httpsAgent?: https.Agent) => {
|
||||
@ -522,19 +616,25 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
const baseUrl = port ? `${host}:${port}` : host;
|
||||
const roleBindingName = `${entityId}-role-binding`;
|
||||
|
||||
const namespace = config?.namespace ?? providerInputs.namespace.split(",")[0].trim();
|
||||
|
||||
if (providerInputs.roleType === KubernetesRoleType.Role) {
|
||||
await axios.delete(
|
||||
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${providerInputs.namespace}/rolebindings/${roleBindingName}`,
|
||||
`${baseUrl}/apis/rbac.authorization.k8s.io/v1/namespaces/${namespace}/rolebindings/${roleBindingName}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
}
|
||||
);
|
||||
} else {
|
||||
@ -542,31 +642,44 @@ export const KubernetesProvider = ({ gatewayService }: TKubernetesProviderDTO):
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
});
|
||||
}
|
||||
|
||||
// Delete the service account
|
||||
await axios.delete(`${baseUrl}/api/v1/namespaces/${providerInputs.namespace}/serviceaccounts/${entityId}`, {
|
||||
await axios.delete(`${baseUrl}/api/v1/namespaces/${namespace}/serviceaccounts/${entityId}`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.InjectGatewayK8sServiceAccountToken }
|
||||
? { "x-infisical-action": GatewayHttpProxyActions.UseGatewayK8sServiceAccount }
|
||||
: { Authorization: `Bearer ${providerInputs.clusterToken}` })
|
||||
},
|
||||
...(providerInputs.authMethod === KubernetesAuthMethod.Api
|
||||
? {
|
||||
httpsAgent
|
||||
}
|
||||
: {}),
|
||||
signal: AbortSignal.timeout(EXTERNAL_REQUEST_TIMEOUT),
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT,
|
||||
httpsAgent
|
||||
timeout: EXTERNAL_REQUEST_TIMEOUT
|
||||
});
|
||||
};
|
||||
|
||||
if (providerInputs.credentialType === KubernetesCredentialType.Dynamic) {
|
||||
const url = new URL(providerInputs.url);
|
||||
const rawUrl =
|
||||
providerInputs.authMethod === KubernetesAuthMethod.Gateway
|
||||
? GATEWAY_AUTH_DEFAULT_URL
|
||||
: providerInputs.url || "";
|
||||
|
||||
const url = new URL(rawUrl);
|
||||
const k8sGatewayHost = url.hostname;
|
||||
const k8sPort = url.port ? Number(url.port) : 443;
|
||||
const k8sHost = `${url.protocol}//${url.hostname}`;
|
||||
|
@ -1,5 +1,11 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { CharacterType, characterValidator } from "@app/lib/validator/validate-string";
|
||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||
|
||||
import { TDynamicSecretLeaseConfig } from "../../dynamic-secret-lease/dynamic-secret-lease-types";
|
||||
|
||||
export type PasswordRequirements = {
|
||||
length: number;
|
||||
required: {
|
||||
@ -202,7 +208,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
|
||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||
policyDocument: z.string().trim().optional(),
|
||||
userGroups: z.string().trim().optional(),
|
||||
policyArns: z.string().trim().optional()
|
||||
policyArns: z.string().trim().optional(),
|
||||
tags: ResourceMetadataSchema.optional()
|
||||
}),
|
||||
z.object({
|
||||
method: z.literal(AwsIamAuthType.AssumeRole),
|
||||
@ -212,7 +219,8 @@ export const DynamicSecretAwsIamSchema = z.preprocess(
|
||||
permissionBoundaryPolicyArn: z.string().trim().optional(),
|
||||
policyDocument: z.string().trim().optional(),
|
||||
userGroups: z.string().trim().optional(),
|
||||
policyArns: z.string().trim().optional()
|
||||
policyArns: z.string().trim().optional(),
|
||||
tags: ResourceMetadataSchema.optional()
|
||||
})
|
||||
])
|
||||
);
|
||||
@ -323,24 +331,54 @@ export const LdapSchema = z.union([
|
||||
export const DynamicSecretKubernetesSchema = z
|
||||
.discriminatedUnion("credentialType", [
|
||||
z.object({
|
||||
url: z.string().url().trim().min(1),
|
||||
url: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine((val: string | undefined) => !val || new RE2(/^https?:\/\/.+/).test(val), {
|
||||
message: "Invalid URL. Must start with http:// or https:// (e.g. https://example.com)"
|
||||
}),
|
||||
clusterToken: z.string().trim().optional(),
|
||||
ca: z.string().optional(),
|
||||
sslEnabled: z.boolean().default(false),
|
||||
credentialType: z.literal(KubernetesCredentialType.Static),
|
||||
serviceAccountName: z.string().trim().min(1),
|
||||
namespace: z.string().trim().min(1),
|
||||
namespace: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.refine((val) => !val.includes(","), "Namespace must be a single value, not a comma-separated list")
|
||||
.refine(
|
||||
(val) => characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen])(val),
|
||||
"Invalid namespace format"
|
||||
),
|
||||
gatewayId: z.string().optional(),
|
||||
audiences: z.array(z.string().trim().min(1)),
|
||||
authMethod: z.nativeEnum(KubernetesAuthMethod).default(KubernetesAuthMethod.Api)
|
||||
}),
|
||||
z.object({
|
||||
url: z.string().url().trim().min(1),
|
||||
url: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.refine((val: string | undefined) => !val || new RE2(/^https?:\/\/.+/).test(val), {
|
||||
message: "Invalid URL. Must start with http:// or https:// (e.g. https://example.com)"
|
||||
}),
|
||||
clusterToken: z.string().trim().optional(),
|
||||
ca: z.string().optional(),
|
||||
sslEnabled: z.boolean().default(false),
|
||||
credentialType: z.literal(KubernetesCredentialType.Dynamic),
|
||||
namespace: z.string().trim().min(1),
|
||||
namespace: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.refine((val) => {
|
||||
const namespaces = val.split(",").map((ns) => ns.trim());
|
||||
return (
|
||||
namespaces.length > 0 &&
|
||||
namespaces.every((ns) => ns.length > 0) &&
|
||||
namespaces.every((ns) => characterValidator([CharacterType.AlphaNumeric, CharacterType.Hyphen])(ns))
|
||||
);
|
||||
}, "Must be a valid comma-separated list of namespace values"),
|
||||
gatewayId: z.string().optional(),
|
||||
audiences: z.array(z.string().trim().min(1)),
|
||||
roleType: z.nativeEnum(KubernetesRoleType),
|
||||
@ -356,12 +394,21 @@ export const DynamicSecretKubernetesSchema = z
|
||||
message: "When auth method is set to Gateway, a gateway must be selected"
|
||||
});
|
||||
}
|
||||
if ((data.authMethod === KubernetesAuthMethod.Api || !data.authMethod) && !data.clusterToken) {
|
||||
ctx.addIssue({
|
||||
path: ["clusterToken"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "When auth method is set to Manual Token, a cluster token must be provided"
|
||||
});
|
||||
if (data.authMethod === KubernetesAuthMethod.Api || !data.authMethod) {
|
||||
if (!data.clusterToken) {
|
||||
ctx.addIssue({
|
||||
path: ["clusterToken"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "When auth method is set to Token, a cluster token must be provided"
|
||||
});
|
||||
}
|
||||
if (!data.url) {
|
||||
ctx.addIssue({
|
||||
path: ["url"],
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "When auth method is set to Token, a cluster URL must be provided"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -426,6 +473,27 @@ export const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [
|
||||
})
|
||||
]);
|
||||
|
||||
export const DynamicSecretGcpIamSchema = z.object({
|
||||
serviceAccountEmail: z.string().email().trim().min(1, "Service account email required").max(128)
|
||||
});
|
||||
|
||||
export const DynamicSecretGithubSchema = z.object({
|
||||
appId: z.number().min(1).describe("The ID of your GitHub App."),
|
||||
installationId: z.number().min(1).describe("The ID of the GitHub App installation."),
|
||||
privateKey: z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.refine(
|
||||
(val) =>
|
||||
new RE2(
|
||||
/^-----BEGIN(?:(?: RSA| PGP| ENCRYPTED)? PRIVATE KEY)-----\s*[\s\S]*?-----END(?:(?: RSA| PGP| ENCRYPTED)? PRIVATE KEY)-----$/
|
||||
).test(val),
|
||||
"Invalid PEM format for private key"
|
||||
)
|
||||
.describe("The private key generated for your GitHub App.")
|
||||
});
|
||||
|
||||
export enum DynamicSecretProviders {
|
||||
SqlDatabase = "sql-database",
|
||||
Cassandra = "cassandra",
|
||||
@ -443,7 +511,9 @@ export enum DynamicSecretProviders {
|
||||
Totp = "totp",
|
||||
SapAse = "sap-ase",
|
||||
Kubernetes = "kubernetes",
|
||||
Vertica = "vertica"
|
||||
Vertica = "vertica",
|
||||
GcpIam = "gcp-iam",
|
||||
Github = "github"
|
||||
}
|
||||
|
||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||
@ -463,7 +533,9 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||
z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema }),
|
||||
z.object({ type: z.literal(DynamicSecretProviders.Totp), inputs: DynamicSecretTotpSchema }),
|
||||
z.object({ type: z.literal(DynamicSecretProviders.Kubernetes), inputs: DynamicSecretKubernetesSchema }),
|
||||
z.object({ type: z.literal(DynamicSecretProviders.Vertica), inputs: DynamicSecretVerticaSchema })
|
||||
z.object({ type: z.literal(DynamicSecretProviders.Vertica), inputs: DynamicSecretVerticaSchema }),
|
||||
z.object({ type: z.literal(DynamicSecretProviders.GcpIam), inputs: DynamicSecretGcpIamSchema }),
|
||||
z.object({ type: z.literal(DynamicSecretProviders.Github), inputs: DynamicSecretGithubSchema })
|
||||
]);
|
||||
|
||||
export type TDynamicProviderFns = {
|
||||
@ -475,10 +547,16 @@ export type TDynamicProviderFns = {
|
||||
name: string;
|
||||
};
|
||||
metadata: { projectId: string };
|
||||
config?: TDynamicSecretLeaseConfig;
|
||||
}) => Promise<{ entityId: string; data: unknown }>;
|
||||
validateConnection: (inputs: unknown, metadata: { projectId: string }) => Promise<boolean>;
|
||||
validateProviderInputs: (inputs: object, metadata: { projectId: string }) => Promise<unknown>;
|
||||
revoke: (inputs: unknown, entityId: string, metadata: { projectId: string }) => Promise<{ entityId: string }>;
|
||||
revoke: (
|
||||
inputs: unknown,
|
||||
entityId: string,
|
||||
metadata: { projectId: string },
|
||||
config?: TDynamicSecretLeaseConfig
|
||||
) => Promise<{ entityId: string }>;
|
||||
renew: (
|
||||
inputs: unknown,
|
||||
entityId: string,
|
||||
|
@ -11,7 +11,7 @@ import { KmsDataKey, KmsKeyUsage } from "@app/services/kms/kms-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TExternalKmsDALFactory } from "./external-kms-dal";
|
||||
import {
|
||||
TCreateExternalKmsDTO,
|
||||
|
@ -21,7 +21,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionGatewayActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TGatewayDALFactory } from "./gateway-dal";
|
||||
import {
|
||||
TExchangeAllocatedRelayAddressDTO,
|
||||
|
@ -14,7 +14,7 @@ import { TGroupDALFactory } from "../group/group-dal";
|
||||
import { TUserGroupMembershipDALFactory } from "../group/user-group-membership-dal";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal";
|
||||
import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types";
|
||||
|
||||
|
@ -169,11 +169,29 @@ export const groupDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findById = async (id: string, tx?: Knex) => {
|
||||
try {
|
||||
const doc = await (tx || db.replicaNode())(TableName.Groups)
|
||||
.leftJoin(TableName.OrgRoles, `${TableName.Groups}.roleId`, `${TableName.OrgRoles}.id`)
|
||||
.where(`${TableName.Groups}.id`, id)
|
||||
.select(
|
||||
selectAllTableCols(TableName.Groups),
|
||||
db.ref("slug").as("customRoleSlug").withSchema(TableName.OrgRoles)
|
||||
)
|
||||
.first();
|
||||
|
||||
return doc;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find by id" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...groupOrm,
|
||||
findGroups,
|
||||
findByOrgId,
|
||||
findAllGroupPossibleMembers,
|
||||
findGroupsByProjectId,
|
||||
...groupOrm
|
||||
findById
|
||||
};
|
||||
};
|
||||
|
@ -15,7 +15,7 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionGroupActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TGroupDALFactory } from "./group-dal";
|
||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "./group-fns";
|
||||
import {
|
||||
|
@ -11,7 +11,7 @@ import { TIdentityProjectDALFactory } from "@app/services/identity-project/ident
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { ProjectPermissionIdentityActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TIdentityProjectAdditionalPrivilegeV2DALFactory } from "./identity-project-additional-privilege-v2-dal";
|
||||
import {
|
||||
|
@ -11,7 +11,7 @@ import { TIdentityProjectDALFactory } from "@app/services/identity-project/ident
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionIdentityActions,
|
||||
ProjectPermissionSet,
|
||||
|
@ -7,7 +7,7 @@ import { KmsKeyUsage } from "@app/services/kms/kms-types";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TKmipClientDALFactory } from "./kmip-client-dal";
|
||||
import { KmipPermission } from "./kmip-enum";
|
||||
import {
|
||||
|
@ -18,7 +18,7 @@ import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||
import { TKmipClientCertificateDALFactory } from "./kmip-client-certificate-dal";
|
||||
import { TKmipClientDALFactory } from "./kmip-client-dal";
|
||||
|
@ -29,7 +29,7 @@ import { UserAliasType } from "@app/services/user-alias/user-alias-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { TLdapConfigDALFactory } from "./ldap-config-dal";
|
||||
import {
|
||||
TCreateLdapCfgDTO,
|
||||
|
@ -18,7 +18,7 @@ import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { OrgPermissionBillingActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import { BillingPlanRows, BillingPlanTableHead } from "./licence-enums";
|
||||
import { TLicenseDALFactory } from "./license-dal";
|
||||
import { getDefaultOnPremFeatures, setupLicenseRequestWithStore } from "./license-fns";
|
||||
|
@ -5,14 +5,13 @@ import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet }
|
||||
|
||||
import { OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
|
||||
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
@ -699,9 +698,9 @@ export const oidcConfigServiceFactory = ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(_req: any, tokenSet: TokenSet, cb: any) => {
|
||||
const claims = tokenSet.claims();
|
||||
if (!claims.email || !claims.given_name) {
|
||||
if (!claims.email) {
|
||||
throw new BadRequestError({
|
||||
message: "Invalid request. Missing email or first name"
|
||||
message: "Invalid request. Missing email claim."
|
||||
});
|
||||
}
|
||||
|
||||
@ -714,12 +713,19 @@ export const oidcConfigServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
const name = claims?.given_name || claims?.name;
|
||||
if (!name) {
|
||||
throw new BadRequestError({
|
||||
message: "Invalid request. Missing name claim."
|
||||
});
|
||||
}
|
||||
|
||||
const groups = typeof claims.groups === "string" ? [claims.groups] : (claims.groups as string[] | undefined);
|
||||
|
||||
oidcLogin({
|
||||
email: claims.email.toLowerCase(),
|
||||
externalId: claims.sub,
|
||||
firstName: claims.given_name ?? "",
|
||||
firstName: name,
|
||||
lastName: claims.family_name ?? "",
|
||||
orgId: org.id,
|
||||
groups,
|
||||
|
@ -6,16 +6,312 @@ import {
|
||||
OrgMembershipRole,
|
||||
OrgMembershipsSchema,
|
||||
TableName,
|
||||
TIdentityOrgMemberships,
|
||||
TProjectRoles,
|
||||
TProjects
|
||||
} from "@app/db/schemas";
|
||||
import { DatabaseError } from "@app/lib/errors";
|
||||
import { selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||
|
||||
export type TPermissionDALFactory = ReturnType<typeof permissionDALFactory>;
|
||||
export interface TPermissionDALFactory {
|
||||
getOrgPermission: (
|
||||
userId: string,
|
||||
orgId: string
|
||||
) => Promise<
|
||||
{
|
||||
status: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
role: string;
|
||||
isActive: boolean;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
bypassOrgAuthEnabled: boolean;
|
||||
permissions?: unknown;
|
||||
userId?: string | null | undefined;
|
||||
roleId?: string | null | undefined;
|
||||
inviteEmail?: string | null | undefined;
|
||||
projectFavorites?: string[] | null | undefined;
|
||||
customRoleSlug?: string | null | undefined;
|
||||
orgAuthEnforced?: boolean | null | undefined;
|
||||
} & {
|
||||
groups: {
|
||||
id: string;
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
role: string;
|
||||
roleId: string | null | undefined;
|
||||
customRolePermission: unknown;
|
||||
name: string;
|
||||
slug: string;
|
||||
orgId: string;
|
||||
}[];
|
||||
}
|
||||
>;
|
||||
getOrgIdentityPermission: (
|
||||
identityId: string,
|
||||
orgId: string
|
||||
) => Promise<
|
||||
| (TIdentityOrgMemberships & {
|
||||
orgAuthEnforced: boolean | null | undefined;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
permissions?: unknown;
|
||||
})
|
||||
| undefined
|
||||
>;
|
||||
getProjectPermission: (
|
||||
userId: string,
|
||||
projectId: string
|
||||
) => Promise<
|
||||
| {
|
||||
roles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
additionalPrivileges: {
|
||||
id: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
orgId: string;
|
||||
orgAuthEnforced: boolean | null | undefined;
|
||||
orgRole: OrgMembershipRole;
|
||||
userId: string;
|
||||
projectId: string;
|
||||
username: string;
|
||||
projectType: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
bypassOrgAuthEnabled: boolean;
|
||||
metadata: {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
userGroupRoles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
projecMembershiptRoles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
getProjectIdentityPermission: (
|
||||
identityId: string,
|
||||
projectId: string
|
||||
) => Promise<
|
||||
| {
|
||||
roles: {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
isTemporary: boolean;
|
||||
role: string;
|
||||
projectMembershipId: string;
|
||||
temporaryRange?: string | null | undefined;
|
||||
permissions?: unknown;
|
||||
customRoleId?: string | null | undefined;
|
||||
temporaryMode?: string | null | undefined;
|
||||
temporaryAccessStartTime?: Date | null | undefined;
|
||||
temporaryAccessEndTime?: Date | null | undefined;
|
||||
customRoleSlug?: string | null | undefined;
|
||||
}[];
|
||||
additionalPrivileges: {
|
||||
id: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
id: string;
|
||||
identityId: string;
|
||||
username: string;
|
||||
projectId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
orgId: string;
|
||||
projectType: string;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
orgAuthEnforced: boolean;
|
||||
metadata: {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
}
|
||||
| undefined
|
||||
>;
|
||||
getProjectUserPermissions: (projectId: string) => Promise<
|
||||
{
|
||||
roles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
additionalPrivileges: {
|
||||
id: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
orgId: string;
|
||||
orgAuthEnforced: boolean | null | undefined;
|
||||
userId: string;
|
||||
projectId: string;
|
||||
username: string;
|
||||
projectType: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
metadata: {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
userGroupRoles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
projectMembershipRoles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
}[]
|
||||
>;
|
||||
getProjectIdentityPermissions: (projectId: string) => Promise<
|
||||
{
|
||||
roles: {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
isTemporary: boolean;
|
||||
role: string;
|
||||
projectMembershipId: string;
|
||||
temporaryRange?: string | null | undefined;
|
||||
permissions?: unknown;
|
||||
customRoleId?: string | null | undefined;
|
||||
temporaryMode?: string | null | undefined;
|
||||
temporaryAccessStartTime?: Date | null | undefined;
|
||||
temporaryAccessEndTime?: Date | null | undefined;
|
||||
customRoleSlug?: string | null | undefined;
|
||||
}[];
|
||||
additionalPrivileges: {
|
||||
id: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
id: string;
|
||||
identityId: string;
|
||||
username: string;
|
||||
projectId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
orgId: string;
|
||||
projectType: string;
|
||||
orgAuthEnforced: boolean;
|
||||
metadata: {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}[];
|
||||
}[]
|
||||
>;
|
||||
getProjectGroupPermissions: (
|
||||
projectId: string,
|
||||
filterGroupId?: string
|
||||
) => Promise<
|
||||
{
|
||||
roles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
groupId: string;
|
||||
username: string;
|
||||
id: string;
|
||||
groupRoles: {
|
||||
id: string;
|
||||
role: string;
|
||||
customRoleSlug: string;
|
||||
permissions: unknown;
|
||||
temporaryRange: string | null | undefined;
|
||||
temporaryMode: string | null | undefined;
|
||||
temporaryAccessStartTime: Date | null | undefined;
|
||||
temporaryAccessEndTime: Date | null | undefined;
|
||||
isTemporary: boolean;
|
||||
}[];
|
||||
}[]
|
||||
>;
|
||||
}
|
||||
|
||||
export const permissionDALFactory = (db: TDbClient) => {
|
||||
const getOrgPermission = async (userId: string, orgId: string) => {
|
||||
export const permissionDALFactory = (db: TDbClient): TPermissionDALFactory => {
|
||||
const getOrgPermission: TPermissionDALFactory["getOrgPermission"] = async (userId: string, orgId: string) => {
|
||||
try {
|
||||
const groupSubQuery = db(TableName.Groups)
|
||||
.where(`${TableName.Groups}.orgId`, orgId)
|
||||
@ -112,7 +408,10 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getOrgIdentityPermission = async (identityId: string, orgId: string) => {
|
||||
const getOrgIdentityPermission: TPermissionDALFactory["getOrgIdentityPermission"] = async (
|
||||
identityId: string,
|
||||
orgId: string
|
||||
) => {
|
||||
try {
|
||||
const membership = await db
|
||||
.replicaNode()(TableName.IdentityOrgMembership)
|
||||
@ -132,7 +431,10 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectGroupPermissions = async (projectId: string, filterGroupId?: string) => {
|
||||
const getProjectGroupPermissions: TPermissionDALFactory["getProjectGroupPermissions"] = async (
|
||||
projectId: string,
|
||||
filterGroupId?: string
|
||||
) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.GroupProjectMembership)
|
||||
@ -245,7 +547,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectUserPermissions = async (projectId: string) => {
|
||||
const getProjectUserPermissions: TPermissionDALFactory["getProjectUserPermissions"] = async (projectId: string) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.Users)
|
||||
@ -535,7 +837,10 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectPermission = async (userId: string, projectId: string) => {
|
||||
const getProjectPermission: TPermissionDALFactory["getProjectPermission"] = async (
|
||||
userId: string,
|
||||
projectId: string
|
||||
) => {
|
||||
try {
|
||||
const subQueryUserGroups = db(TableName.UserGroupMembership).where("userId", userId).select("groupId");
|
||||
const docs = await db
|
||||
@ -838,7 +1143,9 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectIdentityPermissions = async (projectId: string) => {
|
||||
const getProjectIdentityPermissions: TPermissionDALFactory["getProjectIdentityPermissions"] = async (
|
||||
projectId: string
|
||||
) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.IdentityProjectMembership)
|
||||
@ -995,7 +1302,10 @@ export const permissionDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectIdentityPermission = async (identityId: string, projectId: string) => {
|
||||
const getProjectIdentityPermission: TPermissionDALFactory["getProjectIdentityPermission"] = async (
|
||||
identityId,
|
||||
projectId
|
||||
) => {
|
||||
try {
|
||||
const docs = await db
|
||||
.replicaNode()(TableName.IdentityProjectMembership)
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||
import { MongoQuery } from "@ucast/mongo2js";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
|
||||
import { OrgPermissionSet } from "./org-permission";
|
||||
import { ProjectPermissionSet } from "./project-permission";
|
||||
|
||||
export type TBuildProjectPermissionDTO = {
|
||||
permissions?: unknown;
|
||||
role: string;
|
||||
@ -41,3 +47,240 @@ export type TGetProjectPermissionArg = {
|
||||
actorOrgId?: string;
|
||||
actionProjectType: ActionProjectType;
|
||||
};
|
||||
|
||||
export type TPermissionServiceFactory = {
|
||||
getUserOrgPermission: (
|
||||
userId: string,
|
||||
orgId: string,
|
||||
authMethod: ActorAuthMethod,
|
||||
userOrgId?: string
|
||||
) => Promise<{
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
status: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
role: string;
|
||||
isActive: boolean;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
bypassOrgAuthEnabled: boolean;
|
||||
permissions?: unknown;
|
||||
userId?: string | null | undefined;
|
||||
roleId?: string | null | undefined;
|
||||
inviteEmail?: string | null | undefined;
|
||||
projectFavorites?: string[] | null | undefined;
|
||||
customRoleSlug?: string | null | undefined;
|
||||
orgAuthEnforced?: boolean | null | undefined;
|
||||
} & {
|
||||
groups: {
|
||||
id: string;
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
role: string;
|
||||
roleId: string | null | undefined;
|
||||
customRolePermission: unknown;
|
||||
name: string;
|
||||
slug: string;
|
||||
orgId: string;
|
||||
}[];
|
||||
};
|
||||
}>;
|
||||
getOrgPermission: (
|
||||
type: ActorType,
|
||||
id: string,
|
||||
orgId: string,
|
||||
authMethod: ActorAuthMethod,
|
||||
actorOrgId: string | undefined
|
||||
) => Promise<
|
||||
| {
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
status: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
role: string;
|
||||
isActive: boolean;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
bypassOrgAuthEnabled: boolean;
|
||||
permissions?: unknown;
|
||||
userId?: string | null | undefined;
|
||||
roleId?: string | null | undefined;
|
||||
inviteEmail?: string | null | undefined;
|
||||
projectFavorites?: string[] | null | undefined;
|
||||
customRoleSlug?: string | null | undefined;
|
||||
orgAuthEnforced?: boolean | null | undefined;
|
||||
} & {
|
||||
groups: {
|
||||
id: string;
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
role: string;
|
||||
roleId: string | null | undefined;
|
||||
customRolePermission: unknown;
|
||||
name: string;
|
||||
slug: string;
|
||||
orgId: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
| {
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
id: string;
|
||||
role: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
orgId: string;
|
||||
roleId?: string | null | undefined;
|
||||
permissions?: unknown;
|
||||
identityId: string;
|
||||
orgAuthEnforced: boolean | null | undefined;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
};
|
||||
}
|
||||
>;
|
||||
getUserProjectPermission: ({
|
||||
userId,
|
||||
projectId,
|
||||
authMethod,
|
||||
userOrgId,
|
||||
actionProjectType
|
||||
}: TGetUserProjectPermissionArg) => Promise<{
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
userId: string;
|
||||
projectId: string;
|
||||
} & {
|
||||
orgAuthEnforced: boolean | null | undefined;
|
||||
orgId: string;
|
||||
roles: Array<{
|
||||
role: string;
|
||||
}>;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
};
|
||||
hasRole: (role: string) => boolean;
|
||||
}>;
|
||||
getProjectPermission: <T extends ActorType>(
|
||||
arg: TGetProjectPermissionArg
|
||||
) => Promise<
|
||||
T extends ActorType.SERVICE
|
||||
? {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: {
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
};
|
||||
hasRole: (arg: string) => boolean;
|
||||
}
|
||||
: {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
membership: (T extends ActorType.USER
|
||||
? {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
userId: string;
|
||||
projectId: string;
|
||||
}
|
||||
: {
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
identityId: string;
|
||||
}) & {
|
||||
orgAuthEnforced: boolean | null | undefined;
|
||||
orgId: string;
|
||||
roles: Array<{
|
||||
role: string;
|
||||
}>;
|
||||
shouldUseNewPrivilegeSystem: boolean;
|
||||
};
|
||||
hasRole: (role: string) => boolean;
|
||||
}
|
||||
>;
|
||||
getProjectPermissions: (projectId: string) => Promise<{
|
||||
userPermissions: {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
id: string;
|
||||
name: string;
|
||||
membershipId: string;
|
||||
}[];
|
||||
identityPermissions: {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
id: string;
|
||||
name: string;
|
||||
membershipId: string;
|
||||
}[];
|
||||
groupPermissions: {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
id: string;
|
||||
name: string;
|
||||
membershipId: string;
|
||||
}[];
|
||||
}>;
|
||||
getOrgPermissionByRole: (
|
||||
role: string,
|
||||
orgId: string
|
||||
) => Promise<
|
||||
| {
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
role: {
|
||||
name: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
slug: string;
|
||||
permissions?: unknown;
|
||||
description?: string | null | undefined;
|
||||
};
|
||||
}
|
||||
| {
|
||||
permission: MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
role?: undefined;
|
||||
}
|
||||
>;
|
||||
getProjectPermissionByRole: (
|
||||
role: string,
|
||||
projectId: string
|
||||
) => Promise<
|
||||
| {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
role: {
|
||||
name: string;
|
||||
version: number;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
slug: string;
|
||||
permissions?: unknown;
|
||||
description?: string | null | undefined;
|
||||
};
|
||||
}
|
||||
| {
|
||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||
role?: undefined;
|
||||
}
|
||||
>;
|
||||
buildOrgPermission: (orgUserRoles: TBuildOrgPermissionDTO) => MongoAbility<OrgPermissionSet, MongoQuery>;
|
||||
buildProjectPermissionRules: (
|
||||
projectUserRoles: TBuildProjectPermissionDTO
|
||||
) => RawRuleOf<MongoAbility<ProjectPermissionSet>>[];
|
||||
checkGroupProjectPermission: ({
|
||||
groupId,
|
||||
projectId,
|
||||
checkPermissions
|
||||
}: {
|
||||
groupId: string;
|
||||
projectId: string;
|
||||
checkPermissions: ProjectPermissionSet;
|
||||
}) => Promise<boolean>;
|
||||
};
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
import { conditionsMatcher } from "@app/lib/casl";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { objectify } from "@app/lib/fn";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { ActorType } from "@app/services/auth/auth-type";
|
||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { TProjectRoleDALFactory } from "@app/services/project-role/project-role-dal";
|
||||
@ -38,7 +38,8 @@ import {
|
||||
TGetIdentityProjectPermissionArg,
|
||||
TGetProjectPermissionArg,
|
||||
TGetServiceTokenProjectPermissionArg,
|
||||
TGetUserProjectPermissionArg
|
||||
TGetUserProjectPermissionArg,
|
||||
TPermissionServiceFactory
|
||||
} from "./permission-service-types";
|
||||
import { buildServiceTokenProjectPermission, ProjectPermissionSet } from "./project-permission";
|
||||
|
||||
@ -50,15 +51,13 @@ type TPermissionServiceFactoryDep = {
|
||||
permissionDAL: TPermissionDALFactory;
|
||||
};
|
||||
|
||||
export type TPermissionServiceFactory = ReturnType<typeof permissionServiceFactory>;
|
||||
|
||||
export const permissionServiceFactory = ({
|
||||
permissionDAL,
|
||||
orgRoleDAL,
|
||||
projectRoleDAL,
|
||||
serviceTokenDAL,
|
||||
projectDAL
|
||||
}: TPermissionServiceFactoryDep) => {
|
||||
}: TPermissionServiceFactoryDep): TPermissionServiceFactory => {
|
||||
const buildOrgPermission = (orgUserRoles: TBuildOrgPermissionDTO) => {
|
||||
const rules = orgUserRoles
|
||||
.map(({ role, permissions }) => {
|
||||
@ -120,11 +119,11 @@ export const permissionServiceFactory = ({
|
||||
/*
|
||||
* Get user permission in an organization
|
||||
*/
|
||||
const getUserOrgPermission = async (
|
||||
userId: string,
|
||||
orgId: string,
|
||||
authMethod: ActorAuthMethod,
|
||||
userOrgId?: string
|
||||
const getUserOrgPermission: TPermissionServiceFactory["getUserOrgPermission"] = async (
|
||||
userId,
|
||||
orgId,
|
||||
authMethod,
|
||||
userOrgId
|
||||
) => {
|
||||
// when token is scoped, ensure the passed org id is same as user org id
|
||||
if (userOrgId && userOrgId !== orgId)
|
||||
@ -172,12 +171,12 @@ export const permissionServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getOrgPermission = async (
|
||||
type: ActorType,
|
||||
id: string,
|
||||
orgId: string,
|
||||
authMethod: ActorAuthMethod,
|
||||
actorOrgId: string | undefined
|
||||
const getOrgPermission: TPermissionServiceFactory["getOrgPermission"] = async (
|
||||
type,
|
||||
id,
|
||||
orgId,
|
||||
authMethod,
|
||||
actorOrgId
|
||||
) => {
|
||||
switch (type) {
|
||||
case ActorType.USER:
|
||||
@ -194,7 +193,7 @@ export const permissionServiceFactory = ({
|
||||
|
||||
// instead of actor type this will fetch by role slug. meaning it can be the pre defined slugs like
|
||||
// admin member or user defined ones like biller etc
|
||||
const getOrgPermissionByRole = async (role: string, orgId: string) => {
|
||||
const getOrgPermissionByRole: TPermissionServiceFactory["getOrgPermissionByRole"] = async (role, orgId) => {
|
||||
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
|
||||
if (isCustomRole) {
|
||||
const orgRole = await orgRoleDAL.findOne({ slug: role, orgId });
|
||||
@ -437,7 +436,7 @@ export const permissionServiceFactory = ({
|
||||
hasRole: (role: string) => boolean;
|
||||
};
|
||||
|
||||
const getProjectPermissions = async (projectId: string) => {
|
||||
const getProjectPermissions: TPermissionServiceFactory["getProjectPermissions"] = async (projectId) => {
|
||||
// fetch user permissions
|
||||
const rawUserProjectPermissions = await permissionDAL.getProjectUserPermissions(projectId);
|
||||
const userPermissions = rawUserProjectPermissions.map((userProjectPermission) => {
|
||||
@ -607,7 +606,10 @@ export const permissionServiceFactory = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectPermissionByRole = async (role: string, projectId: string) => {
|
||||
const getProjectPermissionByRole: TPermissionServiceFactory["getProjectPermissionByRole"] = async (
|
||||
role,
|
||||
projectId
|
||||
) => {
|
||||
const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole);
|
||||
if (isCustomRole) {
|
||||
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
|
||||
@ -630,14 +632,10 @@ export const permissionServiceFactory = ({
|
||||
return { permission };
|
||||
};
|
||||
|
||||
const checkGroupProjectPermission = async ({
|
||||
const checkGroupProjectPermission: TPermissionServiceFactory["checkGroupProjectPermission"] = async ({
|
||||
groupId,
|
||||
projectId,
|
||||
checkPermissions
|
||||
}: {
|
||||
groupId: string;
|
||||
projectId: string;
|
||||
checkPermissions: ProjectPermissionSet;
|
||||
}) => {
|
||||
const rawGroupProjectPermissions = await permissionDAL.getProjectGroupPermissions(projectId, groupId);
|
||||
const groupPermissions = rawGroupProjectPermissions.map((groupProjectPermission) => {
|
||||
|
@ -211,6 +211,11 @@ export type SecretFolderSubjectFields = {
|
||||
secretPath: string;
|
||||
};
|
||||
|
||||
export type SecretSyncSubjectFields = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
};
|
||||
|
||||
export type DynamicSecretSubjectFields = {
|
||||
environment: string;
|
||||
secretPath: string;
|
||||
@ -267,6 +272,10 @@ export type ProjectPermissionSet =
|
||||
| (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields)
|
||||
)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionSecretSyncActions,
|
||||
ProjectPermissionSub.SecretSyncs | (ForcedSubject<ProjectPermissionSub.SecretSyncs> & SecretSyncSubjectFields)
|
||||
]
|
||||
| [
|
||||
ProjectPermissionActions,
|
||||
(
|
||||
@ -323,7 +332,6 @@ export type ProjectPermissionSet =
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.SshHostGroups]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
||||
| [ProjectPermissionKmipActions, ProjectPermissionSub.Kmip]
|
||||
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
|
||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||
@ -412,6 +420,23 @@ const DynamicSecretConditionV2Schema = z
|
||||
})
|
||||
.partial();
|
||||
|
||||
const SecretSyncConditionV2Schema = z
|
||||
.object({
|
||||
environment: z.union([
|
||||
z.string(),
|
||||
z
|
||||
.object({
|
||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||
})
|
||||
.partial()
|
||||
]),
|
||||
secretPath: SECRET_PATH_PERMISSION_OPERATOR_SCHEMA
|
||||
})
|
||||
.partial();
|
||||
|
||||
const SecretImportConditionSchema = z
|
||||
.object({
|
||||
environment: z.union([
|
||||
@ -671,12 +696,6 @@ const GeneralPermissionSchema = [
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.SecretSyncs).describe("The entity this permission pertains to."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretSyncActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
)
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.Kmip).describe("The entity this permission pertains to."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionKmipActions).describe(
|
||||
@ -836,6 +855,16 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
||||
"When specified, only matching conditions will be allowed to access given resource."
|
||||
).optional()
|
||||
}),
|
||||
z.object({
|
||||
subject: z.literal(ProjectPermissionSub.SecretSyncs).describe("The entity this permission pertains to."),
|
||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretSyncActions).describe(
|
||||
"Describe what action an entity can take."
|
||||
),
|
||||
conditions: SecretSyncConditionV2Schema.describe(
|
||||
"When specified, only matching conditions will be allowed to access given resource."
|
||||
).optional()
|
||||
}),
|
||||
|
||||
...GeneralPermissionSchema
|
||||
]);
|
||||
|
@ -16,7 +16,7 @@ import { TSecretServiceFactory } from "@app/services/secret/secret-service";
|
||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||
import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service";
|
||||
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
|
||||
type TPitServiceFactoryDep = {
|
||||
folderCommitService: TFolderCommitServiceFactory;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TProjectTemplateDALFactory = ReturnType<typeof projectTemplateDALFactory>;
|
||||
export type TProjectTemplateDALFactory = TOrmify<TableName.ProjectTemplates>;
|
||||
|
||||
export const projectTemplateDALFactory = (db: TDbClient) => ormify(db, TableName.ProjectTemplates);
|
||||
export const projectTemplateDALFactory = (db: TDbClient): TProjectTemplateDALFactory =>
|
||||
ormify(db, TableName.ProjectTemplates);
|
||||
|
@ -4,18 +4,16 @@ import { packRules } from "@casl/ability/extra";
|
||||
import { ProjectType, TProjectTemplates } from "@app/db/schemas";
|
||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import { ProjectTemplateDefaultEnvironments } from "@app/ee/services/project-template/project-template-constants";
|
||||
import { getDefaultProjectTemplate } from "@app/ee/services/project-template/project-template-fns";
|
||||
import {
|
||||
TCreateProjectTemplateDTO,
|
||||
TProjectTemplateEnvironment,
|
||||
TProjectTemplateRole,
|
||||
TUnpackedPermission,
|
||||
TUpdateProjectTemplateDTO
|
||||
TProjectTemplateServiceFactory,
|
||||
TUnpackedPermission
|
||||
} from "@app/ee/services/project-template/project-template-types";
|
||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
|
||||
import { getPredefinedRoles } from "@app/services/project-role/project-role-fns";
|
||||
|
||||
@ -27,8 +25,6 @@ type TProjectTemplatesServiceFactoryDep = {
|
||||
projectTemplateDAL: TProjectTemplateDALFactory;
|
||||
};
|
||||
|
||||
export type TProjectTemplateServiceFactory = ReturnType<typeof projectTemplateServiceFactory>;
|
||||
|
||||
const $unpackProjectTemplate = ({ roles, environments, ...rest }: TProjectTemplates) => ({
|
||||
...rest,
|
||||
environments: environments as TProjectTemplateEnvironment[],
|
||||
@ -51,8 +47,11 @@ export const projectTemplateServiceFactory = ({
|
||||
licenseService,
|
||||
permissionService,
|
||||
projectTemplateDAL
|
||||
}: TProjectTemplatesServiceFactoryDep) => {
|
||||
const listProjectTemplatesByOrg = async (actor: OrgServiceActor, type?: ProjectType) => {
|
||||
}: TProjectTemplatesServiceFactoryDep): TProjectTemplateServiceFactory => {
|
||||
const listProjectTemplatesByOrg: TProjectTemplateServiceFactory["listProjectTemplatesByOrg"] = async (
|
||||
actor,
|
||||
type
|
||||
) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
if (!plan.projectTemplates)
|
||||
@ -83,7 +82,10 @@ export const projectTemplateServiceFactory = ({
|
||||
];
|
||||
};
|
||||
|
||||
const findProjectTemplateByName = async (name: string, actor: OrgServiceActor) => {
|
||||
const findProjectTemplateByName: TProjectTemplateServiceFactory["findProjectTemplateByName"] = async (
|
||||
name,
|
||||
actor
|
||||
) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
if (!plan.projectTemplates)
|
||||
@ -111,7 +113,7 @@ export const projectTemplateServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const findProjectTemplateById = async (id: string, actor: OrgServiceActor) => {
|
||||
const findProjectTemplateById: TProjectTemplateServiceFactory["findProjectTemplateById"] = async (id, actor) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
if (!plan.projectTemplates)
|
||||
@ -139,9 +141,9 @@ export const projectTemplateServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const createProjectTemplate = async (
|
||||
{ roles, environments, type, ...params }: TCreateProjectTemplateDTO,
|
||||
actor: OrgServiceActor
|
||||
const createProjectTemplate: TProjectTemplateServiceFactory["createProjectTemplate"] = async (
|
||||
{ roles, environments, type, ...params },
|
||||
actor
|
||||
) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
@ -195,10 +197,10 @@ export const projectTemplateServiceFactory = ({
|
||||
return $unpackProjectTemplate(projectTemplate);
|
||||
};
|
||||
|
||||
const updateProjectTemplateById = async (
|
||||
id: string,
|
||||
{ roles, environments, ...params }: TUpdateProjectTemplateDTO,
|
||||
actor: OrgServiceActor
|
||||
const updateProjectTemplateById: TProjectTemplateServiceFactory["updateProjectTemplateById"] = async (
|
||||
id,
|
||||
{ roles, environments, ...params },
|
||||
actor
|
||||
) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
@ -259,7 +261,7 @@ export const projectTemplateServiceFactory = ({
|
||||
return $unpackProjectTemplate(updatedProjectTemplate);
|
||||
};
|
||||
|
||||
const deleteProjectTemplateById = async (id: string, actor: OrgServiceActor) => {
|
||||
const deleteProjectTemplateById: TProjectTemplateServiceFactory["deleteProjectTemplateById"] = async (id, actor) => {
|
||||
const plan = await licenseService.getPlan(actor.orgId);
|
||||
|
||||
if (!plan.projectTemplates)
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { ProjectType, TProjectEnvironments } from "@app/db/schemas";
|
||||
import { ProjectMembershipRole, ProjectType, TProjectEnvironments } from "@app/db/schemas";
|
||||
import { TProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||
import { OrgServiceActor } from "@app/lib/types";
|
||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||
|
||||
export type TProjectTemplateEnvironment = Pick<TProjectEnvironments, "name" | "slug" | "position">;
|
||||
@ -27,3 +28,177 @@ export type TUnpackedPermission = z.infer<typeof UnpackedPermissionSchema>;
|
||||
export enum InfisicalProjectTemplate {
|
||||
Default = "default"
|
||||
}
|
||||
|
||||
export type TProjectTemplateServiceFactory = {
|
||||
listProjectTemplatesByOrg: (
|
||||
actor: OrgServiceActor,
|
||||
type?: ProjectType
|
||||
) => Promise<
|
||||
(
|
||||
| {
|
||||
id: string;
|
||||
type: ProjectType;
|
||||
name: InfisicalProjectTemplate;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description: string;
|
||||
environments:
|
||||
| {
|
||||
name: string;
|
||||
slug: string;
|
||||
position: number;
|
||||
}[]
|
||||
| null;
|
||||
roles: {
|
||||
name: string;
|
||||
slug: ProjectMembershipRole;
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
}[];
|
||||
orgId: string;
|
||||
}
|
||||
| {
|
||||
environments: TProjectTemplateEnvironment[];
|
||||
roles: {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
slug: string;
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
}
|
||||
)[]
|
||||
>;
|
||||
createProjectTemplate: (
|
||||
arg: TCreateProjectTemplateDTO,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<{
|
||||
environments: TProjectTemplateEnvironment[];
|
||||
roles: {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
slug: string;
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
}>;
|
||||
updateProjectTemplateById: (
|
||||
id: string,
|
||||
{ roles, environments, ...params }: TUpdateProjectTemplateDTO,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<{
|
||||
environments: TProjectTemplateEnvironment[];
|
||||
roles: {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
slug: string;
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
}>;
|
||||
deleteProjectTemplateById: (
|
||||
id: string,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<{
|
||||
environments: TProjectTemplateEnvironment[];
|
||||
roles: {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
slug: string;
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
}>;
|
||||
findProjectTemplateById: (
|
||||
id: string,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<{
|
||||
packedRoles: TProjectTemplateRole[];
|
||||
environments: TProjectTemplateEnvironment[];
|
||||
roles: {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
slug: string;
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
}>;
|
||||
findProjectTemplateByName: (
|
||||
name: string,
|
||||
actor: OrgServiceActor
|
||||
) => Promise<{
|
||||
packedRoles: TProjectTemplateRole[];
|
||||
environments: TProjectTemplateEnvironment[];
|
||||
roles: {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
slug: string;
|
||||
name: string;
|
||||
}[];
|
||||
name: string;
|
||||
type: string;
|
||||
orgId: string;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
description?: string | null | undefined;
|
||||
}>;
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TProjectUserAdditionalPrivilegeDALFactory = ReturnType<typeof projectUserAdditionalPrivilegeDALFactory>;
|
||||
export type TProjectUserAdditionalPrivilegeDALFactory = TOrmify<TableName.ProjectUserAdditionalPrivilege>;
|
||||
|
||||
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient) => {
|
||||
export const projectUserAdditionalPrivilegeDALFactory = (db: TDbClient): TProjectUserAdditionalPrivilegeDALFactory => {
|
||||
const orm = ormify(db, TableName.ProjectUserAdditionalPrivilege);
|
||||
return orm;
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ import { TProjectMembershipDALFactory } from "@app/services/project-membership/p
|
||||
|
||||
import { TAccessApprovalRequestDALFactory } from "../access-approval-request/access-approval-request-dal";
|
||||
import { constructPermissionErrorMessage, validatePrivilegeChangeOperation } from "../permission/permission-fns";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionMemberActions,
|
||||
ProjectPermissionSet,
|
||||
@ -21,11 +21,7 @@ import { ApprovalStatus } from "../secret-approval-request/secret-approval-reque
|
||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||
import {
|
||||
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||
TCreateUserPrivilegeDTO,
|
||||
TDeleteUserPrivilegeDTO,
|
||||
TGetUserPrivilegeDetailsDTO,
|
||||
TListUserPrivilegesDTO,
|
||||
TUpdateUserPrivilegeDTO
|
||||
TProjectUserAdditionalPrivilegeServiceFactory
|
||||
} from "./project-user-additional-privilege-types";
|
||||
|
||||
type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||
@ -35,10 +31,6 @@ type TProjectUserAdditionalPrivilegeServiceFactoryDep = {
|
||||
accessApprovalRequestDAL: Pick<TAccessApprovalRequestDALFactory, "update">;
|
||||
};
|
||||
|
||||
export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
||||
typeof projectUserAdditionalPrivilegeServiceFactory
|
||||
>;
|
||||
|
||||
const unpackPermissions = (permissions: unknown) =>
|
||||
UnpackedPermissionSchema.array().parse(
|
||||
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||
@ -49,8 +41,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
projectMembershipDAL,
|
||||
permissionService,
|
||||
accessApprovalRequestDAL
|
||||
}: TProjectUserAdditionalPrivilegeServiceFactoryDep) => {
|
||||
const create = async ({
|
||||
}: TProjectUserAdditionalPrivilegeServiceFactoryDep): TProjectUserAdditionalPrivilegeServiceFactory => {
|
||||
const create: TProjectUserAdditionalPrivilegeServiceFactory["create"] = async ({
|
||||
slug,
|
||||
actor,
|
||||
actorId,
|
||||
@ -59,7 +51,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
actorAuthMethod,
|
||||
projectMembershipId,
|
||||
...dto
|
||||
}: TCreateUserPrivilegeDTO) => {
|
||||
}) => {
|
||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||
if (!projectMembership)
|
||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
||||
@ -147,14 +139,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const updateById = async ({
|
||||
const updateById: TProjectUserAdditionalPrivilegeServiceFactory["updateById"] = async ({
|
||||
privilegeId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
...dto
|
||||
}: TUpdateUserPrivilegeDTO) => {
|
||||
}) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege)
|
||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||
@ -259,7 +251,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
||||
const deleteById: TProjectUserAdditionalPrivilegeServiceFactory["deleteById"] = async ({
|
||||
actorId,
|
||||
actor,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
privilegeId
|
||||
}) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege)
|
||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||
@ -299,13 +297,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const getPrivilegeDetailsById = async ({
|
||||
const getPrivilegeDetailsById: TProjectUserAdditionalPrivilegeServiceFactory["getPrivilegeDetailsById"] = async ({
|
||||
privilegeId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TGetUserPrivilegeDetailsDTO) => {
|
||||
}) => {
|
||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||
if (!userPrivilege)
|
||||
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||
@ -335,13 +333,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||
};
|
||||
};
|
||||
|
||||
const listPrivileges = async ({
|
||||
const listPrivileges: TProjectUserAdditionalPrivilegeServiceFactory["listPrivileges"] = async ({
|
||||
projectMembershipId,
|
||||
actorOrgId,
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod
|
||||
}: TListUserPrivilegesDTO) => {
|
||||
}) => {
|
||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||
if (!projectMembership)
|
||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { TProjectUserAdditionalPrivilege } from "@app/db/schemas";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||
@ -40,3 +41,20 @@ export type TDeleteUserPrivilegeDTO = Omit<TProjectPermission, "projectId"> & {
|
||||
export type TGetUserPrivilegeDetailsDTO = Omit<TProjectPermission, "projectId"> & { privilegeId: string };
|
||||
|
||||
export type TListUserPrivilegesDTO = Omit<TProjectPermission, "projectId"> & { projectMembershipId: string };
|
||||
|
||||
interface TAdditionalPrivilege extends TProjectUserAdditionalPrivilege {
|
||||
permissions: {
|
||||
action: string[];
|
||||
subject?: string | undefined;
|
||||
conditions?: unknown;
|
||||
inverted?: boolean | undefined;
|
||||
}[];
|
||||
}
|
||||
|
||||
export type TProjectUserAdditionalPrivilegeServiceFactory = {
|
||||
create: (arg: TCreateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
||||
updateById: (arg: TUpdateUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
||||
deleteById: (arg: TDeleteUserPrivilegeDTO) => Promise<TAdditionalPrivilege>;
|
||||
getPrivilegeDetailsById: (arg: TGetUserPrivilegeDetailsDTO) => Promise<TAdditionalPrivilege>;
|
||||
listPrivileges: (arg: TListUserPrivilegesDTO) => Promise<TProjectUserAdditionalPrivilege[]>;
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TRateLimitDALFactory = ReturnType<typeof rateLimitDALFactory>;
|
||||
export type TRateLimitDALFactory = TOrmify<TableName.RateLimit>;
|
||||
|
||||
export const rateLimitDALFactory = (db: TDbClient) => ormify(db, TableName.RateLimit, {});
|
||||
export const rateLimitDALFactory = (db: TDbClient): TRateLimitDALFactory => ormify(db, TableName.RateLimit, {});
|
||||
|
@ -4,7 +4,7 @@ import { logger } from "@app/lib/logger";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TRateLimitDALFactory } from "./rate-limit-dal";
|
||||
import { RateLimitConfiguration, TRateLimit, TRateLimitUpdateDTO } from "./rate-limit-types";
|
||||
import { RateLimitConfiguration, TRateLimit, TRateLimitServiceFactory } from "./rate-limit-types";
|
||||
|
||||
let rateLimitMaxConfiguration: RateLimitConfiguration = {
|
||||
readLimit: 60,
|
||||
@ -27,12 +27,13 @@ type TRateLimitServiceFactoryDep = {
|
||||
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
|
||||
};
|
||||
|
||||
export type TRateLimitServiceFactory = ReturnType<typeof rateLimitServiceFactory>;
|
||||
|
||||
export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateLimitServiceFactoryDep) => {
|
||||
export const rateLimitServiceFactory = ({
|
||||
rateLimitDAL,
|
||||
licenseService
|
||||
}: TRateLimitServiceFactoryDep): TRateLimitServiceFactory => {
|
||||
const DEFAULT_RATE_LIMIT_CONFIG_ID = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
const getRateLimits = async (): Promise<TRateLimit | undefined> => {
|
||||
const getRateLimits: TRateLimitServiceFactory["getRateLimits"] = async () => {
|
||||
let rateLimit: TRateLimit;
|
||||
|
||||
try {
|
||||
@ -51,11 +52,11 @@ export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateL
|
||||
}
|
||||
};
|
||||
|
||||
const updateRateLimit = async (updates: TRateLimitUpdateDTO): Promise<TRateLimit> => {
|
||||
const updateRateLimit: TRateLimitServiceFactory["updateRateLimit"] = async (updates) => {
|
||||
return rateLimitDAL.updateById(DEFAULT_RATE_LIMIT_CONFIG_ID, updates);
|
||||
};
|
||||
|
||||
const syncRateLimitConfiguration = async () => {
|
||||
const syncRateLimitConfiguration: TRateLimitServiceFactory["syncRateLimitConfiguration"] = async () => {
|
||||
try {
|
||||
const rateLimit = await getRateLimits();
|
||||
if (rateLimit) {
|
||||
@ -78,7 +79,7 @@ export const rateLimitServiceFactory = ({ rateLimitDAL, licenseService }: TRateL
|
||||
}
|
||||
};
|
||||
|
||||
const initializeBackgroundSync = async () => {
|
||||
const initializeBackgroundSync: TRateLimitServiceFactory["initializeBackgroundSync"] = async () => {
|
||||
if (!licenseService.onPremFeatures.customRateLimits) {
|
||||
logger.info("Current license does not support custom rate limit configuration");
|
||||
return;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { CronJob } from "cron";
|
||||
|
||||
export type TRateLimitUpdateDTO = {
|
||||
readRateLimit: number;
|
||||
writeRateLimit: number;
|
||||
@ -23,3 +25,10 @@ export type RateLimitConfiguration = {
|
||||
inviteUserRateLimit: number;
|
||||
mfaRateLimit: number;
|
||||
};
|
||||
|
||||
export type TRateLimitServiceFactory = {
|
||||
getRateLimits: () => Promise<TRateLimit | undefined>;
|
||||
updateRateLimit: (updates: TRateLimitUpdateDTO) => Promise<TRateLimit>;
|
||||
initializeBackgroundSync: () => Promise<CronJob<null, null> | undefined>;
|
||||
syncRateLimitConfiguration: () => Promise<void>;
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { TDbClient } from "@app/db";
|
||||
import { TableName } from "@app/db/schemas";
|
||||
import { ormify } from "@app/lib/knex";
|
||||
import { ormify, TOrmify } from "@app/lib/knex";
|
||||
|
||||
export type TSamlConfigDALFactory = ReturnType<typeof samlConfigDALFactory>;
|
||||
export type TSamlConfigDALFactory = TOrmify<TableName.SamlConfig>;
|
||||
|
||||
export const samlConfigDALFactory = (db: TDbClient) => {
|
||||
export const samlConfigDALFactory = (db: TDbClient): TSamlConfigDALFactory => {
|
||||
const samlCfgOrm = ormify(db, TableName.SamlConfig);
|
||||
|
||||
return samlCfgOrm;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user