mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-29 22:37:44 +00:00
Compare commits
345 Commits
misc/add-p
...
daniel/del
Author | SHA1 | Date | |
---|---|---|---|
|
da35ec90bc | ||
|
4e48ab1eeb | ||
|
a6671b4355 | ||
|
4c7ae3475a | ||
|
49797c3c13 | ||
|
7d9c5657aa | ||
|
eda4abb610 | ||
|
e341bbae9d | ||
|
7286f9a9e6 | ||
|
1c9a9283ae | ||
|
8d52011173 | ||
|
1b5b937db5 | ||
|
7b8b024654 | ||
|
a67badf660 | ||
|
ba42ea736b | ||
|
6c7289ebe6 | ||
|
5cd6a66989 | ||
|
4e41e84491 | ||
|
85d71b1085 | ||
|
9d66659f72 | ||
|
70c9761abe | ||
|
6047c4489b | ||
|
c9d7559983 | ||
|
66251403bf | ||
|
b9c4407507 | ||
|
624be80768 | ||
|
8d7b5968d3 | ||
|
b7d4bb0ce2 | ||
|
598dea0dd3 | ||
|
7154b19703 | ||
|
9ce465b3e2 | ||
|
598e5c0be5 | ||
|
72f08a6b89 | ||
|
55d8762351 | ||
|
3c92ec4dc3 | ||
|
f2224262a4 | ||
|
23eac40740 | ||
|
4ae88c0447 | ||
|
7aecaad050 | ||
|
cf61390e52 | ||
|
3f02481e78 | ||
|
7adc103ed2 | ||
|
5bdbf37171 | ||
|
4f874734ab | ||
|
eb6fd8259b | ||
|
1766a44dd0 | ||
|
624c9ef8da | ||
|
dfd4b13574 | ||
|
22b57b7a74 | ||
|
1ba0b9c204 | ||
|
a903537441 | ||
|
92c4d83714 | ||
|
a6414104ad | ||
|
071f37666e | ||
|
cd5078d8b7 | ||
|
110d0e95b0 | ||
|
a8c0bbb7ca | ||
|
6af8a4fab8 | ||
|
407fd8eda7 | ||
|
9d976de19b | ||
|
43ecd31b74 | ||
|
be99e40050 | ||
|
800d2c0454 | ||
|
6d0534b165 | ||
|
ccee0f5428 | ||
|
14586c7cd0 | ||
|
7090eea716 | ||
|
01d3443139 | ||
|
c4b23a8d4f | ||
|
90a2a11fff | ||
|
95d7c2082c | ||
|
ab5eb4c696 | ||
|
65aeb81934 | ||
|
a406511405 | ||
|
61da0db49e | ||
|
0968893d4b | ||
|
59666740ca | ||
|
9cc7edc869 | ||
|
e1b016f76d | ||
|
1175b9b5af | ||
|
09521144ec | ||
|
8759944077 | ||
|
aac3c355e9 | ||
|
2a28a462a5 | ||
|
3328e0850f | ||
|
216cae9b33 | ||
|
d24a5d96e3 | ||
|
89d4d4bc92 | ||
|
cffcb28bc9 | ||
|
61388753cf | ||
|
a6145120e6 | ||
|
dacffbef08 | ||
|
4db3e5d208 | ||
|
2a84d61862 | ||
|
a5945204ad | ||
|
55b0dc7f81 | ||
|
ba03fc256b | ||
|
ea28c374a7 | ||
|
e99eb47cf4 | ||
|
cf107c0c0d | ||
|
9fcb1c2161 | ||
|
70515a1ca2 | ||
|
955cf9303a | ||
|
a24ef46d7d | ||
|
ee49f714b9 | ||
|
657aca516f | ||
|
b5d60398d6 | ||
|
c3d515bb95 | ||
|
7f89a7c860 | ||
|
23cb05c16d | ||
|
d74b819f57 | ||
|
457056b600 | ||
|
7dc9ea4f6a | ||
|
3b4b520d42 | ||
|
23f605bda7 | ||
|
1c3c8dbdce | ||
|
317c95384e | ||
|
7dd959e124 | ||
|
2049e5668f | ||
|
0a3e99b334 | ||
|
c4ad0aa163 | ||
|
5bb0b7a508 | ||
|
96bcd42753 | ||
|
2c75e23acf | ||
|
907dd4880a | ||
|
6af7c5c371 | ||
|
72468d5428 | ||
|
939ee892e0 | ||
|
c7ec9ff816 | ||
|
554e268f88 | ||
|
a8a27c3045 | ||
|
27af943ee1 | ||
|
9b772ad55a | ||
|
94a1fc2809 | ||
|
10c10642a1 | ||
|
3e0f04273c | ||
|
91f2d0384e | ||
|
811dc8dd75 | ||
|
4ee9375a8d | ||
|
92f697e195 | ||
|
8062f0238b | ||
|
1181c684db | ||
|
dda436bcd9 | ||
|
89124b18d2 | ||
|
effd88c4bd | ||
|
27efc908e2 | ||
|
8e4226038b | ||
|
27425a1a64 | ||
|
18cf3c89c1 | ||
|
49e6d7a861 | ||
|
c4446389b0 | ||
|
7c21dec54d | ||
|
2ea5710896 | ||
|
f9ac7442df | ||
|
a534a4975c | ||
|
79a616dc1c | ||
|
a93bfa69c9 | ||
|
598d14fc54 | ||
|
08a0550cd7 | ||
|
d7503573b1 | ||
|
b5a89edeed | ||
|
860eaae4c8 | ||
|
c7a4b6c4e9 | ||
|
c12c6dcc6e | ||
|
99c9b644df | ||
|
d0d5556bd0 | ||
|
753c28a2d3 | ||
|
8741414cfa | ||
|
b8d29793ec | ||
|
92013dbfbc | ||
|
c5319588fe | ||
|
9efb8eaf78 | ||
|
dfc973c7f7 | ||
|
3013d1977c | ||
|
f358e8942d | ||
|
58f51411c0 | ||
|
c3970d1ea2 | ||
|
2dc00a638a | ||
|
94aed485a5 | ||
|
e382941424 | ||
|
bab9c1f454 | ||
|
2bd4770fb4 | ||
|
31905fab6e | ||
|
784acf16d0 | ||
|
114b89c952 | ||
|
81420198cb | ||
|
b949708f45 | ||
|
2a6b6b03b9 | ||
|
0ff18e277f | ||
|
e093f70301 | ||
|
8e2ff18f35 | ||
|
3fbfecf7a9 | ||
|
9087def21c | ||
|
89c6ab591a | ||
|
235a33a01c | ||
|
dd6c217dc8 | ||
|
78b1b5583a | ||
|
8f2a504fd0 | ||
|
1d5b629d8f | ||
|
14f895cae2 | ||
|
b7be6bd1d9 | ||
|
58a97852f6 | ||
|
980aa9eaae | ||
|
a35d1aa72b | ||
|
52d801bce5 | ||
|
c92c160709 | ||
|
71ca7a82db | ||
|
6f799b478d | ||
|
a89e6b6e58 | ||
|
99ca9e04f8 | ||
|
586dbd79b0 | ||
|
6cdc71b9b1 | ||
|
f88d6a183f | ||
|
fa82d4953e | ||
|
12d9fe9ffd | ||
|
86acf88a13 | ||
|
63c7c39e21 | ||
|
151edc7efa | ||
|
5fa7f56285 | ||
|
810b27d121 | ||
|
51fe7450ae | ||
|
938c06a2ed | ||
|
38d431ec77 | ||
|
d202fdf5c8 | ||
|
f1b2028542 | ||
|
5c9b46dfba | ||
|
a516e50984 | ||
|
3c1fc024c2 | ||
|
569439f208 | ||
|
9afc282679 | ||
|
8db85cfb84 | ||
|
664b2f0089 | ||
|
5e9bd3a7c6 | ||
|
2c13af6db3 | ||
|
ec9171d0bc | ||
|
81362bec8f | ||
|
5a4d7541a2 | ||
|
3c97c45455 | ||
|
4f015d77fb | ||
|
78e894c2bb | ||
|
23513158ed | ||
|
934ef8ab27 | ||
|
23e9c52f67 | ||
|
e276752e7c | ||
|
01ae19fa2b | ||
|
9df8cf60ef | ||
|
1b1fe2a700 | ||
|
338961480c | ||
|
03debcab5a | ||
|
645dfafba0 | ||
|
4a6f759900 | ||
|
b9d06ff686 | ||
|
5cc5a4f03d | ||
|
5ef2be1a9c | ||
|
8de9ddfb8b | ||
|
5b40de16cf | ||
|
11aac3f5dc | ||
|
9823c7d1aa | ||
|
d627ecf05d | ||
|
3ba396f7fa | ||
|
9c561266ed | ||
|
36fef11d91 | ||
|
742932c4a0 | ||
|
57a77ae5f1 | ||
|
7c9564c7dc | ||
|
736aecebf8 | ||
|
16748357d7 | ||
|
12863b389b | ||
|
6341b7e989 | ||
|
c592ff00a6 | ||
|
ef87086272 | ||
|
bd459d994c | ||
|
440f93f392 | ||
|
bc32d6cbbf | ||
|
0cf3115830 | ||
|
65f2e626ae | ||
|
8b3e3152a4 | ||
|
661b31f762 | ||
|
e78ad1147b | ||
|
473efa91f0 | ||
|
b440e918ac | ||
|
439f253350 | ||
|
4e68304262 | ||
|
c4d0896609 | ||
|
b8115d481c | ||
|
5fdec97319 | ||
|
755bb1679a | ||
|
7142e7a6c6 | ||
|
11ade92b5b | ||
|
4147725260 | ||
|
c359cf162f | ||
|
9d04b648fa | ||
|
9edfdb7234 | ||
|
ff74e020fc | ||
|
6ee446e574 | ||
|
c806059b11 | ||
|
3a5bb31bde | ||
|
6f38d6c76f | ||
|
d721a46ec9 | ||
|
989065ba34 | ||
|
5ad419c079 | ||
|
80f72e8040 | ||
|
0cef728617 | ||
|
0a735422ed | ||
|
8370a0d9c0 | ||
|
71af662998 | ||
|
27e391f7e0 | ||
|
2187c7588c | ||
|
8ab7e8360d | ||
|
957cab3117 | ||
|
4bf16f68fc | ||
|
ab3ee775bb | ||
|
2a86e6f4d1 | ||
|
194fbb79f2 | ||
|
faaba8deb7 | ||
|
ae21b157a9 | ||
|
6167c70a74 | ||
|
0ecf75cbdb | ||
|
3f8aa0fa4b | ||
|
6487c83bda | ||
|
c08fbbdab2 | ||
|
a4aa65bb81 | ||
|
258d19cbe4 | ||
|
de91356127 | ||
|
ccb07942de | ||
|
3d278b0925 | ||
|
956fb2efb4 | ||
|
13894261ce | ||
|
d7ffa70906 | ||
|
b8fa7c5bb6 | ||
|
2baacfcd8f | ||
|
31c11f7d2a | ||
|
c5f06dece4 | ||
|
662e79ac98 | ||
|
17249d603b | ||
|
9bdff9c504 | ||
|
4552ce6ca4 | ||
|
ba4b8801eb | ||
|
36a5f728a1 | ||
|
502429d914 | ||
|
27abfa4fff | ||
|
4d43accc8a | ||
|
3c89a69410 | ||
|
e741b63e63 | ||
|
60749cfc43 |
18
.env.example
18
.env.example
@@ -26,7 +26,8 @@ SITE_URL=http://localhost:8080
|
|||||||
# Mail/SMTP
|
# Mail/SMTP
|
||||||
SMTP_HOST=
|
SMTP_HOST=
|
||||||
SMTP_PORT=
|
SMTP_PORT=
|
||||||
SMTP_NAME=
|
SMTP_FROM_ADDRESS=
|
||||||
|
SMTP_FROM_NAME=
|
||||||
SMTP_USERNAME=
|
SMTP_USERNAME=
|
||||||
SMTP_PASSWORD=
|
SMTP_PASSWORD=
|
||||||
|
|
||||||
@@ -91,17 +92,24 @@ ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT=true
|
|||||||
|
|
||||||
# App Connections
|
# App Connections
|
||||||
|
|
||||||
# aws assume-role
|
# aws assume-role connection
|
||||||
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID=
|
INF_APP_CONNECTION_AWS_ACCESS_KEY_ID=
|
||||||
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY=
|
INF_APP_CONNECTION_AWS_SECRET_ACCESS_KEY=
|
||||||
|
|
||||||
# github oauth
|
# github oauth connection
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID=
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID=
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET=
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET=
|
||||||
|
|
||||||
#github app
|
#github app connection
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_ID=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
|
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_SLUG=
|
INF_APP_CONNECTION_GITHUB_APP_SLUG=
|
||||||
INF_APP_CONNECTION_GITHUB_APP_ID=
|
INF_APP_CONNECTION_GITHUB_APP_ID=
|
||||||
|
|
||||||
|
#gcp app connection
|
||||||
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL=
|
||||||
|
|
||||||
|
# azure app connection
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_ID=
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_SECRET=
|
58
.github/workflows/deployment-pipeline.yml
vendored
58
.github/workflows/deployment-pipeline.yml
vendored
@@ -5,6 +5,10 @@ permissions:
|
|||||||
id-token: write
|
id-token: write
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "infisical-core-deployment"
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
infisical-tests:
|
infisical-tests:
|
||||||
name: Integration tests
|
name: Integration tests
|
||||||
@@ -113,10 +117,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: twingate/github-action@v1
|
- uses: twingate/github-action@v1
|
||||||
with:
|
with:
|
||||||
# The Twingate Service Key used to connect Twingate to the proper service
|
|
||||||
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
|
|
||||||
#
|
|
||||||
# Required
|
|
||||||
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
|
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -159,6 +159,31 @@ jobs:
|
|||||||
service: infisical-core-platform
|
service: infisical-core-platform
|
||||||
cluster: infisical-core-platform
|
cluster: infisical-core-platform
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
- name: Post slack message
|
||||||
|
uses: slackapi/slack-github-action@v2.0.0
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload: |
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
blocks:
|
||||||
|
- type: "section"
|
||||||
|
text:
|
||||||
|
type: "mrkdwn"
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Application:*\nInfisical Core"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Instance Type:*\nShared Infisical Cloud"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Region:*\nUS"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Git Tag:*\n<https://github.com/Infisical/infisical/commit/${{ steps.commit.outputs.short }}>"
|
||||||
|
|
||||||
|
|
||||||
production-eu:
|
production-eu:
|
||||||
name: EU production deploy
|
name: EU production deploy
|
||||||
@@ -210,3 +235,28 @@ jobs:
|
|||||||
service: infisical-core-platform
|
service: infisical-core-platform
|
||||||
cluster: infisical-core-platform
|
cluster: infisical-core-platform
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
- name: Post slack message
|
||||||
|
uses: slackapi/slack-github-action@v2.0.0
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.SLACK_DEPLOYMENT_WEBHOOK_URL }}
|
||||||
|
webhook-type: incoming-webhook
|
||||||
|
payload: |
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
blocks:
|
||||||
|
- type: "section"
|
||||||
|
text:
|
||||||
|
type: "mrkdwn"
|
||||||
|
text: "*Deployment Status Update*: ${{ job.status }}"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Application:*\nInfisical Core"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Instance Type:*\nShared Infisical Cloud"
|
||||||
|
- type: "section"
|
||||||
|
fields:
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Region:*\nEU"
|
||||||
|
- type: "mrkdwn"
|
||||||
|
text: "*Git Tag:*\n<https://github.com/Infisical/infisical/commit/${{ steps.commit.outputs.short }}>"
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
name: Release Helm Charts
|
name: Release Infisical Core Helm chart
|
||||||
|
|
||||||
on: [workflow_dispatch]
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
@@ -17,6 +17,6 @@ jobs:
|
|||||||
- name: Install Cloudsmith CLI
|
- name: Install Cloudsmith CLI
|
||||||
run: pip install --upgrade cloudsmith-cli
|
run: pip install --upgrade cloudsmith-cli
|
||||||
- name: Build and push helm package to Cloudsmith
|
- name: Build and push helm package to Cloudsmith
|
||||||
run: cd helm-charts && sh upload-to-cloudsmith.sh
|
run: cd helm-charts && sh upload-infisical-core-helm-cloudsmith.sh
|
||||||
env:
|
env:
|
||||||
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
@@ -1,4 +1,4 @@
|
|||||||
name: Release Docker image for K8 operator
|
name: Release image + Helm chart K8s Operator
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -35,3 +35,18 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
infisical/kubernetes-operator:latest
|
infisical/kubernetes-operator:latest
|
||||||
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
infisical/kubernetes-operator:${{ steps.extract_version.outputs.version }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v3
|
||||||
|
with:
|
||||||
|
version: v3.10.0
|
||||||
|
- name: Install python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
- name: Install Cloudsmith CLI
|
||||||
|
run: pip install --upgrade cloudsmith-cli
|
||||||
|
- name: Build and push helm package to Cloudsmith
|
||||||
|
run: cd helm-charts && sh upload-k8s-operator-cloudsmith.sh
|
||||||
|
env:
|
||||||
|
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
|
||||||
|
@@ -7,3 +7,4 @@ docs/self-hosting/configuration/envars.mdx:generic-api-key:106
|
|||||||
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451
|
frontend/src/views/Project/MembersPage/components/MemberListTab/MemberRoleForm/SpecificPrivilegeSection.tsx:generic-api-key:451
|
||||||
docs/mint.json:generic-api-key:651
|
docs/mint.json:generic-api-key:651
|
||||||
backend/src/ee/services/hsm/hsm-service.ts:generic-api-key:134
|
backend/src/ee/services/hsm/hsm-service.ts:generic-api-key:134
|
||||||
|
docs/documentation/platform/audit-log-streams/audit-log-streams.mdx:generic-api-key:104
|
||||||
|
3
Makefile
3
Makefile
@@ -30,3 +30,6 @@ reviewable-api:
|
|||||||
npm run type:check
|
npm run type:check
|
||||||
|
|
||||||
reviewable: reviewable-ui reviewable-api
|
reviewable: reviewable-ui reviewable-api
|
||||||
|
|
||||||
|
up-dev-sso:
|
||||||
|
docker compose -f docker-compose.dev.yml --profile sso up --build
|
||||||
|
11
README.md
11
README.md
@@ -56,7 +56,7 @@ We're on a mission to make security tooling more accessible to everyone, not jus
|
|||||||
- **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments.
|
- **[Infisical Kubernetes Operator](https://infisical.com/docs/documentation/getting-started/kubernetes)**: Deliver secrets to your Kubernetes workloads and automatically reload deployments.
|
||||||
- **[Infisical Agent](https://infisical.com/docs/infisical-agent/overview)**: Inject secrets into applications without modifying any code logic.
|
- **[Infisical Agent](https://infisical.com/docs/infisical-agent/overview)**: Inject secrets into applications without modifying any code logic.
|
||||||
|
|
||||||
### Internal PKI:
|
### Infisical (Internal) PKI:
|
||||||
|
|
||||||
- **[Private Certificate Authority](https://infisical.com/docs/documentation/platform/pki/private-ca)**: Create CA hierarchies, configure [certificate templates](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) for policy enforcement, and start issuing X.509 certificates.
|
- **[Private Certificate Authority](https://infisical.com/docs/documentation/platform/pki/private-ca)**: Create CA hierarchies, configure [certificate templates](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) for policy enforcement, and start issuing X.509 certificates.
|
||||||
- **[Certificate Management](https://infisical.com/docs/documentation/platform/pki/certificates)**: Manage the certificate lifecycle from [issuance](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) to [revocation](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-revoking-certificates) with support for CRL.
|
- **[Certificate Management](https://infisical.com/docs/documentation/platform/pki/certificates)**: Manage the certificate lifecycle from [issuance](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-issuing-certificates) to [revocation](https://infisical.com/docs/documentation/platform/pki/certificates#guide-to-revoking-certificates) with support for CRL.
|
||||||
@@ -64,12 +64,17 @@ We're on a mission to make security tooling more accessible to everyone, not jus
|
|||||||
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
|
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
|
||||||
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
|
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
|
||||||
|
|
||||||
### Key Management (KMS):
|
### Infisical Key Management System (KMS):
|
||||||
|
|
||||||
- **[Cryptographic Keys](https://infisical.com/docs/documentation/platform/kms)**: Centrally manage keys across projects through a user-friendly interface or via the API.
|
- **[Cryptographic Keys](https://infisical.com/docs/documentation/platform/kms)**: Centrally manage keys across projects through a user-friendly interface or via the API.
|
||||||
- **[Encrypt and Decrypt Data](https://infisical.com/docs/documentation/platform/kms#guide-to-encrypting-data)**: Use symmetric keys to encrypt and decrypt data.
|
- **[Encrypt and Decrypt Data](https://infisical.com/docs/documentation/platform/kms#guide-to-encrypting-data)**: Use symmetric keys to encrypt and decrypt data.
|
||||||
|
|
||||||
|
### Infisical SSH
|
||||||
|
|
||||||
|
- **[Signed SSH Certificates](https://infisical.com/docs/documentation/platform/ssh)**: Issue ephemeral SSH credentials for secure, short-lived, and centralized access to infrastructure.
|
||||||
|
|
||||||
### General Platform:
|
### General Platform:
|
||||||
|
|
||||||
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
|
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
|
||||||
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
|
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
|
||||||
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)**: Track every action taken on the platform.
|
- **[Audit logs](https://infisical.com/docs/documentation/platform/audit-logs)**: Track every action taken on the platform.
|
||||||
@@ -120,7 +125,7 @@ Install pre commit hook to scan each commit before you push to your repository
|
|||||||
infisical scan install --pre-commit-hook
|
infisical scan install --pre-commit-hook
|
||||||
```
|
```
|
||||||
|
|
||||||
Lean about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview)
|
Learn about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview)
|
||||||
|
|
||||||
## Open-source vs. paid
|
## Open-source vs. paid
|
||||||
|
|
||||||
|
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@@ -80,6 +80,7 @@ import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-
|
|||||||
import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
||||||
import { TSecretReplicationServiceFactory } from "@app/services/secret-replication/secret-replication-service";
|
import { TSecretReplicationServiceFactory } from "@app/services/secret-replication/secret-replication-service";
|
||||||
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
|
import { TSecretSyncServiceFactory } from "@app/services/secret-sync/secret-sync-service";
|
||||||
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
||||||
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
||||||
import { TSlackServiceFactory } from "@app/services/slack/slack-service";
|
import { TSlackServiceFactory } from "@app/services/slack/slack-service";
|
||||||
@@ -210,6 +211,7 @@ declare module "fastify" {
|
|||||||
projectTemplate: TProjectTemplateServiceFactory;
|
projectTemplate: TProjectTemplateServiceFactory;
|
||||||
totp: TTotpServiceFactory;
|
totp: TTotpServiceFactory;
|
||||||
appConnection: TAppConnectionServiceFactory;
|
appConnection: TAppConnectionServiceFactory;
|
||||||
|
secretSync: TSecretSyncServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
2
backend/src/@types/knex.d.ts
vendored
2
backend/src/@types/knex.d.ts
vendored
@@ -372,6 +372,7 @@ import {
|
|||||||
TExternalGroupOrgRoleMappingsInsert,
|
TExternalGroupOrgRoleMappingsInsert,
|
||||||
TExternalGroupOrgRoleMappingsUpdate
|
TExternalGroupOrgRoleMappingsUpdate
|
||||||
} from "@app/db/schemas/external-group-org-role-mappings";
|
} from "@app/db/schemas/external-group-org-role-mappings";
|
||||||
|
import { TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate } from "@app/db/schemas/secret-syncs";
|
||||||
import {
|
import {
|
||||||
TSecretV2TagJunction,
|
TSecretV2TagJunction,
|
||||||
TSecretV2TagJunctionInsert,
|
TSecretV2TagJunctionInsert,
|
||||||
@@ -900,5 +901,6 @@ declare module "knex/types/tables" {
|
|||||||
TAppConnectionsInsert,
|
TAppConnectionsInsert,
|
||||||
TAppConnectionsUpdate
|
TAppConnectionsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretSync]: KnexOriginal.CompositeTableType<TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,49 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// find any duplicate group names within organizations
|
||||||
|
const duplicates = await knex(TableName.Groups)
|
||||||
|
.select("orgId", "name")
|
||||||
|
.count("* as count")
|
||||||
|
.groupBy("orgId", "name")
|
||||||
|
.having(knex.raw("count(*) > 1"));
|
||||||
|
|
||||||
|
// for each set of duplicates, update all but one with a numbered suffix
|
||||||
|
for await (const duplicate of duplicates) {
|
||||||
|
const groups = await knex(TableName.Groups)
|
||||||
|
.select("id", "name")
|
||||||
|
.where({
|
||||||
|
orgId: duplicate.orgId,
|
||||||
|
name: duplicate.name
|
||||||
|
})
|
||||||
|
.orderBy("createdAt", "asc"); // keep original name for oldest group
|
||||||
|
|
||||||
|
// skip the first (oldest) group, rename others with numbered suffix
|
||||||
|
for (let i = 1; i < groups.length; i += 1) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await knex(TableName.Groups)
|
||||||
|
.where("id", groups[i].id)
|
||||||
|
.update({
|
||||||
|
name: `${groups[i].name} (${i})`,
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore TS doesn't know about Knex's timestamp types
|
||||||
|
updatedAt: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add the unique constraint
|
||||||
|
await knex.schema.alterTable(TableName.Groups, (t) => {
|
||||||
|
t.unique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
// Remove the unique constraint
|
||||||
|
await knex.schema.alterTable(TableName.Groups, (t) => {
|
||||||
|
t.dropUnique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasEnforceCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "enforceCapitalization");
|
||||||
|
const hasAutoCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "autoCapitalization");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||||
|
if (!hasEnforceCapitalizationCol) {
|
||||||
|
t.boolean("enforceCapitalization").defaultTo(false).notNullable();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAutoCapitalizationCol) {
|
||||||
|
t.boolean("autoCapitalization").defaultTo(false).alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasEnforceCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "enforceCapitalization");
|
||||||
|
const hasAutoCapitalizationCol = await knex.schema.hasColumn(TableName.Project, "autoCapitalization");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||||
|
if (hasEnforceCapitalizationCol) {
|
||||||
|
t.dropColumn("enforceCapitalization");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasAutoCapitalizationCol) {
|
||||||
|
t.boolean("autoCapitalization").defaultTo(true).alter();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
50
backend/src/db/migrations/20250122055102_secret-sync.ts
Normal file
50
backend/src/db/migrations/20250122055102_secret-sync.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "@app/db/utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SecretSync))) {
|
||||||
|
await knex.schema.createTable(TableName.SecretSync, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("name", 32).notNullable();
|
||||||
|
t.string("description");
|
||||||
|
t.string("destination").notNullable();
|
||||||
|
t.boolean("isAutoSyncEnabled").notNullable().defaultTo(true);
|
||||||
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
|
t.jsonb("destinationConfig").notNullable();
|
||||||
|
t.jsonb("syncOptions").notNullable();
|
||||||
|
// we're including projectId in addition to folder ID because we allow folderId to be null (if the folder
|
||||||
|
// is deleted), to preserve sync configuration
|
||||||
|
t.string("projectId").notNullable();
|
||||||
|
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
t.uuid("folderId");
|
||||||
|
t.foreign("folderId").references("id").inTable(TableName.SecretFolder).onDelete("SET NULL");
|
||||||
|
t.uuid("connectionId").notNullable();
|
||||||
|
t.foreign("connectionId").references("id").inTable(TableName.AppConnection);
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
// sync secrets to destination
|
||||||
|
t.string("syncStatus");
|
||||||
|
t.string("lastSyncJobId");
|
||||||
|
t.string("lastSyncMessage");
|
||||||
|
t.datetime("lastSyncedAt");
|
||||||
|
// import secrets from destination
|
||||||
|
t.string("importStatus");
|
||||||
|
t.string("lastImportJobId");
|
||||||
|
t.string("lastImportMessage");
|
||||||
|
t.datetime("lastImportedAt");
|
||||||
|
// remove secrets from destination
|
||||||
|
t.string("removeStatus");
|
||||||
|
t.string("lastRemoveJobId");
|
||||||
|
t.string("lastRemoveMessage");
|
||||||
|
t.datetime("lastRemovedAt");
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SecretSync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretSync);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SecretSync);
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasManageGroupMembershipsCol = await knex.schema.hasColumn(TableName.OidcConfig, "manageGroupMemberships");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
|
||||||
|
if (!hasManageGroupMembershipsCol) {
|
||||||
|
tb.boolean("manageGroupMemberships").notNullable().defaultTo(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasManageGroupMembershipsCol = await knex.schema.hasColumn(TableName.OidcConfig, "manageGroupMemberships");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
|
||||||
|
if (hasManageGroupMembershipsCol) {
|
||||||
|
t.dropColumn("manageGroupMemberships");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||||
|
t.unique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
t.unique(["projectId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.AppConnection, (t) => {
|
||||||
|
t.dropUnique(["orgId", "name"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
t.dropUnique(["projectId", "name"]);
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasTable = await knex.schema.hasTable(TableName.IdentityGcpAuth);
|
||||||
|
const hasAllowedProjectsColumn = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedProjects");
|
||||||
|
const hasAllowedServiceAccountsColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.IdentityGcpAuth,
|
||||||
|
"allowedServiceAccounts"
|
||||||
|
);
|
||||||
|
const hasAllowedZones = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedZones");
|
||||||
|
if (hasTable) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
|
||||||
|
if (hasAllowedProjectsColumn) t.string("allowedProjects", 2500).alter();
|
||||||
|
if (hasAllowedServiceAccountsColumn) t.string("allowedServiceAccounts", 5000).alter();
|
||||||
|
if (hasAllowedZones) t.string("allowedZones", 2500).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasTable = await knex.schema.hasTable(TableName.IdentityGcpAuth);
|
||||||
|
const hasAllowedProjectsColumn = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedProjects");
|
||||||
|
const hasAllowedServiceAccountsColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.IdentityGcpAuth,
|
||||||
|
"allowedServiceAccounts"
|
||||||
|
);
|
||||||
|
const hasAllowedZones = await knex.schema.hasColumn(TableName.IdentityGcpAuth, "allowedZones");
|
||||||
|
if (hasTable) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityGcpAuth, (t) => {
|
||||||
|
if (hasAllowedProjectsColumn) t.string("allowedProjects").alter();
|
||||||
|
if (hasAllowedServiceAccountsColumn) t.string("allowedServiceAccounts").alter();
|
||||||
|
if (hasAllowedZones) t.string("allowedZones").alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlugCol = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (hasSlugCol) {
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (t) => {
|
||||||
|
t.dropColumn("slug");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlugCol = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (!hasSlugCol) {
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (t) => {
|
||||||
|
t.string("slug", 32);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,31 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSync)) {
|
||||||
|
const hasLastSyncMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastSyncMessage");
|
||||||
|
const hasLastImportMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastImportMessage");
|
||||||
|
const hasLastRemoveMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastRemoveMessage");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
if (hasLastSyncMessage) t.string("lastSyncMessage", 1024).alter();
|
||||||
|
if (hasLastImportMessage) t.string("lastImportMessage", 1024).alter();
|
||||||
|
if (hasLastRemoveMessage) t.string("lastRemoveMessage", 1024).alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSync)) {
|
||||||
|
const hasLastSyncMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastSyncMessage");
|
||||||
|
const hasLastImportMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastImportMessage");
|
||||||
|
const hasLastRemoveMessage = await knex.schema.hasColumn(TableName.SecretSync, "lastRemoveMessage");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SecretSync, (t) => {
|
||||||
|
if (hasLastSyncMessage) t.string("lastSyncMessage").alter();
|
||||||
|
if (hasLastImportMessage) t.string("lastImportMessage").alter();
|
||||||
|
if (hasLastRemoveMessage) t.string("lastRemoveMessage").alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -17,9 +17,9 @@ export const IdentityGcpAuthsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
allowedServiceAccounts: z.string(),
|
allowedServiceAccounts: z.string().nullable().optional(),
|
||||||
allowedProjects: z.string(),
|
allowedProjects: z.string().nullable().optional(),
|
||||||
allowedZones: z.string()
|
allowedZones: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
|
export type TIdentityGcpAuths = z.infer<typeof IdentityGcpAuthsSchema>;
|
||||||
|
@@ -16,8 +16,7 @@ export const KmsKeysSchema = z.object({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
projectId: z.string().nullable().optional(),
|
projectId: z.string().nullable().optional()
|
||||||
slug: z.string().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||||
|
@@ -131,7 +131,8 @@ export enum TableName {
|
|||||||
WorkflowIntegrations = "workflow_integrations",
|
WorkflowIntegrations = "workflow_integrations",
|
||||||
SlackIntegrations = "slack_integrations",
|
SlackIntegrations = "slack_integrations",
|
||||||
ProjectSlackConfigs = "project_slack_configs",
|
ProjectSlackConfigs = "project_slack_configs",
|
||||||
AppConnection = "app_connections"
|
AppConnection = "app_connections",
|
||||||
|
SecretSync = "secret_syncs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
@@ -215,3 +216,12 @@ export enum ProjectType {
|
|||||||
KMS = "kms",
|
KMS = "kms",
|
||||||
SSH = "ssh"
|
SSH = "ssh"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ActionProjectType {
|
||||||
|
SecretManager = ProjectType.SecretManager,
|
||||||
|
CertificateManager = ProjectType.CertificateManager,
|
||||||
|
KMS = ProjectType.KMS,
|
||||||
|
SSH = ProjectType.SSH,
|
||||||
|
// project operations that happen on all types
|
||||||
|
Any = "any"
|
||||||
|
}
|
||||||
|
@@ -27,7 +27,8 @@ export const OidcConfigsSchema = z.object({
|
|||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
lastUsed: z.date().nullable().optional()
|
lastUsed: z.date().nullable().optional(),
|
||||||
|
manageGroupMemberships: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
||||||
|
@@ -13,7 +13,7 @@ export const ProjectsSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
autoCapitalization: z.boolean().default(true).nullable().optional(),
|
autoCapitalization: z.boolean().default(false).nullable().optional(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
@@ -25,7 +25,8 @@ export const ProjectsSchema = z.object({
|
|||||||
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
kmsSecretManagerKeyId: z.string().uuid().nullable().optional(),
|
||||||
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
description: z.string().nullable().optional(),
|
description: z.string().nullable().optional(),
|
||||||
type: z.string()
|
type: z.string(),
|
||||||
|
enforceCapitalization: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||||
|
40
backend/src/db/schemas/secret-syncs.ts
Normal file
40
backend/src/db/schemas/secret-syncs.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SecretSyncsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
destination: z.string(),
|
||||||
|
isAutoSyncEnabled: z.boolean().default(true),
|
||||||
|
version: z.number().default(1),
|
||||||
|
destinationConfig: z.unknown(),
|
||||||
|
syncOptions: z.unknown(),
|
||||||
|
projectId: z.string(),
|
||||||
|
folderId: z.string().uuid().nullable().optional(),
|
||||||
|
connectionId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
syncStatus: z.string().nullable().optional(),
|
||||||
|
lastSyncJobId: z.string().nullable().optional(),
|
||||||
|
lastSyncMessage: z.string().nullable().optional(),
|
||||||
|
lastSyncedAt: z.date().nullable().optional(),
|
||||||
|
importStatus: z.string().nullable().optional(),
|
||||||
|
lastImportJobId: z.string().nullable().optional(),
|
||||||
|
lastImportMessage: z.string().nullable().optional(),
|
||||||
|
lastImportedAt: z.date().nullable().optional(),
|
||||||
|
removeStatus: z.string().nullable().optional(),
|
||||||
|
lastRemoveJobId: z.string().nullable().optional(),
|
||||||
|
lastRemoveMessage: z.string().nullable().optional(),
|
||||||
|
lastRemovedAt: z.date().nullable().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSecretSyncs = z.infer<typeof SecretSyncsSchema>;
|
||||||
|
export type TSecretSyncsInsert = Omit<z.input<typeof SecretSyncsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSecretSyncsUpdate = Partial<Omit<z.input<typeof SecretSyncsSchema>, TImmutableDBKeys>>;
|
@@ -153,7 +153,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
discoveryURL: true,
|
discoveryURL: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
allowedEmailDomains: true
|
allowedEmailDomains: true,
|
||||||
|
manageGroupMemberships: true
|
||||||
}).extend({
|
}).extend({
|
||||||
clientId: z.string(),
|
clientId: z.string(),
|
||||||
clientSecret: z.string()
|
clientSecret: z.string()
|
||||||
@@ -207,7 +208,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
userinfoEndpoint: z.string().trim(),
|
userinfoEndpoint: z.string().trim(),
|
||||||
clientId: z.string().trim(),
|
clientId: z.string().trim(),
|
||||||
clientSecret: z.string().trim(),
|
clientSecret: z.string().trim(),
|
||||||
isActive: z.boolean()
|
isActive: z.boolean(),
|
||||||
|
manageGroupMemberships: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.merge(z.object({ orgSlug: z.string() })),
|
.merge(z.object({ orgSlug: z.string() })),
|
||||||
@@ -223,7 +225,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
userinfoEndpoint: true,
|
userinfoEndpoint: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
allowedEmailDomains: true,
|
allowedEmailDomains: true,
|
||||||
isActive: true
|
isActive: true,
|
||||||
|
manageGroupMemberships: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -272,7 +275,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientId: z.string().trim(),
|
clientId: z.string().trim(),
|
||||||
clientSecret: z.string().trim(),
|
clientSecret: z.string().trim(),
|
||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
orgSlug: z.string().trim()
|
orgSlug: z.string().trim(),
|
||||||
|
manageGroupMemberships: z.boolean().optional().default(false)
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
if (data.configurationType === OIDCConfigurationType.CUSTOM) {
|
||||||
@@ -334,7 +338,8 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
userinfoEndpoint: true,
|
userinfoEndpoint: true,
|
||||||
orgId: true,
|
orgId: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
allowedEmailDomains: true
|
allowedEmailDomains: true,
|
||||||
|
manageGroupMemberships: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -350,4 +355,25 @@ export const registerOidcRouter = async (server: FastifyZodProvider) => {
|
|||||||
return oidc;
|
return oidc;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/manage-group-memberships",
|
||||||
|
schema: {
|
||||||
|
querystring: z.object({
|
||||||
|
orgId: z.string().trim().min(1, "Org ID is required")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
isEnabled: z.boolean()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const isEnabled = await server.services.oidc.isOidcManageGroupMembershipsEnabled(req.query.orgId, req.permission);
|
||||||
|
|
||||||
|
return { isEnabled };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -24,6 +24,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
),
|
),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().nullish(),
|
description: z.string().trim().nullish(),
|
||||||
|
// TODO(scott): once UI refactored permissions: OrgPermissionSchema.array()
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -96,6 +97,7 @@ export const registerOrgRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
name: z.string().trim().optional(),
|
name: z.string().trim().optional(),
|
||||||
description: z.string().trim().nullish(),
|
description: z.string().trim().nullish(),
|
||||||
|
// TODO(scott): once UI refactored permissions: OrgPermissionSchema.array().optional()
|
||||||
permissions: z.any().array().optional()
|
permissions: z.any().array().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
import { GitAppOrgSchema, SecretScanningGitRisksSchema } from "@app/db/schemas";
|
||||||
import { SecretScanningRiskStatus } from "@app/ee/services/secret-scanning/secret-scanning-types";
|
import {
|
||||||
|
SecretScanningResolvedStatus,
|
||||||
|
SecretScanningRiskStatus
|
||||||
|
} from "@app/ee/services/secret-scanning/secret-scanning-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -97,6 +101,45 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/organization/:organizationId/risks/export",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
querystring: z.object({
|
||||||
|
repositoryNames: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.nullable()
|
||||||
|
.transform((val) => (val ? val.split(",") : undefined)),
|
||||||
|
resolvedStatus: z.nativeEnum(SecretScanningResolvedStatus).optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
risks: SecretScanningGitRisksSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const risks = await server.services.secretScanning.getAllRisksByOrg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
orgId: req.params.organizationId,
|
||||||
|
filter: {
|
||||||
|
repositoryNames: req.query.repositoryNames,
|
||||||
|
resolvedStatus: req.query.resolvedStatus
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { risks };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/organization/:organizationId/risks",
|
url: "/organization/:organizationId/risks",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -105,20 +148,46 @@ export const registerSecretScanningRouter = async (server: FastifyZodProvider) =
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
|
||||||
|
querystring: z.object({
|
||||||
|
offset: z.coerce.number().min(0).default(0),
|
||||||
|
limit: z.coerce.number().min(1).max(20000).default(100),
|
||||||
|
orderBy: z.enum(["createdAt", "name"]).default("createdAt"),
|
||||||
|
orderDirection: z.nativeEnum(OrderByDirection).default(OrderByDirection.DESC),
|
||||||
|
repositoryNames: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.nullable()
|
||||||
|
.transform((val) => (val ? val.split(",") : undefined)),
|
||||||
|
resolvedStatus: z.nativeEnum(SecretScanningResolvedStatus).optional()
|
||||||
|
}),
|
||||||
|
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ risks: SecretScanningGitRisksSchema.array() })
|
200: z.object({
|
||||||
|
risks: SecretScanningGitRisksSchema.array(),
|
||||||
|
totalCount: z.number(),
|
||||||
|
repos: z.array(z.string())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { risks } = await server.services.secretScanning.getRisksByOrg({
|
const { risks, totalCount, repos } = await server.services.secretScanning.getRisksByOrg({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.organizationId
|
orgId: req.params.organizationId,
|
||||||
|
filter: {
|
||||||
|
limit: req.query.limit,
|
||||||
|
offset: req.query.offset,
|
||||||
|
orderBy: req.query.orderBy,
|
||||||
|
orderDirection: req.query.orderDirection,
|
||||||
|
repositoryNames: req.query.repositoryNames,
|
||||||
|
resolvedStatus: req.query.resolvedStatus
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { risks };
|
return { risks, totalCount, repos };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -87,14 +87,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
@@ -193,7 +193,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
// Anyone in the project should be able to get the policies.
|
// Anyone in the project should be able to get the policies.
|
||||||
await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: project.id,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
const accessApprovalPolicies = await accessApprovalPolicyDAL.find({ projectId: project.id, deletedAt: null });
|
||||||
return accessApprovalPolicies;
|
return accessApprovalPolicies;
|
||||||
@@ -237,14 +244,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
if (!accessApprovalPolicy) {
|
if (!accessApprovalPolicy) {
|
||||||
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||||
}
|
}
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
accessApprovalPolicy.projectId,
|
projectId: accessApprovalPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
@@ -321,14 +328,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
||||||
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
policy.projectId,
|
projectId: policy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
@@ -372,13 +379,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
@@ -411,13 +419,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
policy.projectId,
|
projectId: policy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@@ -100,13 +100,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
// Anyone can create an access approval request.
|
// Anyone can create an access approval request.
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
@@ -273,13 +274,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
@@ -318,13 +320,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
accessApprovalRequest.projectId,
|
projectId: accessApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
@@ -422,13 +425,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
project.id,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
}
|
}
|
||||||
|
@@ -93,7 +93,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
throw new Error(`Failed to connect with the source ${(err as Error)?.message}`);
|
throw new BadRequestError({ message: `Failed to connect with upstream source: ${(err as Error)?.message}` });
|
||||||
});
|
});
|
||||||
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
|
const encryptedHeaders = headers ? infisicalSymmetricEncypt(JSON.stringify(headers)) : undefined;
|
||||||
const logStream = await auditLogStreamDAL.create({
|
const logStream = await auditLogStreamDAL.create({
|
||||||
|
@@ -39,11 +39,13 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
offset = 0,
|
offset = 0,
|
||||||
actorId,
|
actorId,
|
||||||
actorType,
|
actorType,
|
||||||
|
secretPath,
|
||||||
eventType,
|
eventType,
|
||||||
eventMetadata
|
eventMetadata
|
||||||
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
}: Omit<TFindQuery, "actor" | "eventType"> & {
|
||||||
actorId?: string;
|
actorId?: string;
|
||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
|
secretPath?: string;
|
||||||
eventType?: EventType[];
|
eventType?: EventType[];
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
},
|
},
|
||||||
@@ -88,6 +90,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (projectId && secretPath) {
|
||||||
|
void sqlQuery.whereRaw(`"eventMetadata" @> jsonb_build_object('secretPath', ?::text)`, [secretPath]);
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by actor type
|
// Filter by actor type
|
||||||
if (actorType) {
|
if (actorType) {
|
||||||
void sqlQuery.where("actor", actorType);
|
void sqlQuery.where("actor", actorType);
|
||||||
@@ -100,10 +106,10 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
// Filter by date range
|
// Filter by date range
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate);
|
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" >= ?::timestamptz`, [startDate]);
|
||||||
}
|
}
|
||||||
if (endDate) {
|
if (endDate) {
|
||||||
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
|
void sqlQuery.whereRaw(`"${TableName.AuditLog}"."createdAt" <= ?::timestamptz`, [endDate]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -26,13 +27,14 @@ export const auditLogServiceFactory = ({
|
|||||||
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
const listAuditLogs = async ({ actorAuthMethod, actorId, actorOrgId, actor, filter }: TListProjectAuditLogDTO) => {
|
||||||
// Filter logs for specific project
|
// Filter logs for specific project
|
||||||
if (filter.projectId) {
|
if (filter.projectId) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
filter.projectId,
|
projectId: filter.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
} else {
|
} else {
|
||||||
// Organization-wide logs
|
// Organization-wide logs
|
||||||
@@ -44,10 +46,6 @@ export const auditLogServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* NOTE (dangtony98): Update this to organization-level audit log permission check once audit logs are moved
|
|
||||||
* to the organization level ✅
|
|
||||||
*/
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +60,7 @@ export const auditLogServiceFactory = ({
|
|||||||
actorId: filter.auditLogActorId,
|
actorId: filter.auditLogActorId,
|
||||||
actorType: filter.actorType,
|
actorType: filter.actorType,
|
||||||
eventMetadata: filter.eventMetadata,
|
eventMetadata: filter.eventMetadata,
|
||||||
|
secretPath: filter.secretPath,
|
||||||
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
...(filter.projectId ? { projectId: filter.projectId } : { orgId: actorOrgId })
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,7 +78,8 @@ export const auditLogServiceFactory = ({
|
|||||||
}
|
}
|
||||||
// add all cases in which project id or org id cannot be added
|
// add all cases in which project id or org id cannot be added
|
||||||
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
if (data.event.type !== EventType.LOGIN_IDENTITY_UNIVERSAL_AUTH) {
|
||||||
if (!data.projectId && !data.orgId) throw new BadRequestError({ message: "Must either project id or org id" });
|
if (!data.projectId && !data.orgId)
|
||||||
|
throw new BadRequestError({ message: "Must specify either project id or org id" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return auditLogQueue.pushToLog(data);
|
return auditLogQueue.pushToLog(data);
|
||||||
|
@@ -13,6 +13,13 @@ import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
|||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
import { TIdentityTrustedIp } from "@app/services/identity/identity-types";
|
||||||
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
||||||
|
import { SecretSync, SecretSyncImportBehavior } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import {
|
||||||
|
TCreateSecretSyncDTO,
|
||||||
|
TDeleteSecretSyncDTO,
|
||||||
|
TSecretSyncRaw,
|
||||||
|
TUpdateSecretSyncDTO
|
||||||
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
export type TListProjectAuditLogDTO = {
|
export type TListProjectAuditLogDTO = {
|
||||||
filter: {
|
filter: {
|
||||||
@@ -25,13 +32,14 @@ export type TListProjectAuditLogDTO = {
|
|||||||
projectId?: string;
|
projectId?: string;
|
||||||
auditLogActorId?: string;
|
auditLogActorId?: string;
|
||||||
actorType?: ActorType;
|
actorType?: ActorType;
|
||||||
|
secretPath?: string;
|
||||||
eventMetadata?: Record<string, string>;
|
eventMetadata?: Record<string, string>;
|
||||||
};
|
};
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateAuditLogDTO = {
|
export type TCreateAuditLogDTO = {
|
||||||
event: Event;
|
event: Event;
|
||||||
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor;
|
actor: UserActor | IdentityActor | ServiceActor | ScimClientActor | PlatformActor | UnknownUserActor;
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
} & BaseAuthData;
|
} & BaseAuthData;
|
||||||
@@ -215,6 +223,7 @@ export enum EventType {
|
|||||||
UPDATE_CMEK = "update-cmek",
|
UPDATE_CMEK = "update-cmek",
|
||||||
DELETE_CMEK = "delete-cmek",
|
DELETE_CMEK = "delete-cmek",
|
||||||
GET_CMEKS = "get-cmeks",
|
GET_CMEKS = "get-cmeks",
|
||||||
|
GET_CMEK = "get-cmek",
|
||||||
CMEK_ENCRYPT = "cmek-encrypt",
|
CMEK_ENCRYPT = "cmek-encrypt",
|
||||||
CMEK_DECRYPT = "cmek-decrypt",
|
CMEK_DECRYPT = "cmek-decrypt",
|
||||||
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
|
UPDATE_EXTERNAL_GROUP_ORG_ROLE_MAPPINGS = "update-external-group-org-role-mapping",
|
||||||
@@ -226,10 +235,24 @@ export enum EventType {
|
|||||||
DELETE_PROJECT_TEMPLATE = "delete-project-template",
|
DELETE_PROJECT_TEMPLATE = "delete-project-template",
|
||||||
APPLY_PROJECT_TEMPLATE = "apply-project-template",
|
APPLY_PROJECT_TEMPLATE = "apply-project-template",
|
||||||
GET_APP_CONNECTIONS = "get-app-connections",
|
GET_APP_CONNECTIONS = "get-app-connections",
|
||||||
|
GET_AVAILABLE_APP_CONNECTIONS_DETAILS = "get-available-app-connections-details",
|
||||||
GET_APP_CONNECTION = "get-app-connection",
|
GET_APP_CONNECTION = "get-app-connection",
|
||||||
CREATE_APP_CONNECTION = "create-app-connection",
|
CREATE_APP_CONNECTION = "create-app-connection",
|
||||||
UPDATE_APP_CONNECTION = "update-app-connection",
|
UPDATE_APP_CONNECTION = "update-app-connection",
|
||||||
DELETE_APP_CONNECTION = "delete-app-connection"
|
DELETE_APP_CONNECTION = "delete-app-connection",
|
||||||
|
CREATE_SHARED_SECRET = "create-shared-secret",
|
||||||
|
DELETE_SHARED_SECRET = "delete-shared-secret",
|
||||||
|
READ_SHARED_SECRET = "read-shared-secret",
|
||||||
|
GET_SECRET_SYNCS = "get-secret-syncs",
|
||||||
|
GET_SECRET_SYNC = "get-secret-sync",
|
||||||
|
CREATE_SECRET_SYNC = "create-secret-sync",
|
||||||
|
UPDATE_SECRET_SYNC = "update-secret-sync",
|
||||||
|
DELETE_SECRET_SYNC = "delete-secret-sync",
|
||||||
|
SECRET_SYNC_SYNC_SECRETS = "secret-sync-sync-secrets",
|
||||||
|
SECRET_SYNC_IMPORT_SECRETS = "secret-sync-import-secrets",
|
||||||
|
SECRET_SYNC_REMOVE_SECRETS = "secret-sync-remove-secrets",
|
||||||
|
OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER = "oidc-group-membership-mapping-assign-user",
|
||||||
|
OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER = "oidc-group-membership-mapping-remove-user"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -252,6 +275,8 @@ interface ScimClientActorMetadata {}
|
|||||||
|
|
||||||
interface PlatformActorMetadata {}
|
interface PlatformActorMetadata {}
|
||||||
|
|
||||||
|
interface UnknownUserActorMetadata {}
|
||||||
|
|
||||||
export interface UserActor {
|
export interface UserActor {
|
||||||
type: ActorType.USER;
|
type: ActorType.USER;
|
||||||
metadata: UserActorMetadata;
|
metadata: UserActorMetadata;
|
||||||
@@ -267,6 +292,11 @@ export interface PlatformActor {
|
|||||||
metadata: PlatformActorMetadata;
|
metadata: PlatformActorMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UnknownUserActor {
|
||||||
|
type: ActorType.UNKNOWN_USER;
|
||||||
|
metadata: UnknownUserActorMetadata;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IdentityActor {
|
export interface IdentityActor {
|
||||||
type: ActorType.IDENTITY;
|
type: ActorType.IDENTITY;
|
||||||
metadata: IdentityActorMetadata;
|
metadata: IdentityActorMetadata;
|
||||||
@@ -288,6 +318,8 @@ interface GetSecretsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TSecretMetadata = { key: string; value: string }[];
|
||||||
|
|
||||||
interface GetSecretEvent {
|
interface GetSecretEvent {
|
||||||
type: EventType.GET_SECRET;
|
type: EventType.GET_SECRET;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -296,6 +328,7 @@ interface GetSecretEvent {
|
|||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +340,7 @@ interface CreateSecretEvent {
|
|||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +349,12 @@ interface CreateSecretBatchEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
|
secrets: Array<{
|
||||||
|
secretId: string;
|
||||||
|
secretKey: string;
|
||||||
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,6 +366,7 @@ interface UpdateSecretEvent {
|
|||||||
secretId: string;
|
secretId: string;
|
||||||
secretKey: string;
|
secretKey: string;
|
||||||
secretVersion: number;
|
secretVersion: number;
|
||||||
|
secretMetadata?: TSecretMetadata;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +375,7 @@ interface UpdateSecretBatchEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number }>;
|
secrets: Array<{ secretId: string; secretKey: string; secretVersion: number; secretMetadata?: TSecretMetadata }>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -733,9 +773,9 @@ interface AddIdentityGcpAuthEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
type: string;
|
type: string;
|
||||||
allowedServiceAccounts: string;
|
allowedServiceAccounts?: string | null;
|
||||||
allowedProjects: string;
|
allowedProjects?: string | null;
|
||||||
allowedZones: string;
|
allowedZones?: string | null;
|
||||||
accessTokenTTL: number;
|
accessTokenTTL: number;
|
||||||
accessTokenMaxTTL: number;
|
accessTokenMaxTTL: number;
|
||||||
accessTokenNumUsesLimit: number;
|
accessTokenNumUsesLimit: number;
|
||||||
@@ -755,9 +795,9 @@ interface UpdateIdentityGcpAuthEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
allowedServiceAccounts?: string;
|
allowedServiceAccounts?: string | null;
|
||||||
allowedProjects?: string;
|
allowedProjects?: string | null;
|
||||||
allowedZones?: string;
|
allowedZones?: string | null;
|
||||||
accessTokenTTL?: number;
|
accessTokenTTL?: number;
|
||||||
accessTokenMaxTTL?: number;
|
accessTokenMaxTTL?: number;
|
||||||
accessTokenNumUsesLimit?: number;
|
accessTokenNumUsesLimit?: number;
|
||||||
@@ -1808,6 +1848,13 @@ interface GetCmeksEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetCmekEvent {
|
||||||
|
type: EventType.GET_CMEK;
|
||||||
|
metadata: {
|
||||||
|
keyId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CmekEncryptEvent {
|
interface CmekEncryptEvent {
|
||||||
type: EventType.CMEK_ENCRYPT;
|
type: EventType.CMEK_ENCRYPT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1883,6 +1930,15 @@ interface GetAppConnectionsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetAvailableAppConnectionsDetailsEvent {
|
||||||
|
type: EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS;
|
||||||
|
metadata: {
|
||||||
|
app?: AppConnection;
|
||||||
|
count: number;
|
||||||
|
connectionIds: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface GetAppConnectionEvent {
|
interface GetAppConnectionEvent {
|
||||||
type: EventType.GET_APP_CONNECTION;
|
type: EventType.GET_APP_CONNECTION;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1907,6 +1963,127 @@ interface DeleteAppConnectionEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateSharedSecretEvent {
|
||||||
|
type: EventType.CREATE_SHARED_SECRET;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
accessType: string;
|
||||||
|
name?: string;
|
||||||
|
expiresAfterViews?: number;
|
||||||
|
usingPassword: boolean;
|
||||||
|
expiresAt: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSharedSecretEvent {
|
||||||
|
type: EventType.DELETE_SHARED_SECRET;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReadSharedSecretEvent {
|
||||||
|
type: EventType.READ_SHARED_SECRET;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
accessType: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSecretSyncsEvent {
|
||||||
|
type: EventType.GET_SECRET_SYNCS;
|
||||||
|
metadata: {
|
||||||
|
destination?: SecretSync;
|
||||||
|
count: number;
|
||||||
|
syncIds: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSecretSyncEvent {
|
||||||
|
type: EventType.GET_SECRET_SYNC;
|
||||||
|
metadata: {
|
||||||
|
destination: SecretSync;
|
||||||
|
syncId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CreateSecretSyncEvent {
|
||||||
|
type: EventType.CREATE_SECRET_SYNC;
|
||||||
|
metadata: Omit<TCreateSecretSyncDTO, "projectId"> & { syncId: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSecretSyncEvent {
|
||||||
|
type: EventType.UPDATE_SECRET_SYNC;
|
||||||
|
metadata: TUpdateSecretSyncDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSecretSyncEvent {
|
||||||
|
type: EventType.DELETE_SECRET_SYNC;
|
||||||
|
metadata: TDeleteSecretSyncDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretSyncSyncSecretsEvent {
|
||||||
|
type: EventType.SECRET_SYNC_SYNC_SECRETS;
|
||||||
|
metadata: Pick<
|
||||||
|
TSecretSyncRaw,
|
||||||
|
"syncOptions" | "destinationConfig" | "destination" | "syncStatus" | "connectionId" | "folderId"
|
||||||
|
> & {
|
||||||
|
syncId: string;
|
||||||
|
syncMessage: string | null;
|
||||||
|
jobId: string;
|
||||||
|
jobRanAt: Date;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretSyncImportSecretsEvent {
|
||||||
|
type: EventType.SECRET_SYNC_IMPORT_SECRETS;
|
||||||
|
metadata: Pick<
|
||||||
|
TSecretSyncRaw,
|
||||||
|
"syncOptions" | "destinationConfig" | "destination" | "importStatus" | "connectionId" | "folderId"
|
||||||
|
> & {
|
||||||
|
syncId: string;
|
||||||
|
importMessage: string | null;
|
||||||
|
jobId: string;
|
||||||
|
jobRanAt: Date;
|
||||||
|
importBehavior: SecretSyncImportBehavior;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SecretSyncRemoveSecretsEvent {
|
||||||
|
type: EventType.SECRET_SYNC_REMOVE_SECRETS;
|
||||||
|
metadata: Pick<
|
||||||
|
TSecretSyncRaw,
|
||||||
|
"syncOptions" | "destinationConfig" | "destination" | "removeStatus" | "connectionId" | "folderId"
|
||||||
|
> & {
|
||||||
|
syncId: string;
|
||||||
|
removeMessage: string | null;
|
||||||
|
jobId: string;
|
||||||
|
jobRanAt: Date;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OidcGroupMembershipMappingAssignUserEvent {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER;
|
||||||
|
metadata: {
|
||||||
|
assignedToGroups: { id: string; name: string }[];
|
||||||
|
userId: string;
|
||||||
|
userEmail: string;
|
||||||
|
userGroupsClaim: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OidcGroupMembershipMappingRemoveUserEvent {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER;
|
||||||
|
metadata: {
|
||||||
|
removedFromGroups: { id: string; name: string }[];
|
||||||
|
userId: string;
|
||||||
|
userEmail: string;
|
||||||
|
userGroupsClaim: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -2068,6 +2245,7 @@ export type Event =
|
|||||||
| CreateCmekEvent
|
| CreateCmekEvent
|
||||||
| UpdateCmekEvent
|
| UpdateCmekEvent
|
||||||
| DeleteCmekEvent
|
| DeleteCmekEvent
|
||||||
|
| GetCmekEvent
|
||||||
| GetCmeksEvent
|
| GetCmeksEvent
|
||||||
| CmekEncryptEvent
|
| CmekEncryptEvent
|
||||||
| CmekDecryptEvent
|
| CmekDecryptEvent
|
||||||
@@ -2080,7 +2258,21 @@ export type Event =
|
|||||||
| DeleteProjectTemplateEvent
|
| DeleteProjectTemplateEvent
|
||||||
| ApplyProjectTemplateEvent
|
| ApplyProjectTemplateEvent
|
||||||
| GetAppConnectionsEvent
|
| GetAppConnectionsEvent
|
||||||
|
| GetAvailableAppConnectionsDetailsEvent
|
||||||
| GetAppConnectionEvent
|
| GetAppConnectionEvent
|
||||||
| CreateAppConnectionEvent
|
| CreateAppConnectionEvent
|
||||||
| UpdateAppConnectionEvent
|
| UpdateAppConnectionEvent
|
||||||
| DeleteAppConnectionEvent;
|
| DeleteAppConnectionEvent
|
||||||
|
| CreateSharedSecretEvent
|
||||||
|
| DeleteSharedSecretEvent
|
||||||
|
| ReadSharedSecretEvent
|
||||||
|
| GetSecretSyncsEvent
|
||||||
|
| GetSecretSyncEvent
|
||||||
|
| CreateSecretSyncEvent
|
||||||
|
| UpdateSecretSyncEvent
|
||||||
|
| DeleteSecretSyncEvent
|
||||||
|
| SecretSyncSyncSecretsEvent
|
||||||
|
| SecretSyncImportSecretsEvent
|
||||||
|
| SecretSyncRemoveSecretsEvent
|
||||||
|
| OidcGroupMembershipMappingAssignUserEvent
|
||||||
|
| OidcGroupMembershipMappingRemoveUserEvent;
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
@@ -66,13 +67,14 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
@@ -67,14 +67,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -147,14 +147,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -227,14 +227,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -297,13 +297,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -339,13 +340,14 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.Lease,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
import { ActionProjectType, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import {
|
||||||
@@ -73,14 +73,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -145,14 +145,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -229,14 +229,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -290,13 +290,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -340,13 +341,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
isInternal
|
isInternal
|
||||||
}: TListDynamicSecretsMultiEnvDTO) => {
|
}: TListDynamicSecretsMultiEnvDTO) => {
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
// verify user has access to each env in request
|
// verify user has access to each env in request
|
||||||
environmentSlugs.forEach((environmentSlug) =>
|
environmentSlugs.forEach((environmentSlug) =>
|
||||||
@@ -383,13 +385,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
search,
|
search,
|
||||||
projectId
|
projectId
|
||||||
}: TGetDynamicSecretsCountDTO) => {
|
}: TGetDynamicSecretsCountDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -431,13 +434,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
@@ -462,13 +466,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO,
|
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO,
|
||||||
actor: OrgServiceActor
|
actor: OrgServiceActor
|
||||||
) => {
|
) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor.type,
|
actor: actor.type,
|
||||||
actor.id,
|
actorId: actor.id,
|
||||||
projectId,
|
projectId,
|
||||||
actor.authMethod,
|
actorAuthMethod: actor.authMethod,
|
||||||
actor.orgId
|
actorOrgId: actor.orgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||||
permission.can(
|
permission.can(
|
||||||
@@ -507,13 +512,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
...params
|
...params
|
||||||
}: TListDynamicSecretsMultiEnvDTO) => {
|
}: TListDynamicSecretsMultiEnvDTO) => {
|
||||||
if (!isInternal) {
|
if (!isInternal) {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
// verify user has access to each env in request
|
// verify user has access to each env in request
|
||||||
environmentSlugs.forEach((environmentSlug) =>
|
environmentSlugs.forEach((environmentSlug) =>
|
||||||
|
@@ -34,6 +34,8 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
|
|
||||||
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
|
const $getClient = async (providerInputs: z.infer<typeof DynamicSecretSqlDBSchema>) => {
|
||||||
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
|
const ssl = providerInputs.ca ? { rejectUnauthorized: false, ca: providerInputs.ca } : undefined;
|
||||||
|
const isMsSQLClient = providerInputs.client === SqlProviders.MsSQL;
|
||||||
|
|
||||||
const db = knex({
|
const db = knex({
|
||||||
client: providerInputs.client,
|
client: providerInputs.client,
|
||||||
connection: {
|
connection: {
|
||||||
@@ -43,7 +45,16 @@ export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
|||||||
user: providerInputs.username,
|
user: providerInputs.username,
|
||||||
password: providerInputs.password,
|
password: providerInputs.password,
|
||||||
ssl,
|
ssl,
|
||||||
pool: { min: 0, max: 1 }
|
pool: { min: 0, max: 1 },
|
||||||
|
// @ts-expect-error this is because of knexjs type signature issue. This is directly passed to driver
|
||||||
|
// https://github.com/knex/knex/blob/b6507a7129d2b9fafebf5f831494431e64c6a8a0/lib/dialects/mssql/index.js#L66
|
||||||
|
// https://github.com/tediousjs/tedious/blob/ebb023ed90969a7ec0e4b036533ad52739d921f7/test/config.ci.ts#L19
|
||||||
|
options: isMsSQLClient
|
||||||
|
? {
|
||||||
|
trustServerCertificate: !providerInputs.ca,
|
||||||
|
cryptoCredentialsDetails: providerInputs.ca ? { ca: providerInputs.ca } : {}
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
},
|
},
|
||||||
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
|
acquireConnectionTimeout: EXTERNAL_REQUEST_TIMEOUT
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
|
import { KMSServiceException } from "@aws-sdk/client-kms";
|
||||||
|
import { STSServiceException } from "@aws-sdk/client-sts";
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
@@ -71,7 +73,16 @@ export const externalKmsServiceFactory = ({
|
|||||||
switch (provider.type) {
|
switch (provider.type) {
|
||||||
case KmsProviders.Aws:
|
case KmsProviders.Aws:
|
||||||
{
|
{
|
||||||
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs });
|
const externalKms = await AwsKmsProviderFactory({ inputs: provider.inputs }).catch((error) => {
|
||||||
|
if (error instanceof STSServiceException || error instanceof KMSServiceException) {
|
||||||
|
throw new InternalServerError({
|
||||||
|
message: error.message ? `AWS error: ${error.message}` : ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
// if missing kms key this generate a new kms key id and returns new provider input
|
// if missing kms key this generate a new kms key id and returns new provider input
|
||||||
const newProviderInput = await externalKms.generateInputKmsKey();
|
const newProviderInput = await externalKms.generateInputKmsKey();
|
||||||
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
||||||
|
@@ -2,6 +2,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
||||||
|
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@@ -32,7 +33,7 @@ type TGroupServiceFactoryDep = {
|
|||||||
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
|
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById"
|
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById" | "transaction"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
||||||
@@ -45,6 +46,7 @@ type TGroupServiceFactoryDep = {
|
|||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
export type TGroupServiceFactory = ReturnType<typeof groupServiceFactory>;
|
||||||
@@ -59,7 +61,8 @@ export const groupServiceFactory = ({
|
|||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService,
|
||||||
|
oidcConfigDAL
|
||||||
}: TGroupServiceFactoryDep) => {
|
}: TGroupServiceFactoryDep) => {
|
||||||
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
@@ -88,12 +91,26 @@ export const groupServiceFactory = ({
|
|||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
||||||
|
|
||||||
const group = await groupDAL.create({
|
const group = await groupDAL.transaction(async (tx) => {
|
||||||
name,
|
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
|
||||||
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
if (existingGroup) {
|
||||||
orgId: actorOrgId,
|
throw new BadRequestError({
|
||||||
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
message: `Failed to create group with name '${name}'. Group with the same name already exists`
|
||||||
roleId: customRole?.id
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newGroup = await groupDAL.create(
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
slug: slug || slugify(`${name}-${alphaNumericNanoId(4)}`),
|
||||||
|
orgId: actorOrgId,
|
||||||
|
role: isCustomRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return newGroup;
|
||||||
});
|
});
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
@@ -145,21 +162,36 @@ export const groupServiceFactory = ({
|
|||||||
if (isCustomRole) customRole = customOrgRole;
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [updatedGroup] = await groupDAL.update(
|
const updatedGroup = await groupDAL.transaction(async (tx) => {
|
||||||
{
|
if (name) {
|
||||||
id: group.id
|
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
|
||||||
},
|
|
||||||
{
|
if (existingGroup && existingGroup.id !== id) {
|
||||||
name,
|
throw new BadRequestError({
|
||||||
slug: slug ? slugify(slug) : undefined,
|
message: `Failed to update group with name '${name}'. Group with the same name already exists`
|
||||||
...(role
|
});
|
||||||
? {
|
}
|
||||||
role: customRole ? OrgMembershipRole.Custom : role,
|
|
||||||
roleId: customRole?.id ?? null
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
const [updated] = await groupDAL.update(
|
||||||
|
{
|
||||||
|
id: group.id
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
slug: slug ? slugify(slug) : undefined,
|
||||||
|
...(role
|
||||||
|
? {
|
||||||
|
role: customRole ? OrgMembershipRole.Custom : role,
|
||||||
|
roleId: customRole?.id ?? null
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
|
||||||
return updatedGroup;
|
return updatedGroup;
|
||||||
};
|
};
|
||||||
@@ -282,6 +314,18 @@ export const groupServiceFactory = ({
|
|||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const oidcConfig = await oidcConfigDAL.findOne({
|
||||||
|
orgId: group.orgId,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oidcConfig?.manageGroupMemberships) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Cannot add user to group: OIDC group membership mapping is enabled - user must be assigned to this group in your OIDC provider."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
@@ -337,6 +381,18 @@ export const groupServiceFactory = ({
|
|||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const oidcConfig = await oidcConfigDAL.findOne({
|
||||||
|
orgId: group.orgId,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (oidcConfig?.manageGroupMemberships) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Cannot remove user from group: OIDC group membership mapping is enabled - user must be removed from this group in your OIDC provider."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
const { permission: groupRolePermission } = await permissionService.getOrgPermissionByRole(group.role, actorOrgId);
|
||||||
|
|
||||||
// check if user has broader or equal to privileges than group
|
// check if user has broader or equal to privileges than group
|
||||||
|
@@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
|
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
|
||||||
@@ -55,24 +55,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityId,
|
actorId: identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@@ -135,24 +137,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
);
|
);
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@@ -215,24 +219,26 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
);
|
);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
@@ -260,13 +266,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
@@ -294,13 +301,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
@@ -329,13 +337,14 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
subject(ProjectPermissionSub.Identity, { identityId: identityProjectMembership.identityId })
|
||||||
|
@@ -2,6 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf, subject } from "@casl/ability"
|
|||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
@@ -62,25 +63,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityId,
|
actorId: identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@@ -143,26 +146,28 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@@ -242,25 +247,27 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
);
|
);
|
||||||
|
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.IDENTITY,
|
actor: ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
actorId: identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
||||||
@@ -299,13 +306,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Identity, { identityId })
|
subject(ProjectPermissionSub.Identity, { identityId })
|
||||||
@@ -341,13 +349,14 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
identityProjectMembership.projectId,
|
projectId: identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
|
@@ -476,14 +476,14 @@ export const ldapConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via LDAP due to member limit reached. Upgrade plan to add more members."
|
||||||
|
@@ -50,8 +50,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
},
|
},
|
||||||
pkiEst: false,
|
pkiEst: false,
|
||||||
enforceMfa: false,
|
enforceMfa: false,
|
||||||
projectTemplates: false,
|
projectTemplates: false
|
||||||
appConnections: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
@@ -68,7 +68,6 @@ export type TFeatureSet = {
|
|||||||
pkiEst: boolean;
|
pkiEst: boolean;
|
||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
projectTemplates: false;
|
projectTemplates: false;
|
||||||
appConnections: false; // TODO: remove once live
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
@@ -5,6 +5,11 @@ import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet }
|
|||||||
|
|
||||||
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
import { OrgMembershipStatus, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
import { TOidcConfigsUpdate } from "@app/db/schemas/oidc-configs";
|
||||||
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
|
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||||
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -18,13 +23,18 @@ import {
|
|||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
import { getDefaultOrgMembershipRole } from "@app/services/org/org-role-fns";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
|
||||||
@@ -45,7 +55,14 @@ import {
|
|||||||
type TOidcConfigServiceFactoryDep = {
|
type TOidcConfigServiceFactoryDep = {
|
||||||
userDAL: Pick<
|
userDAL: Pick<
|
||||||
TUserDALFactory,
|
TUserDALFactory,
|
||||||
"create" | "findOne" | "transaction" | "updateById" | "findById" | "findUserEncKeyByUserId"
|
| "create"
|
||||||
|
| "findOne"
|
||||||
|
| "updateById"
|
||||||
|
| "findById"
|
||||||
|
| "findUserEncKeyByUserId"
|
||||||
|
| "findUserEncKeyByUserIdsBatch"
|
||||||
|
| "find"
|
||||||
|
| "transaction"
|
||||||
>;
|
>;
|
||||||
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
||||||
orgDAL: Pick<
|
orgDAL: Pick<
|
||||||
@@ -57,8 +74,23 @@ type TOidcConfigServiceFactoryDep = {
|
|||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
|
||||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
|
smtpService: Pick<TSmtpService, "sendMail" | "verify">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getUserOrgPermission">;
|
||||||
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "update" | "create">;
|
oidcConfigDAL: Pick<TOidcConfigDALFactory, "findOne" | "update" | "create">;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "findByOrgId">;
|
||||||
|
userGroupMembershipDAL: Pick<
|
||||||
|
TUserGroupMembershipDALFactory,
|
||||||
|
| "find"
|
||||||
|
| "transaction"
|
||||||
|
| "insertMany"
|
||||||
|
| "findGroupMembershipsByUserIdInOrg"
|
||||||
|
| "delete"
|
||||||
|
| "filterProjectsByUserMembership"
|
||||||
|
>;
|
||||||
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
||||||
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFactory>;
|
export type TOidcConfigServiceFactory = ReturnType<typeof oidcConfigServiceFactory>;
|
||||||
@@ -73,7 +105,14 @@ export const oidcConfigServiceFactory = ({
|
|||||||
tokenService,
|
tokenService,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
oidcConfigDAL
|
oidcConfigDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectBotDAL,
|
||||||
|
auditLogService
|
||||||
}: TOidcConfigServiceFactoryDep) => {
|
}: TOidcConfigServiceFactoryDep) => {
|
||||||
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||||
@@ -156,11 +195,21 @@ export const oidcConfigServiceFactory = ({
|
|||||||
isActive: oidcCfg.isActive,
|
isActive: oidcCfg.isActive,
|
||||||
allowedEmailDomains: oidcCfg.allowedEmailDomains,
|
allowedEmailDomains: oidcCfg.allowedEmailDomains,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret
|
clientSecret,
|
||||||
|
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const oidcLogin = async ({ externalId, email, firstName, lastName, orgId, callbackPort }: TOidcLoginDTO) => {
|
const oidcLogin = async ({
|
||||||
|
externalId,
|
||||||
|
email,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
orgId,
|
||||||
|
callbackPort,
|
||||||
|
groups = [],
|
||||||
|
manageGroupMemberships
|
||||||
|
}: TOidcLoginDTO) => {
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
||||||
@@ -315,6 +364,83 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manageGroupMemberships) {
|
||||||
|
const userGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(user.id, orgId);
|
||||||
|
const orgGroups = await groupDAL.findByOrgId(orgId);
|
||||||
|
|
||||||
|
const userGroupsNames = userGroups.map((membership) => membership.groupName);
|
||||||
|
const missingGroupsMemberships = groups.filter((groupName) => !userGroupsNames.includes(groupName));
|
||||||
|
const groupsToAddUserTo = orgGroups.filter((group) => missingGroupsMemberships.includes(group.name));
|
||||||
|
|
||||||
|
for await (const group of groupsToAddUserTo) {
|
||||||
|
await addUsersToGroupByUserIds({
|
||||||
|
userIds: [user.id],
|
||||||
|
group,
|
||||||
|
userDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
orgDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
projectDAL,
|
||||||
|
projectBotDAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToAddUserTo.length) {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
actor: {
|
||||||
|
type: ActorType.PLATFORM,
|
||||||
|
metadata: {}
|
||||||
|
},
|
||||||
|
orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_ASSIGN_USER,
|
||||||
|
metadata: {
|
||||||
|
userId: user.id,
|
||||||
|
userEmail: user.email ?? user.username,
|
||||||
|
assignedToGroups: groupsToAddUserTo.map(({ id, name }) => ({ id, name })),
|
||||||
|
userGroupsClaim: groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const membershipsToRemove = userGroups
|
||||||
|
.filter((membership) => !groups.includes(membership.groupName))
|
||||||
|
.map((membership) => membership.groupId);
|
||||||
|
const groupsToRemoveUserFrom = orgGroups.filter((group) => membershipsToRemove.includes(group.id));
|
||||||
|
|
||||||
|
for await (const group of groupsToRemoveUserFrom) {
|
||||||
|
await removeUsersFromGroupByUserIds({
|
||||||
|
userIds: [user.id],
|
||||||
|
group,
|
||||||
|
userDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
projectKeyDAL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (groupsToRemoveUserFrom.length) {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
actor: {
|
||||||
|
type: ActorType.PLATFORM,
|
||||||
|
metadata: {}
|
||||||
|
},
|
||||||
|
orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.OIDC_GROUP_MEMBERSHIP_MAPPING_REMOVE_USER,
|
||||||
|
metadata: {
|
||||||
|
userId: user.id,
|
||||||
|
userEmail: user.email ?? user.username,
|
||||||
|
removedFromGroups: groupsToRemoveUserFrom.map(({ id, name }) => ({ id, name })),
|
||||||
|
userGroupsClaim: groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
|
await licenseService.updateSubscriptionOrgMemberCount(organization.id);
|
||||||
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
|
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
|
||||||
@@ -385,7 +511,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
tokenEndpoint,
|
tokenEndpoint,
|
||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret
|
clientSecret,
|
||||||
|
manageGroupMemberships
|
||||||
}: TUpdateOidcCfgDTO) => {
|
}: TUpdateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({
|
||||||
slug: orgSlug
|
slug: orgSlug
|
||||||
@@ -448,7 +575,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
jwksUri,
|
jwksUri,
|
||||||
isActive,
|
isActive,
|
||||||
lastUsed: null
|
lastUsed: null,
|
||||||
|
manageGroupMemberships
|
||||||
};
|
};
|
||||||
|
|
||||||
if (clientId !== undefined) {
|
if (clientId !== undefined) {
|
||||||
@@ -491,7 +619,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
tokenEndpoint,
|
tokenEndpoint,
|
||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret
|
clientSecret,
|
||||||
|
manageGroupMemberships
|
||||||
}: TCreateOidcCfgDTO) => {
|
}: TCreateOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({
|
const org = await orgDAL.findOne({
|
||||||
slug: orgSlug
|
slug: orgSlug
|
||||||
@@ -589,7 +718,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
clientIdTag,
|
clientIdTag,
|
||||||
encryptedClientSecret,
|
encryptedClientSecret,
|
||||||
clientSecretIV,
|
clientSecretIV,
|
||||||
clientSecretTag
|
clientSecretTag,
|
||||||
|
manageGroupMemberships
|
||||||
});
|
});
|
||||||
|
|
||||||
return oidcCfg;
|
return oidcCfg;
|
||||||
@@ -683,7 +813,9 @@ export const oidcConfigServiceFactory = ({
|
|||||||
firstName: claims.given_name ?? "",
|
firstName: claims.given_name ?? "",
|
||||||
lastName: claims.family_name ?? "",
|
lastName: claims.family_name ?? "",
|
||||||
orgId: org.id,
|
orgId: org.id,
|
||||||
callbackPort
|
groups: claims.groups as string[] | undefined,
|
||||||
|
callbackPort,
|
||||||
|
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
||||||
})
|
})
|
||||||
.then(({ isUserCompleted, providerAuthToken }) => {
|
.then(({ isUserCompleted, providerAuthToken }) => {
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
@@ -697,5 +829,16 @@ export const oidcConfigServiceFactory = ({
|
|||||||
return strategy;
|
return strategy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { oidcLogin, getOrgAuthStrategy, getOidc, updateOidcCfg, createOidcCfg };
|
const isOidcManageGroupMembershipsEnabled = async (orgId: string, actor: OrgServiceActor) => {
|
||||||
|
await permissionService.getUserOrgPermission(actor.id, orgId, actor.authMethod, actor.orgId);
|
||||||
|
|
||||||
|
const oidcConfig = await oidcConfigDAL.findOne({
|
||||||
|
orgId,
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return Boolean(oidcConfig?.manageGroupMemberships);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { oidcLogin, getOrgAuthStrategy, getOidc, updateOidcCfg, createOidcCfg, isOidcManageGroupMembershipsEnabled };
|
||||||
};
|
};
|
||||||
|
@@ -12,6 +12,8 @@ export type TOidcLoginDTO = {
|
|||||||
lastName?: string;
|
lastName?: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
callbackPort?: string;
|
callbackPort?: string;
|
||||||
|
groups?: string[];
|
||||||
|
manageGroupMemberships?: boolean | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TGetOidcCfgDTO =
|
export type TGetOidcCfgDTO =
|
||||||
@@ -37,6 +39,7 @@ export type TCreateOidcCfgDTO = {
|
|||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
orgSlug: string;
|
||||||
|
manageGroupMemberships: boolean;
|
||||||
} & TGenericPermission;
|
} & TGenericPermission;
|
||||||
|
|
||||||
export type TUpdateOidcCfgDTO = Partial<{
|
export type TUpdateOidcCfgDTO = Partial<{
|
||||||
@@ -52,5 +55,6 @@ export type TUpdateOidcCfgDTO = Partial<{
|
|||||||
clientSecret: string;
|
clientSecret: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
orgSlug: string;
|
orgSlug: string;
|
||||||
|
manageGroupMemberships: boolean;
|
||||||
}> &
|
}> &
|
||||||
TGenericPermission;
|
TGenericPermission;
|
||||||
|
@@ -1,4 +1,12 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CASL_ACTION_SCHEMA_ENUM,
|
||||||
|
CASL_ACTION_SCHEMA_NATIVE_ENUM
|
||||||
|
} from "@app/ee/services/permission/permission-schemas";
|
||||||
|
import { PermissionConditionSchema } from "@app/ee/services/permission/permission-types";
|
||||||
|
import { PermissionConditionOperators } from "@app/lib/casl";
|
||||||
|
|
||||||
export enum OrgPermissionActions {
|
export enum OrgPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
@@ -7,6 +15,14 @@ export enum OrgPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionAppConnectionActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
Connect = "connect"
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
AccessAllProjects = "access-all-projects"
|
AccessAllProjects = "access-all-projects"
|
||||||
}
|
}
|
||||||
@@ -31,6 +47,10 @@ export enum OrgPermissionSubjects {
|
|||||||
AppConnections = "app-connections"
|
AppConnections = "app-connections"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AppConnectionSubjectFields = {
|
||||||
|
connectionId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
|
| [OrgPermissionActions.Create, OrgPermissionSubjects.Workspace]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Role]
|
| [OrgPermissionActions, OrgPermissionSubjects.Role]
|
||||||
@@ -47,9 +67,109 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
| [OrgPermissionActions, OrgPermissionSubjects.Kms]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
| [OrgPermissionActions, OrgPermissionSubjects.AuditLogs]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
| [OrgPermissionActions, OrgPermissionSubjects.ProjectTemplates]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.AppConnections]
|
| [
|
||||||
|
OrgPermissionAppConnectionActions,
|
||||||
|
(
|
||||||
|
| OrgPermissionSubjects.AppConnections
|
||||||
|
| (ForcedSubject<OrgPermissionSubjects.AppConnections> & AppConnectionSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole];
|
||||||
|
|
||||||
|
const AppConnectionConditionSchema = z
|
||||||
|
.object({
|
||||||
|
connectionId: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
|
export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Workspace).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_ENUM([OrgPermissionActions.Create]).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Role).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Member).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Settings).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.IncidentAccount).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Sso).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Scim).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Ldap).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Groups).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.SecretScanning).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Billing).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Identity).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.Kms).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.AuditLogs).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.ProjectTemplates).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.AppConnections).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionAppConnectionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: AppConnectionConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.AdminConsole).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionAdminConsoleAction).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
// ws permissions
|
// ws permissions
|
||||||
@@ -125,10 +245,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.ProjectTemplates);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.ProjectTemplates);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Read, OrgPermissionSubjects.AppConnections);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Create, OrgPermissionSubjects.AppConnections);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Edit, OrgPermissionSubjects.AppConnections);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Delete, OrgPermissionSubjects.AppConnections);
|
||||||
|
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
|
||||||
|
|
||||||
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
can(OrgPermissionAdminConsoleAction.AccessAllProjects, OrgPermissionSubjects.AdminConsole);
|
||||||
|
|
||||||
@@ -160,7 +281,7 @@ const buildMemberPermission = () => {
|
|||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.AuditLogs);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.AppConnections);
|
can(OrgPermissionAppConnectionActions.Connect, OrgPermissionSubjects.AppConnections);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
9
backend/src/ee/services/permission/permission-schemas.ts
Normal file
9
backend/src/ee/services/permission/permission-schemas.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const CASL_ACTION_SCHEMA_NATIVE_ENUM = <ACTION extends z.EnumLike>(actions: ACTION) =>
|
||||||
|
z
|
||||||
|
.union([z.nativeEnum(actions), z.nativeEnum(actions).array().min(1)])
|
||||||
|
.transform((el) => (typeof el === "string" ? [el] : el));
|
||||||
|
|
||||||
|
export const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
|
||||||
|
z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
|
@@ -1,3 +1,6 @@
|
|||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export type TBuildProjectPermissionDTO = {
|
export type TBuildProjectPermissionDTO = {
|
||||||
permissions?: unknown;
|
permissions?: unknown;
|
||||||
role: string;
|
role: string;
|
||||||
@@ -7,3 +10,34 @@ export type TBuildOrgPermissionDTO = {
|
|||||||
permissions?: unknown;
|
permissions?: unknown;
|
||||||
role: string;
|
role: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
export type TGetUserProjectPermissionArg = {
|
||||||
|
userId: string;
|
||||||
|
projectId: string;
|
||||||
|
authMethod: ActorAuthMethod;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
userOrgId?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetIdentityProjectPermissionArg = {
|
||||||
|
identityId: string;
|
||||||
|
projectId: string;
|
||||||
|
identityOrgId?: string;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetServiceTokenProjectPermissionArg = {
|
||||||
|
serviceTokenId: string;
|
||||||
|
projectId: string;
|
||||||
|
actorOrgId?: string;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetProjectPermissionArg = {
|
||||||
|
actor: ActorType;
|
||||||
|
actorId: string;
|
||||||
|
projectId: string;
|
||||||
|
actorAuthMethod: ActorAuthMethod;
|
||||||
|
actorOrgId?: string;
|
||||||
|
actionProjectType: ActionProjectType;
|
||||||
|
};
|
||||||
|
@@ -4,9 +4,9 @@ import { MongoQuery } from "@ucast/mongo2js";
|
|||||||
import handlebars from "handlebars";
|
import handlebars from "handlebars";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ActionProjectType,
|
||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectType,
|
|
||||||
ServiceTokenScopes,
|
ServiceTokenScopes,
|
||||||
TIdentityProjectMemberships,
|
TIdentityProjectMemberships,
|
||||||
TProjectMemberships
|
TProjectMemberships
|
||||||
@@ -23,7 +23,14 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
|
|||||||
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
||||||
import { TPermissionDALFactory } from "./permission-dal";
|
import { TPermissionDALFactory } from "./permission-dal";
|
||||||
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
|
import { escapeHandlebarsMissingMetadata, validateOrgSSO } from "./permission-fns";
|
||||||
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-service-types";
|
import {
|
||||||
|
TBuildOrgPermissionDTO,
|
||||||
|
TBuildProjectPermissionDTO,
|
||||||
|
TGetIdentityProjectPermissionArg,
|
||||||
|
TGetProjectPermissionArg,
|
||||||
|
TGetServiceTokenProjectPermissionArg,
|
||||||
|
TGetUserProjectPermissionArg
|
||||||
|
} from "./permission-service-types";
|
||||||
import {
|
import {
|
||||||
buildServiceTokenProjectPermission,
|
buildServiceTokenProjectPermission,
|
||||||
projectAdminPermissions,
|
projectAdminPermissions,
|
||||||
@@ -193,12 +200,13 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// user permission for a project in an organization
|
// user permission for a project in an organization
|
||||||
const getUserProjectPermission = async (
|
const getUserProjectPermission = async ({
|
||||||
userId: string,
|
userId,
|
||||||
projectId: string,
|
projectId,
|
||||||
authMethod: ActorAuthMethod,
|
authMethod,
|
||||||
userOrgId?: string
|
userOrgId,
|
||||||
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
actionProjectType
|
||||||
|
}: TGetUserProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
||||||
|
|
||||||
@@ -219,6 +227,12 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
|
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
|
||||||
|
|
||||||
|
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== userProjectPermission.projectType) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// join two permissions and pass to build the final permission set
|
// join two permissions and pass to build the final permission set
|
||||||
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
const additionalPrivileges =
|
const additionalPrivileges =
|
||||||
@@ -256,13 +270,6 @@ export const permissionServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
permission,
|
permission,
|
||||||
membership: userProjectPermission,
|
membership: userProjectPermission,
|
||||||
ForbidOnInvalidProjectType: (productType: ProjectType) => {
|
|
||||||
if (productType !== userProjectPermission.projectType) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${userProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
userProjectPermission.roles.findIndex(
|
userProjectPermission.roles.findIndex(
|
||||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
@@ -270,11 +277,12 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIdentityProjectPermission = async (
|
const getIdentityProjectPermission = async ({
|
||||||
identityId: string,
|
identityId,
|
||||||
projectId: string,
|
projectId,
|
||||||
identityOrgId: string | undefined
|
identityOrgId,
|
||||||
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
actionProjectType
|
||||||
|
}: TGetIdentityProjectPermissionArg): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
if (!identityProjectPermission)
|
if (!identityProjectPermission)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
@@ -293,6 +301,12 @@ export const permissionServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== identityProjectPermission.projectType) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${actionProjectType} are not allowed.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const rolePermissions =
|
const rolePermissions =
|
||||||
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
identityProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
const additionalPrivileges =
|
const additionalPrivileges =
|
||||||
@@ -331,13 +345,6 @@ export const permissionServiceFactory = ({
|
|||||||
return {
|
return {
|
||||||
permission,
|
permission,
|
||||||
membership: identityProjectPermission,
|
membership: identityProjectPermission,
|
||||||
ForbidOnInvalidProjectType: (productType: ProjectType) => {
|
|
||||||
if (productType !== identityProjectPermission.projectType) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${identityProjectPermission.projectType}. Operations of type ${productType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
identityProjectPermission.roles.findIndex(
|
identityProjectPermission.roles.findIndex(
|
||||||
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
({ role: slug, customRoleSlug }) => role === slug || slug === customRoleSlug
|
||||||
@@ -345,11 +352,12 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getServiceTokenProjectPermission = async (
|
const getServiceTokenProjectPermission = async ({
|
||||||
serviceTokenId: string,
|
serviceTokenId,
|
||||||
projectId: string,
|
projectId,
|
||||||
actorOrgId: string | undefined
|
actorOrgId,
|
||||||
) => {
|
actionProjectType
|
||||||
|
}: TGetServiceTokenProjectPermissionArg) => {
|
||||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||||
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
||||||
|
|
||||||
@@ -373,17 +381,16 @@ export const permissionServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actionProjectType !== ActionProjectType.Any && actionProjectType !== serviceTokenProject.type) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${actionProjectType} are not allowed.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||||
return {
|
return {
|
||||||
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
permission: buildServiceTokenProjectPermission(scopes, serviceToken.permissions),
|
||||||
membership: undefined,
|
membership: undefined
|
||||||
ForbidOnInvalidProjectType: (productType: ProjectType) => {
|
|
||||||
if (productType !== serviceTokenProject.type) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `The project is of type ${serviceTokenProject.type}. Operations of type ${productType} are not allowed.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -392,7 +399,6 @@ export const permissionServiceFactory = ({
|
|||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
membership: undefined;
|
membership: undefined;
|
||||||
hasRole: (arg: string) => boolean;
|
hasRole: (arg: string) => boolean;
|
||||||
ForbidOnInvalidProjectType: (type: ProjectType) => void;
|
|
||||||
} // service token doesn't have both membership and roles
|
} // service token doesn't have both membership and roles
|
||||||
: {
|
: {
|
||||||
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
permission: MongoAbility<ProjectPermissionSet, MongoQuery>;
|
||||||
@@ -402,7 +408,6 @@ export const permissionServiceFactory = ({
|
|||||||
roles: Array<{ role: string }>;
|
roles: Array<{ role: string }>;
|
||||||
};
|
};
|
||||||
hasRole: (role: string) => boolean;
|
hasRole: (role: string) => boolean;
|
||||||
ForbidOnInvalidProjectType: (type: ProjectType) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectPermissions = async (projectId: string) => {
|
const getProjectPermissions = async (projectId: string) => {
|
||||||
@@ -522,20 +527,37 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectPermission = async <T extends ActorType>(
|
const getProjectPermission = async <T extends ActorType>({
|
||||||
type: T,
|
actor,
|
||||||
id: string,
|
actorId,
|
||||||
projectId: string,
|
projectId,
|
||||||
actorAuthMethod: ActorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId: string | undefined
|
actorOrgId,
|
||||||
): Promise<TProjectPermissionRT<T>> => {
|
actionProjectType
|
||||||
switch (type) {
|
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||||
|
switch (actor) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
return getUserProjectPermission(id, projectId, actorAuthMethod, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
return getUserProjectPermission({
|
||||||
|
userId: actorId,
|
||||||
|
projectId,
|
||||||
|
authMethod: actorAuthMethod,
|
||||||
|
userOrgId: actorOrgId,
|
||||||
|
actionProjectType
|
||||||
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.SERVICE:
|
case ActorType.SERVICE:
|
||||||
return getServiceTokenProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
return getServiceTokenProjectPermission({
|
||||||
|
serviceTokenId: actorId,
|
||||||
|
projectId,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType
|
||||||
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
return getIdentityProjectPermission({
|
||||||
|
identityId: actorId,
|
||||||
|
projectId,
|
||||||
|
identityOrgId: actorOrgId,
|
||||||
|
actionProjectType
|
||||||
|
}) as Promise<TProjectPermissionRT<T>>;
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Invalid actor provided",
|
message: "Invalid actor provided",
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CASL_ACTION_SCHEMA_ENUM,
|
||||||
|
CASL_ACTION_SCHEMA_NATIVE_ENUM
|
||||||
|
} from "@app/ee/services/permission/permission-schemas";
|
||||||
import { conditionsMatcher, PermissionConditionOperators } from "@app/lib/casl";
|
import { conditionsMatcher, PermissionConditionOperators } from "@app/lib/casl";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
|
|
||||||
@@ -30,6 +34,16 @@ export enum ProjectPermissionDynamicSecretActions {
|
|||||||
Lease = "lease"
|
Lease = "lease"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionSecretSyncActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
SyncSecrets = "sync-secrets",
|
||||||
|
ImportSecrets = "import-secrets",
|
||||||
|
RemoveSecrets = "remove-secrets"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSub {
|
export enum ProjectPermissionSub {
|
||||||
Role = "role",
|
Role = "role",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
@@ -60,7 +74,8 @@ export enum ProjectPermissionSub {
|
|||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
Cmek = "cmek"
|
Cmek = "cmek",
|
||||||
|
SecretSyncs = "secret-syncs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SecretSubjectFields = {
|
export type SecretSubjectFields = {
|
||||||
@@ -140,6 +155,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateTemplates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
|
| [ProjectPermissionSecretSyncActions, ProjectPermissionSub.SecretSyncs]
|
||||||
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
|
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||||
@@ -147,14 +163,27 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
||||||
|
|
||||||
const CASL_ACTION_SCHEMA_NATIVE_ENUM = <ACTION extends z.EnumLike>(actions: ACTION) =>
|
const SECRET_PATH_MISSING_SLASH_ERR_MSG = "Invalid Secret Path; it must start with a '/'";
|
||||||
|
const SECRET_PATH_PERMISSION_OPERATOR_SCHEMA = z.union([
|
||||||
|
z.string().refine((val) => val.startsWith("/"), SECRET_PATH_MISSING_SLASH_ERR_MSG),
|
||||||
z
|
z
|
||||||
.union([z.nativeEnum(actions), z.nativeEnum(actions).array().min(1)])
|
.object({
|
||||||
.transform((el) => (typeof el === "string" ? [el] : el));
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ].refine(
|
||||||
|
(val) => val.startsWith("/"),
|
||||||
const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
|
SECRET_PATH_MISSING_SLASH_ERR_MSG
|
||||||
z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
|
),
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ].refine(
|
||||||
|
(val) => val.startsWith("/"),
|
||||||
|
SECRET_PATH_MISSING_SLASH_ERR_MSG
|
||||||
|
),
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN].refine(
|
||||||
|
(val) => val.every((el) => el.startsWith("/")),
|
||||||
|
SECRET_PATH_MISSING_SLASH_ERR_MSG
|
||||||
|
),
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]);
|
||||||
// akhilmhdh: don't modify this for v2
|
// akhilmhdh: don't modify this for v2
|
||||||
// if you want to update create a new schema
|
// if you want to update create a new schema
|
||||||
const SecretConditionV1Schema = z
|
const SecretConditionV1Schema = z
|
||||||
@@ -169,17 +198,7 @@ const SecretConditionV1Schema = z
|
|||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
]),
|
]),
|
||||||
secretPath: z.union([
|
secretPath: SECRET_PATH_PERMISSION_OPERATOR_SCHEMA
|
||||||
z.string(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
@@ -196,17 +215,7 @@ const SecretConditionV2Schema = z
|
|||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
]),
|
]),
|
||||||
secretPath: z.union([
|
secretPath: SECRET_PATH_PERMISSION_OPERATOR_SCHEMA,
|
||||||
z.string(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
]),
|
|
||||||
secretName: z.union([
|
secretName: z.union([
|
||||||
z.string(),
|
z.string(),
|
||||||
z
|
z
|
||||||
@@ -392,10 +401,15 @@ const GeneralPermissionSchema = [
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.SecretSyncs).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretSyncActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -549,6 +563,18 @@ const buildAdminPermissionRules = () => {
|
|||||||
],
|
],
|
||||||
ProjectPermissionSub.Cmek
|
ProjectPermissionSub.Cmek
|
||||||
);
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretSyncActions.Create,
|
||||||
|
ProjectPermissionSecretSyncActions.Edit,
|
||||||
|
ProjectPermissionSecretSyncActions.Delete,
|
||||||
|
ProjectPermissionSecretSyncActions.Read,
|
||||||
|
ProjectPermissionSecretSyncActions.SyncSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.ImportSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.RemoveSecrets
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretSyncs
|
||||||
|
);
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -713,6 +739,19 @@ const buildMemberPermissionRules = () => {
|
|||||||
ProjectPermissionSub.Cmek
|
ProjectPermissionSub.Cmek
|
||||||
);
|
);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionSecretSyncActions.Create,
|
||||||
|
ProjectPermissionSecretSyncActions.Edit,
|
||||||
|
ProjectPermissionSecretSyncActions.Delete,
|
||||||
|
ProjectPermissionSecretSyncActions.Read,
|
||||||
|
ProjectPermissionSecretSyncActions.SyncSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.ImportSecrets,
|
||||||
|
ProjectPermissionSecretSyncActions.RemoveSecrets
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretSyncs
|
||||||
|
);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -746,6 +785,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateAuthorities);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateAuthorities);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificates);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SshCertificateTemplates);
|
||||||
|
can(ProjectPermissionSecretSyncActions.Read, ProjectPermissionSub.SecretSyncs);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
@@ -2,7 +2,7 @@ import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
|||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
@@ -55,21 +55,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!projectMembership)
|
if (!projectMembership)
|
||||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
projectMembership.userId,
|
actorId: projectMembership.userId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@@ -140,21 +142,23 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
projectMembership.userId,
|
actorId: projectMembership.userId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
// we need to validate that the privilege given is not higher than the assigning users permission
|
// we need to validate that the privilege given is not higher than the assigning users permission
|
||||||
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
// @ts-expect-error this is expected error because of one being really accurate rule definition other being a bit more broader. Both are valid casl rules
|
||||||
@@ -224,13 +228,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||||
@@ -260,13 +265,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -286,13 +292,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
if (!projectMembership)
|
if (!projectMembership)
|
||||||
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
|
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
|
||||||
|
@@ -421,14 +421,14 @@ export const samlConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
message: "Failed to create new member via SAML due to member limit reached. Upgrade plan to add more members."
|
||||||
|
@@ -531,7 +531,7 @@ export const scimServiceFactory = ({
|
|||||||
firstName: scimUser.name.givenName,
|
firstName: scimUser.name.givenName,
|
||||||
email: scimUser.emails[0].value,
|
email: scimUser.emails[0].value,
|
||||||
lastName: scimUser.name.familyName,
|
lastName: scimUser.name.familyName,
|
||||||
isEmailVerified: hasEmailChanged ? trustScimEmails : true
|
isEmailVerified: hasEmailChanged ? trustScimEmails : undefined
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -790,6 +790,21 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const newGroup = await groupDAL.transaction(async (tx) => {
|
const newGroup = await groupDAL.transaction(async (tx) => {
|
||||||
|
const conflictingGroup = await groupDAL.findOne(
|
||||||
|
{
|
||||||
|
name: displayName,
|
||||||
|
orgId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (conflictingGroup) {
|
||||||
|
throw new ScimRequestError({
|
||||||
|
detail: `Group with name '${displayName}' already exists in the organization`,
|
||||||
|
status: 409
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const group = await groupDAL.create(
|
const group = await groupDAL.create(
|
||||||
{
|
{
|
||||||
name: displayName,
|
name: displayName,
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -79,14 +79,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
if (!groupApprovers.length && approvals > approvers.length)
|
if (!groupApprovers.length && approvals > approvers.length)
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
@@ -193,14 +193,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalPolicy.projectId,
|
projectId: secretApprovalPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -288,14 +288,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
if (!sapPolicy)
|
if (!sapPolicy)
|
||||||
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
|
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sapPolicy.projectId,
|
projectId: sapPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
@@ -328,13 +328,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
projectId
|
projectId
|
||||||
}: TListSapDTO) => {
|
}: TListSapDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
const sapPolicies = await secretApprovalPolicyDAL.find({ projectId, deletedAt: null });
|
||||||
@@ -372,7 +373,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
}: TGetBoardSapDTO) => {
|
}: TGetBoardSapDTO) => {
|
||||||
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||||
};
|
};
|
||||||
@@ -392,13 +400,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sapPolicy.projectId,
|
projectId: sapPolicy.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ActionProjectType,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectType,
|
|
||||||
SecretEncryptionAlgo,
|
SecretEncryptionAlgo,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
SecretType,
|
SecretType,
|
||||||
@@ -147,13 +147,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
await permissionService.getProjectPermission(
|
await permissionService.getProjectPermission({
|
||||||
actor as ActorType.USER,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
|
const count = await secretApprovalRequestDAL.findProjectRequestCount(projectId, actorId);
|
||||||
return count;
|
return count;
|
||||||
@@ -173,7 +174,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}: TListApprovalsDTO) => {
|
}: TListApprovalsDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@@ -216,13 +224,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
|
|
||||||
const { policy } = secretApprovalRequest;
|
const { policy } = secretApprovalRequest;
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
@@ -336,13 +345,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
projectId: secretApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
@@ -402,13 +412,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
secretApprovalRequest.projectId,
|
projectId: secretApprovalRequest.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
@@ -458,13 +469,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hasRole } = await permissionService.getProjectPermission(
|
const { hasRole } = await permissionService.getProjectPermission({
|
||||||
ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
@@ -889,14 +901,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}: TGenerateSecretApprovalRequestDTO) => {
|
}: TGenerateSecretApprovalRequestDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
@@ -1170,14 +1182,14 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
if (actor === ActorType.SERVICE || actor === ActorType.Machine)
|
||||||
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
throw new BadRequestError({ message: "Cannot use service token or machine token over protected branches" });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
@@ -1255,9 +1267,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
if (secrets.length)
|
|
||||||
|
if (secrets.length !== secretsWithNewName.length)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Secret does not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
message: `Secret does not exist: ${secrets.map((el) => el.key).join(",")}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -180,6 +180,8 @@ export const secretRotationQueueFactory = ({
|
|||||||
provider.template.client === TDbProviderClients.MsSqlServer
|
provider.template.client === TDbProviderClients.MsSqlServer
|
||||||
? ({
|
? ({
|
||||||
encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT,
|
encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT,
|
||||||
|
// when ca is provided use that
|
||||||
|
trustServerCertificate: !ca,
|
||||||
cryptoCredentialsDetails: ca ? { ca } : {}
|
cryptoCredentialsDetails: ca ? { ca } : {}
|
||||||
} as Record<string, unknown>)
|
} as Record<string, unknown>)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
|
|
||||||
import { ProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
import { ActionProjectType, ProjectVersion, TableName } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
@@ -53,14 +53,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
projectId
|
projectId
|
||||||
}: TProjectPermission) => {
|
}: TProjectPermission) => {
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -82,14 +82,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
environment
|
environment
|
||||||
}: TCreateSecretRotationDTO) => {
|
}: TCreateSecretRotationDTO) => {
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretRotation
|
ProjectPermissionSub.SecretRotation
|
||||||
@@ -191,13 +191,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
|
const getByProjectId = async ({ actorId, projectId, actor, actorOrgId, actorAuthMethod }: TListByProjectIdDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@@ -236,14 +237,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
|
message: "Failed to add secret rotation due to plan restriction. Upgrade plan to add secret rotation."
|
||||||
});
|
});
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
doc.projectId,
|
projectId: project.id,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
|
||||||
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
|
await secretRotationQueue.removeFromQueue(doc.id, doc.interval);
|
||||||
await secretRotationQueue.addToQueue(doc.id, doc.interval);
|
await secretRotationQueue.addToQueue(doc.id, doc.interval);
|
||||||
@@ -254,14 +255,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
const doc = await secretRotationDAL.findById(rotationId);
|
const doc = await secretRotationDAL.findById(rotationId);
|
||||||
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
|
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
doc.projectId,
|
projectId: doc.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SecretRotation
|
ProjectPermissionSub.SecretRotation
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
import { Knex } from "knex";
|
import knex, { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TSecretScanningGitRisksInsert } from "@app/db/schemas";
|
import { TableName, TSecretScanningGitRisksInsert } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { SecretScanningResolvedStatus, TGetOrgRisksDTO } from "./secret-scanning-types";
|
||||||
|
|
||||||
export type TSecretScanningDALFactory = ReturnType<typeof secretScanningDALFactory>;
|
export type TSecretScanningDALFactory = ReturnType<typeof secretScanningDALFactory>;
|
||||||
|
|
||||||
@@ -19,5 +22,70 @@ export const secretScanningDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...gitRiskOrm, upsert };
|
const findByOrgId = async (orgId: string, filter: TGetOrgRisksDTO["filter"], tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
// Find statements
|
||||||
|
const sqlQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
|
||||||
|
// eslint-disable-next-line func-names
|
||||||
|
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId);
|
||||||
|
|
||||||
|
if (filter.repositoryNames) {
|
||||||
|
void sqlQuery.whereIn(`${TableName.SecretScanningGitRisk}.repositoryFullName`, filter.repositoryNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.resolvedStatus) {
|
||||||
|
if (filter.resolvedStatus !== SecretScanningResolvedStatus.All) {
|
||||||
|
const isResolved = filter.resolvedStatus === SecretScanningResolvedStatus.Resolved;
|
||||||
|
|
||||||
|
void sqlQuery.where(`${TableName.SecretScanningGitRisk}.isResolved`, isResolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select statements
|
||||||
|
void sqlQuery
|
||||||
|
.select(selectAllTableCols(TableName.SecretScanningGitRisk))
|
||||||
|
.limit(filter.limit)
|
||||||
|
.offset(filter.offset);
|
||||||
|
|
||||||
|
if (filter.orderBy) {
|
||||||
|
const orderDirection = filter.orderDirection || OrderByDirection.ASC;
|
||||||
|
|
||||||
|
void sqlQuery.orderBy(filter.orderBy, orderDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
|
||||||
|
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
const uniqueReposQuery = (tx || db.replicaNode())(TableName.SecretScanningGitRisk)
|
||||||
|
.where(`${TableName.SecretScanningGitRisk}.orgId`, orgId)
|
||||||
|
.distinct("repositoryFullName")
|
||||||
|
.select("repositoryFullName");
|
||||||
|
|
||||||
|
// we timeout long running queries to prevent DB resource issues (2 minutes)
|
||||||
|
const docs = await sqlQuery.timeout(1000 * 120);
|
||||||
|
const uniqueRepos = await uniqueReposQuery.timeout(1000 * 120);
|
||||||
|
const totalCount = await countQuery;
|
||||||
|
|
||||||
|
return {
|
||||||
|
risks: docs,
|
||||||
|
totalCount: Number(totalCount?.[0].count),
|
||||||
|
repos: uniqueRepos
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((r) => r.repositoryFullName!)
|
||||||
|
.sort((a, b) => a.localeCompare(b))
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof knex.KnexTimeoutError) {
|
||||||
|
throw new GatewayTimeoutError({
|
||||||
|
error,
|
||||||
|
message: "Failed to fetch secret leaks due to timeout. Add more search filters."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new DatabaseError({ error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...gitRiskOrm, upsert, findByOrgId };
|
||||||
};
|
};
|
||||||
|
@@ -15,6 +15,7 @@ import { TSecretScanningDALFactory } from "./secret-scanning-dal";
|
|||||||
import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
|
import { TSecretScanningQueueFactory } from "./secret-scanning-queue";
|
||||||
import {
|
import {
|
||||||
SecretScanningRiskStatus,
|
SecretScanningRiskStatus,
|
||||||
|
TGetAllOrgRisksDTO,
|
||||||
TGetOrgInstallStatusDTO,
|
TGetOrgInstallStatusDTO,
|
||||||
TGetOrgRisksDTO,
|
TGetOrgRisksDTO,
|
||||||
TInstallAppSessionDTO,
|
TInstallAppSessionDTO,
|
||||||
@@ -118,11 +119,21 @@ export const secretScanningServiceFactory = ({
|
|||||||
return Boolean(appInstallation);
|
return Boolean(appInstallation);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetOrgRisksDTO) => {
|
const getRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId, filter }: TGetOrgRisksDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
|
const results = await secretScanningDAL.findByOrgId(orgId, filter);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllRisksByOrg = async ({ actor, orgId, actorId, actorAuthMethod, actorOrgId }: TGetAllOrgRisksDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
|
const risks = await secretScanningDAL.find({ orgId }, { sort: [["createdAt", "desc"]] });
|
||||||
return { risks };
|
return risks;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRiskStatus = async ({
|
const updateRiskStatus = async ({
|
||||||
@@ -189,6 +200,7 @@ export const secretScanningServiceFactory = ({
|
|||||||
linkInstallationToOrg,
|
linkInstallationToOrg,
|
||||||
getOrgInstallationStatus,
|
getOrgInstallationStatus,
|
||||||
getRisksByOrg,
|
getRisksByOrg,
|
||||||
|
getAllRisksByOrg,
|
||||||
updateRiskStatus,
|
updateRiskStatus,
|
||||||
handleRepoPushEvent,
|
handleRepoPushEvent,
|
||||||
handleRepoDeleteEvent
|
handleRepoDeleteEvent
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { TOrgPermission } from "@app/lib/types";
|
import { OrderByDirection, TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export enum SecretScanningRiskStatus {
|
export enum SecretScanningRiskStatus {
|
||||||
FalsePositive = "RESOLVED_FALSE_POSITIVE",
|
FalsePositive = "RESOLVED_FALSE_POSITIVE",
|
||||||
@@ -7,6 +7,12 @@ export enum SecretScanningRiskStatus {
|
|||||||
Unresolved = "UNRESOLVED"
|
Unresolved = "UNRESOLVED"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SecretScanningResolvedStatus {
|
||||||
|
All = "all",
|
||||||
|
Resolved = "resolved",
|
||||||
|
Unresolved = "unresolved"
|
||||||
|
}
|
||||||
|
|
||||||
export type TInstallAppSessionDTO = TOrgPermission;
|
export type TInstallAppSessionDTO = TOrgPermission;
|
||||||
|
|
||||||
export type TLinkInstallSessionDTO = {
|
export type TLinkInstallSessionDTO = {
|
||||||
@@ -16,7 +22,22 @@ export type TLinkInstallSessionDTO = {
|
|||||||
|
|
||||||
export type TGetOrgInstallStatusDTO = TOrgPermission;
|
export type TGetOrgInstallStatusDTO = TOrgPermission;
|
||||||
|
|
||||||
export type TGetOrgRisksDTO = TOrgPermission;
|
type RiskFilter = {
|
||||||
|
offset: number;
|
||||||
|
limit: number;
|
||||||
|
orderBy?: "createdAt" | "name";
|
||||||
|
orderDirection?: OrderByDirection;
|
||||||
|
repositoryNames?: string[];
|
||||||
|
resolvedStatus?: SecretScanningResolvedStatus;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetOrgRisksDTO = {
|
||||||
|
filter: RiskFilter;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export type TGetAllOrgRisksDTO = {
|
||||||
|
filter: Omit<RiskFilter, "offset" | "limit" | "orderBy" | "orderDirection">;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
export type TUpdateRiskStatusDTO = {
|
export type TUpdateRiskStatusDTO = {
|
||||||
riskId: string;
|
riskId: string;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
import { ActionProjectType, TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { InternalServerError, NotFoundError } from "@app/lib/errors";
|
import { InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
@@ -83,13 +83,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
path
|
path
|
||||||
}: TProjectSnapshotCountDTO) => {
|
}: TProjectSnapshotCountDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
@@ -119,13 +120,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
limit = 20,
|
limit = 20,
|
||||||
offset = 0
|
offset = 0
|
||||||
}: TProjectSnapshotListDTO) => {
|
}: TProjectSnapshotListDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
|
|
||||||
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
// We need to check if the user has access to the secrets in the folder. If we don't do this, a user could theoretically access snapshot secret values even if they don't have read access to the secrets in the folder.
|
||||||
@@ -147,13 +149,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
|
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
|
||||||
const snapshot = await snapshotDAL.findById(id);
|
const snapshot = await snapshotDAL.findById(id);
|
||||||
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
|
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
snapshot.projectId,
|
projectId: snapshot.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||||
@@ -322,14 +325,14 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
|
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
|
||||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
snapshot.projectId,
|
projectId: snapshot.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
ForbidOnInvalidProjectType(ProjectType.SecretManager);
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SecretRollback
|
ProjectPermissionSub.SecretRollback
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -54,15 +54,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
@@ -127,15 +127,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
certTemplate.projectId,
|
projectId: certTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
@@ -196,15 +196,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
certificateTemplate.projectId,
|
projectId: certificateTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
@@ -223,15 +223,15 @@ export const sshCertificateTemplateServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
certTemplate.projectId,
|
projectId: certTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
import { TSshCertificateAuthorityDALFactory } from "@app/ee/services/ssh/ssh-certificate-authority-dal";
|
||||||
@@ -65,15 +65,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TCreateSshCaDTO) => {
|
}: TCreateSshCaDTO) => {
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@@ -118,15 +118,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@@ -187,15 +187,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@@ -226,15 +226,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionActions.Delete,
|
||||||
ProjectPermissionSub.SshCertificateAuthorities
|
ProjectPermissionSub.SshCertificateAuthorities
|
||||||
@@ -268,15 +268,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sshCertificateTemplate.projectId,
|
projectId: sshCertificateTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificates
|
ProjectPermissionSub.SshCertificates
|
||||||
@@ -390,15 +390,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
sshCertificateTemplate.projectId,
|
projectId: sshCertificateTemplate.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSub.SshCertificates
|
ProjectPermissionSub.SshCertificates
|
||||||
@@ -488,15 +488,15 @@ export const sshCertificateAuthorityServiceFactory = ({
|
|||||||
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
const ca = await sshCertificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
if (!ca) throw new NotFoundError({ message: `SSH CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission, ForbidOnInvalidProjectType } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
ca.projectId,
|
projectId: ca.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SSH
|
||||||
|
});
|
||||||
|
|
||||||
ForbidOnInvalidProjectType(ProjectType.SSH);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSub.SshCertificateTemplates
|
ProjectPermissionSub.SshCertificateTemplates
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
@@ -27,13 +28,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
projectDAL
|
projectDAL
|
||||||
}: TTrustedIpServiceFactoryDep) => {
|
}: TTrustedIpServiceFactoryDep) => {
|
||||||
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
|
const listIpsByProjectId = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
const trustedIps = await trustedIpDAL.find({
|
const trustedIps = await trustedIpDAL.find({
|
||||||
projectId
|
projectId
|
||||||
@@ -51,13 +53,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
comment,
|
comment,
|
||||||
isActive
|
isActive
|
||||||
}: TCreateIpDTO) => {
|
}: TCreateIpDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
@@ -96,13 +99,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
comment,
|
comment,
|
||||||
trustedIpId
|
trustedIpId
|
||||||
}: TUpdateIpDTO) => {
|
}: TUpdateIpDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
@@ -141,13 +145,14 @@ export const trustedIpServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
trustedIpId
|
trustedIpId
|
||||||
}: TDeleteIpDTO) => {
|
}: TDeleteIpDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
);
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
|
@@ -23,6 +23,8 @@ export const KeyStorePrefixes = {
|
|||||||
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const,
|
`sync-integration-last-run-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
|
SecretSyncLock: (syncId: string) => `secret-sync-mutex-${syncId}` as const,
|
||||||
|
SecretSyncLastRunTimestamp: (syncId: string) => `secret-sync-last-run-${syncId}` as const,
|
||||||
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
IdentityAccessTokenStatusUpdate: (identityAccessTokenId: string) =>
|
||||||
`identity-access-token-status:${identityAccessTokenId}`,
|
`identity-access-token-status:${identityAccessTokenId}`,
|
||||||
ServiceTokenStatusUpdate: (serviceTokenId: string) => `service-token-status:${serviceTokenId}`
|
ServiceTokenStatusUpdate: (serviceTokenId: string) => `service-token-status:${serviceTokenId}`
|
||||||
@@ -30,6 +32,7 @@ export const KeyStorePrefixes = {
|
|||||||
|
|
||||||
export const KeyStoreTtls = {
|
export const KeyStoreTtls = {
|
||||||
SetSyncSecretIntegrationLastRunTimestampInSeconds: 60,
|
SetSyncSecretIntegrationLastRunTimestampInSeconds: 60,
|
||||||
|
SetSecretSyncLastRunTimestampInSeconds: 60,
|
||||||
AccessTokenStatusUpdateInSeconds: 120
|
AccessTokenStatusUpdateInSeconds: 120
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps";
|
import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps";
|
||||||
|
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
||||||
|
import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps";
|
||||||
|
|
||||||
export const GROUPS = {
|
export const GROUPS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
@@ -474,7 +476,7 @@ export const PROJECTS = {
|
|||||||
},
|
},
|
||||||
ADD_GROUP_TO_PROJECT: {
|
ADD_GROUP_TO_PROJECT: {
|
||||||
projectId: "The ID of the project to add the group to.",
|
projectId: "The ID of the project to add the group to.",
|
||||||
groupId: "The ID of the group to add to the project.",
|
groupIdOrName: "The ID or name of the group to add to the project.",
|
||||||
role: "The role for the group to assume in the project."
|
role: "The role for the group to assume in the project."
|
||||||
},
|
},
|
||||||
UPDATE_GROUP_IN_PROJECT: {
|
UPDATE_GROUP_IN_PROJECT: {
|
||||||
@@ -686,7 +688,9 @@ export const RAW_SECRETS = {
|
|||||||
environment: "The slug of the environment to list secrets from.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
secretPath: "The secret path to list secrets from.",
|
secretPath: "The secret path to list secrets from.",
|
||||||
includeImports: "Weather to include imported secrets or not.",
|
includeImports: "Weather to include imported secrets or not.",
|
||||||
tagSlugs: "The comma separated tag slugs to filter secrets."
|
tagSlugs: "The comma separated tag slugs to filter secrets.",
|
||||||
|
metadataFilter:
|
||||||
|
"The secret metadata key-value pairs to filter secrets by. When querying for multiple metadata pairs, the query is treated as an AND operation. Secret metadata format is key=value1,value=value2|key=value3,value=value4."
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
secretName: "The name of the secret to create.",
|
secretName: "The name of the secret to create.",
|
||||||
@@ -826,6 +830,8 @@ export const AUDIT_LOGS = {
|
|||||||
projectId:
|
projectId:
|
||||||
"Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.",
|
"Optionally filter logs by project ID. If not provided, logs from the entire organization will be returned.",
|
||||||
eventType: "The type of the event to export.",
|
eventType: "The type of the event to export.",
|
||||||
|
secretPath:
|
||||||
|
"The path of the secret to query audit logs for. Note that the projectId parameter must also be provided.",
|
||||||
userAgentType: "Choose which consuming application to export audit logs for.",
|
userAgentType: "Choose which consuming application to export audit logs for.",
|
||||||
eventMetadata:
|
eventMetadata:
|
||||||
"Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.",
|
"Filter by event metadata key-value pairs. Formatted as `key1=value1,key2=value2`, with comma-separation.",
|
||||||
@@ -1587,6 +1593,13 @@ export const KMS = {
|
|||||||
orderDirection: "The direction to order keys in.",
|
orderDirection: "The direction to order keys in.",
|
||||||
search: "The text string to filter key names by."
|
search: "The text string to filter key names by."
|
||||||
},
|
},
|
||||||
|
GET_KEY_BY_ID: {
|
||||||
|
keyId: "The ID of the KMS key to retrieve."
|
||||||
|
},
|
||||||
|
GET_KEY_BY_NAME: {
|
||||||
|
keyName: "The name of the KMS key to retrieve.",
|
||||||
|
projectId: "The ID of the project the key belongs to."
|
||||||
|
},
|
||||||
ENCRYPT: {
|
ENCRYPT: {
|
||||||
keyId: "The ID of the key to encrypt the data with.",
|
keyId: "The ID of the key to encrypt the data with.",
|
||||||
plaintext: "The plaintext to be encrypted (base64 encoded)."
|
plaintext: "The plaintext to be encrypted (base64 encoded)."
|
||||||
@@ -1643,6 +1656,98 @@ export const AppConnections = {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
DELETE: (app: AppConnection) => ({
|
DELETE: (app: AppConnection) => ({
|
||||||
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} connection to be deleted.`
|
connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to be deleted.`
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SecretSyncs = {
|
||||||
|
LIST: (destination?: SecretSync) => ({
|
||||||
|
projectId: `The ID of the project to list ${destination ? SECRET_SYNC_NAME_MAP[destination] : "Secret"} Syncs from.`
|
||||||
|
}),
|
||||||
|
GET_BY_ID: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to retrieve.`
|
||||||
|
}),
|
||||||
|
GET_BY_NAME: (destination: SecretSync) => ({
|
||||||
|
syncName: `The name of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to retrieve.`,
|
||||||
|
projectId: `The ID of the project the ${SECRET_SYNC_NAME_MAP[destination]} Sync is associated with.`
|
||||||
|
}),
|
||||||
|
CREATE: (destination: SecretSync) => {
|
||||||
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
|
return {
|
||||||
|
name: `The name of the ${destinationName} Sync to create. Must be slug-friendly.`,
|
||||||
|
description: `An optional description for the ${destinationName} Sync.`,
|
||||||
|
projectId: "The ID of the project to create the sync in.",
|
||||||
|
environment: `The slug of the project environment to sync secrets from.`,
|
||||||
|
secretPath: `The folder path to sync secrets from.`,
|
||||||
|
connectionId: `The ID of the ${
|
||||||
|
APP_CONNECTION_NAME_MAP[SECRET_SYNC_CONNECTION_MAP[destination]]
|
||||||
|
} Connection to use for syncing.`,
|
||||||
|
isAutoSyncEnabled: `Whether secrets should be automatically synced when changes occur at the source location or not.`,
|
||||||
|
syncOptions: "Optional parameters to modify how secrets are synced."
|
||||||
|
};
|
||||||
|
},
|
||||||
|
UPDATE: (destination: SecretSync) => {
|
||||||
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
|
return {
|
||||||
|
syncId: `The ID of the ${destinationName} Sync to be updated.`,
|
||||||
|
connectionId: `The updated ID of the ${
|
||||||
|
APP_CONNECTION_NAME_MAP[SECRET_SYNC_CONNECTION_MAP[destination]]
|
||||||
|
} Connection to use for syncing.`,
|
||||||
|
name: `The updated name of the ${destinationName} Sync. Must be slug-friendly.`,
|
||||||
|
environment: `The updated slug of the project environment to sync secrets from.`,
|
||||||
|
secretPath: `The updated folder path to sync secrets from.`,
|
||||||
|
description: `The updated description of the ${destinationName} Sync.`,
|
||||||
|
isAutoSyncEnabled: `Whether secrets should be automatically synced when changes occur at the source location or not.`,
|
||||||
|
syncOptions: "Optional parameters to modify how secrets are synced."
|
||||||
|
};
|
||||||
|
},
|
||||||
|
DELETE: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to be deleted.`,
|
||||||
|
removeSecrets: `Whether previously synced secrets should be removed prior to deletion.`
|
||||||
|
}),
|
||||||
|
SYNC_SECRETS: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to trigger a sync for.`
|
||||||
|
}),
|
||||||
|
IMPORT_SECRETS: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to trigger importing secrets for.`,
|
||||||
|
importBehavior: `Specify whether Infisical should prioritize secret values from Infisical or ${SECRET_SYNC_NAME_MAP[destination]}.`
|
||||||
|
}),
|
||||||
|
REMOVE_SECRETS: (destination: SecretSync) => ({
|
||||||
|
syncId: `The ID of the ${SECRET_SYNC_NAME_MAP[destination]} Sync to trigger removing secrets for.`
|
||||||
|
}),
|
||||||
|
SYNC_OPTIONS: (destination: SecretSync) => {
|
||||||
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
|
return {
|
||||||
|
INITIAL_SYNC_BEHAVIOR: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
|
||||||
|
PREPEND_PREFIX: `Optionally prepend a prefix to your secrets' keys when syncing to ${destinationName}.`,
|
||||||
|
APPEND_SUFFIX: `Optionally append a suffix to your secrets' keys when syncing to ${destinationName}.`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
DESTINATION_CONFIG: {
|
||||||
|
AWS_PARAMETER_STORE: {
|
||||||
|
REGION: "The AWS region to sync secrets to.",
|
||||||
|
PATH: "The Parameter Store path to sync secrets to."
|
||||||
|
},
|
||||||
|
AWS_SECRETS_MANAGER: {
|
||||||
|
REGION: "The AWS region to sync secrets to.",
|
||||||
|
MAPPING_BEHAVIOR:
|
||||||
|
"How secrets from Infisical should be mapped to AWS Secrets Manager; one-to-one or many-to-one.",
|
||||||
|
SECRET_NAME: "The secret name in AWS Secrets Manager to sync to when using mapping behavior many-to-one."
|
||||||
|
},
|
||||||
|
GITHUB: {
|
||||||
|
ORG: "The name of the GitHub organization.",
|
||||||
|
OWNER: "The name of the GitHub account owner of the repository.",
|
||||||
|
REPO: "The name of the GitHub repository.",
|
||||||
|
ENV: "The name of the GitHub environment."
|
||||||
|
},
|
||||||
|
AZURE_KEY_VAULT: {
|
||||||
|
VAULT_BASE_URL:
|
||||||
|
"The base URL of the Azure Key Vault to sync secrets to. Example: https://example.vault.azure.net/"
|
||||||
|
},
|
||||||
|
AZURE_APP_CONFIGURATION: {
|
||||||
|
CONFIGURATION_URL:
|
||||||
|
"The URL of the Azure App Configuration to sync secrets to. Example: https://example.azconfig.io/",
|
||||||
|
LABEL: "An optional label to assign to secrets created in Azure App Configuration."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@@ -199,7 +199,36 @@ const envSchema = z
|
|||||||
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_APP_CLIENT_SECRET: zpStr(z.string().optional()),
|
||||||
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_APP_PRIVATE_KEY: zpStr(z.string().optional()),
|
||||||
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
|
INF_APP_CONNECTION_GITHUB_APP_SLUG: zpStr(z.string().optional()),
|
||||||
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional())
|
INF_APP_CONNECTION_GITHUB_APP_ID: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
// gcp app
|
||||||
|
INF_APP_CONNECTION_GCP_SERVICE_ACCOUNT_CREDENTIAL: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
// azure app
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_ID: zpStr(z.string().optional()),
|
||||||
|
INF_APP_CONNECTION_AZURE_CLIENT_SECRET: zpStr(z.string().optional()),
|
||||||
|
|
||||||
|
/* CORS ----------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS: zpStr(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return undefined;
|
||||||
|
return JSON.parse(val) as string[];
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
CORS_ALLOWED_HEADERS: zpStr(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => {
|
||||||
|
if (!val) return undefined;
|
||||||
|
return JSON.parse(val) as string[];
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
// To ensure that basic encryption is always possible.
|
// To ensure that basic encryption is always possible.
|
||||||
.refine(
|
.refine(
|
||||||
|
@@ -116,7 +116,7 @@ export const decryptAsymmetric = ({ ciphertext, nonce, publicKey, privateKey }:
|
|||||||
|
|
||||||
export const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64");
|
export const generateSymmetricKey = (size = 32) => crypto.randomBytes(size).toString("base64");
|
||||||
|
|
||||||
export const generateHash = (value: string) => crypto.createHash("sha256").update(value).digest("hex");
|
export const generateHash = (value: string | Buffer) => crypto.createHash("sha256").update(value).digest("hex");
|
||||||
|
|
||||||
export const generateAsymmetricKeyPair = () => {
|
export const generateAsymmetricKeyPair = () => {
|
||||||
const pair = nacl.box.keyPair();
|
const pair = nacl.box.keyPair();
|
||||||
|
4
backend/src/lib/error-codes/database.ts
Normal file
4
backend/src/lib/error-codes/database.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum DatabaseErrorCode {
|
||||||
|
ForeignKeyViolation = "23503",
|
||||||
|
UniqueViolation = "23505"
|
||||||
|
}
|
1
backend/src/lib/error-codes/index.ts
Normal file
1
backend/src/lib/error-codes/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./database";
|
@@ -7,6 +7,7 @@ import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
|||||||
|
|
||||||
export * from "./connection";
|
export * from "./connection";
|
||||||
export * from "./join";
|
export * from "./join";
|
||||||
|
export * from "./prependTableNameToFindFilter";
|
||||||
export * from "./select";
|
export * from "./select";
|
||||||
|
|
||||||
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
||||||
|
13
backend/src/lib/knex/prependTableNameToFindFilter.ts
Normal file
13
backend/src/lib/knex/prependTableNameToFindFilter.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { buildFindFilter } from "@app/lib/knex/index";
|
||||||
|
|
||||||
|
type TFindFilterParameters = Parameters<typeof buildFindFilter<object>>[0];
|
||||||
|
|
||||||
|
export const prependTableNameToFindFilter = (tableName: TableName, filterObj: object): TFindFilterParameters =>
|
||||||
|
Object.fromEntries(
|
||||||
|
Object.entries(filterObj).map(([key, value]) =>
|
||||||
|
key.startsWith("$")
|
||||||
|
? [key, prependTableNameToFindFilter(tableName, value as object)]
|
||||||
|
: [`${tableName}.${key}`, value]
|
||||||
|
)
|
||||||
|
);
|
@@ -1,3 +1,4 @@
|
|||||||
export { isDisposableEmail } from "./validate-email";
|
export { isDisposableEmail } from "./validate-email";
|
||||||
export { isValidFolderName, isValidSecretPath } from "./validate-folder-name";
|
export { isValidFolderName, isValidSecretPath } from "./validate-folder-name";
|
||||||
export { blockLocalAndPrivateIpAddresses } from "./validate-url";
|
export { blockLocalAndPrivateIpAddresses } from "./validate-url";
|
||||||
|
export { isUuidV4 } from "./validate-uuid";
|
||||||
|
3
backend/src/lib/validator/validate-uuid.ts
Normal file
3
backend/src/lib/validator/validate-uuid.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const isUuidV4 = (uuid: string) => z.string().uuid().safeParse(uuid).success;
|
@@ -15,6 +15,12 @@ import {
|
|||||||
TIntegrationSyncPayload,
|
TIntegrationSyncPayload,
|
||||||
TSyncSecretsDTO
|
TSyncSecretsDTO
|
||||||
} from "@app/services/secret/secret-types";
|
} from "@app/services/secret/secret-types";
|
||||||
|
import {
|
||||||
|
TQueueSecretSyncImportSecretsByIdDTO,
|
||||||
|
TQueueSecretSyncRemoveSecretsByIdDTO,
|
||||||
|
TQueueSecretSyncSyncSecretsByIdDTO,
|
||||||
|
TQueueSendSecretSyncActionFailedNotificationsDTO
|
||||||
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
|
|
||||||
export enum QueueName {
|
export enum QueueName {
|
||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
@@ -36,7 +42,8 @@ export enum QueueName {
|
|||||||
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
SecretSync = "secret-sync", // parent queue to push integration sync, webhook, and secret replication
|
||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
AccessTokenStatusUpdate = "access-token-status-update",
|
AccessTokenStatusUpdate = "access-token-status-update",
|
||||||
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source",
|
||||||
|
AppConnectionSecretSync = "app-connection-secret-sync"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueJobs {
|
export enum QueueJobs {
|
||||||
@@ -61,7 +68,11 @@ export enum QueueJobs {
|
|||||||
ProjectV3Migration = "project-v3-migration",
|
ProjectV3Migration = "project-v3-migration",
|
||||||
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
IdentityAccessTokenStatusUpdate = "identity-access-token-status-update",
|
||||||
ServiceTokenStatusUpdate = "service-token-status-update",
|
ServiceTokenStatusUpdate = "service-token-status-update",
|
||||||
ImportSecretsFromExternalSource = "import-secrets-from-external-source"
|
ImportSecretsFromExternalSource = "import-secrets-from-external-source",
|
||||||
|
SecretSyncSyncSecrets = "secret-sync-sync-secrets",
|
||||||
|
SecretSyncImportSecrets = "secret-sync-import-secrets",
|
||||||
|
SecretSyncRemoveSecrets = "secret-sync-remove-secrets",
|
||||||
|
SecretSyncSendActionFailedNotifications = "secret-sync-send-action-failed-notifications"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TQueueJobTypes = {
|
export type TQueueJobTypes = {
|
||||||
@@ -184,6 +195,23 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
[QueueName.AppConnectionSecretSync]:
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncSyncSecrets;
|
||||||
|
payload: TQueueSecretSyncSyncSecretsByIdDTO;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncImportSecrets;
|
||||||
|
payload: TQueueSecretSyncImportSecretsByIdDTO;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncRemoveSecrets;
|
||||||
|
payload: TQueueSecretSyncRemoveSecretsByIdDTO;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
name: QueueJobs.SecretSyncSendActionFailedNotifications;
|
||||||
|
payload: TQueueSendSecretSyncActionFailedNotificationsDTO;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
export type TQueueServiceFactory = ReturnType<typeof queueServiceFactory>;
|
||||||
|
@@ -87,7 +87,16 @@ export const main = async ({ db, hsmModule, auditLogDb, smtp, logger, queue, key
|
|||||||
|
|
||||||
await server.register<FastifyCorsOptions>(cors, {
|
await server.register<FastifyCorsOptions>(cors, {
|
||||||
credentials: true,
|
credentials: true,
|
||||||
origin: appCfg.SITE_URL || true
|
...(appCfg.CORS_ALLOWED_ORIGINS?.length
|
||||||
|
? {
|
||||||
|
origin: [...appCfg.CORS_ALLOWED_ORIGINS, ...(appCfg.SITE_URL ? [appCfg.SITE_URL] : [])]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
origin: appCfg.SITE_URL || true
|
||||||
|
}),
|
||||||
|
...(appCfg.CORS_ALLOWED_HEADERS?.length && {
|
||||||
|
allowedHeaders: appCfg.CORS_ALLOWED_HEADERS
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.register(addErrorsToResponseSchemas);
|
await server.register(addErrorsToResponseSchemas);
|
||||||
|
@@ -32,13 +32,21 @@ export const getUserAgentType = (userAgent: string | undefined) => {
|
|||||||
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
|
export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
|
||||||
server.decorateRequest("auditLogInfo", null);
|
server.decorateRequest("auditLogInfo", null);
|
||||||
server.addHook("onRequest", async (req) => {
|
server.addHook("onRequest", async (req) => {
|
||||||
if (!req.auth) return;
|
|
||||||
const userAgent = req.headers["user-agent"] ?? "";
|
const userAgent = req.headers["user-agent"] ?? "";
|
||||||
const payload = {
|
const payload = {
|
||||||
ipAddress: req.realIp,
|
ipAddress: req.realIp,
|
||||||
userAgent,
|
userAgent,
|
||||||
userAgentType: getUserAgentType(userAgent)
|
userAgentType: getUserAgentType(userAgent)
|
||||||
} as typeof req.auditLogInfo;
|
} as typeof req.auditLogInfo;
|
||||||
|
|
||||||
|
if (!req.auth) {
|
||||||
|
payload.actor = {
|
||||||
|
type: ActorType.UNKNOWN_USER,
|
||||||
|
metadata: {}
|
||||||
|
};
|
||||||
|
req.auditLogInfo = payload;
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (req.auth.actor === ActorType.USER) {
|
if (req.auth.actor === ActorType.USER) {
|
||||||
payload.actor = {
|
payload.actor = {
|
||||||
type: ActorType.USER,
|
type: ActorType.USER,
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import type { EmitterWebhookEventName } from "@octokit/webhooks/dist-types/types";
|
||||||
import { PushEvent } from "@octokit/webhooks-types";
|
import { PushEvent } from "@octokit/webhooks-types";
|
||||||
import { Probot } from "probot";
|
import { Probot } from "probot";
|
||||||
import SmeeClient from "smee-client";
|
import SmeeClient from "smee-client";
|
||||||
@@ -54,14 +55,14 @@ export const registerSecretScannerGhApp = async (server: FastifyZodProvider) =>
|
|||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
},
|
},
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
const eventName = req.headers["x-github-event"];
|
const eventName = req.headers["x-github-event"] as EmitterWebhookEventName;
|
||||||
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
|
const signatureSHA256 = req.headers["x-hub-signature-256"] as string;
|
||||||
const id = req.headers["x-github-delivery"] as string;
|
const id = req.headers["x-github-delivery"] as string;
|
||||||
|
|
||||||
await probot.webhooks.verifyAndReceive({
|
await probot.webhooks.verifyAndReceive({
|
||||||
id,
|
id,
|
||||||
// @ts-expect-error type
|
|
||||||
name: eventName,
|
name: eventName,
|
||||||
payload: req.body as string,
|
payload: JSON.stringify(req.body),
|
||||||
signature: signatureSHA256
|
signature: signatureSHA256
|
||||||
});
|
});
|
||||||
void res.send("ok");
|
void res.send("ok");
|
||||||
|
@@ -196,6 +196,9 @@ import { secretImportDALFactory } from "@app/services/secret-import/secret-impor
|
|||||||
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
||||||
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
||||||
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
|
import { secretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
||||||
|
import { secretSyncQueueFactory } from "@app/services/secret-sync/secret-sync-queue";
|
||||||
|
import { secretSyncServiceFactory } from "@app/services/secret-sync/secret-sync-service";
|
||||||
import { secretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
import { secretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal";
|
||||||
import { secretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
import { secretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
||||||
import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal";
|
import { secretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
@@ -318,6 +321,7 @@ export const registerRoutes = async (
|
|||||||
const trustedIpDAL = trustedIpDALFactory(db);
|
const trustedIpDAL = trustedIpDALFactory(db);
|
||||||
const telemetryDAL = telemetryDALFactory(db);
|
const telemetryDAL = telemetryDALFactory(db);
|
||||||
const appConnectionDAL = appConnectionDALFactory(db);
|
const appConnectionDAL = appConnectionDALFactory(db);
|
||||||
|
const secretSyncDAL = secretSyncDALFactory(db, folderDAL);
|
||||||
|
|
||||||
// ee db layer ops
|
// ee db layer ops
|
||||||
const permissionDAL = permissionDALFactory(db);
|
const permissionDAL = permissionDALFactory(db);
|
||||||
@@ -463,7 +467,8 @@ export const registerRoutes = async (
|
|||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService,
|
||||||
|
oidcConfigDAL
|
||||||
});
|
});
|
||||||
const groupProjectService = groupProjectServiceFactory({
|
const groupProjectService = groupProjectServiceFactory({
|
||||||
groupDAL,
|
groupDAL,
|
||||||
@@ -608,6 +613,7 @@ export const registerRoutes = async (
|
|||||||
});
|
});
|
||||||
const superAdminService = superAdminServiceFactory({
|
const superAdminService = superAdminServiceFactory({
|
||||||
userDAL,
|
userDAL,
|
||||||
|
userAliasDAL,
|
||||||
authService: loginService,
|
authService: loginService,
|
||||||
serverCfgDAL: superAdminDAL,
|
serverCfgDAL: superAdminDAL,
|
||||||
kmsRootConfigDAL,
|
kmsRootConfigDAL,
|
||||||
@@ -823,6 +829,30 @@ export const registerRoutes = async (
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const secretSyncQueue = secretSyncQueueFactory({
|
||||||
|
queueService,
|
||||||
|
secretSyncDAL,
|
||||||
|
folderDAL,
|
||||||
|
secretImportDAL,
|
||||||
|
secretV2BridgeDAL,
|
||||||
|
kmsService,
|
||||||
|
keyStore,
|
||||||
|
auditLogService,
|
||||||
|
smtpService,
|
||||||
|
projectDAL,
|
||||||
|
projectMembershipDAL,
|
||||||
|
projectBotDAL,
|
||||||
|
secretDAL,
|
||||||
|
secretBlindIndexDAL,
|
||||||
|
secretVersionDAL,
|
||||||
|
secretTagDAL,
|
||||||
|
secretVersionTagDAL,
|
||||||
|
secretVersionV2BridgeDAL,
|
||||||
|
secretVersionTagV2BridgeDAL,
|
||||||
|
resourceMetadataDAL,
|
||||||
|
appConnectionDAL
|
||||||
|
});
|
||||||
|
|
||||||
const secretQueueService = secretQueueFactory({
|
const secretQueueService = secretQueueFactory({
|
||||||
keyStore,
|
keyStore,
|
||||||
queueService,
|
queueService,
|
||||||
@@ -857,7 +887,8 @@ export const registerRoutes = async (
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
orgService,
|
orgService,
|
||||||
resourceMetadataDAL
|
resourceMetadataDAL,
|
||||||
|
secretSyncQueue
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectService = projectServiceFactory({
|
const projectService = projectServiceFactory({
|
||||||
@@ -894,7 +925,8 @@ export const registerRoutes = async (
|
|||||||
certificateTemplateDAL,
|
certificateTemplateDAL,
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
slackIntegrationDAL,
|
slackIntegrationDAL,
|
||||||
projectTemplateService
|
projectTemplateService,
|
||||||
|
groupProjectDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
@@ -1307,7 +1339,14 @@ export const registerRoutes = async (
|
|||||||
smtpService,
|
smtpService,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
oidcConfigDAL
|
oidcConfigDAL,
|
||||||
|
projectBotDAL,
|
||||||
|
projectKeyDAL,
|
||||||
|
projectDAL,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupProjectDAL,
|
||||||
|
groupDAL,
|
||||||
|
auditLogService
|
||||||
});
|
});
|
||||||
|
|
||||||
const userEngagementService = userEngagementServiceFactory({
|
const userEngagementService = userEngagementServiceFactory({
|
||||||
@@ -1367,8 +1406,17 @@ export const registerRoutes = async (
|
|||||||
const appConnectionService = appConnectionServiceFactory({
|
const appConnectionService = appConnectionServiceFactory({
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
kmsService,
|
kmsService
|
||||||
licenseService
|
});
|
||||||
|
|
||||||
|
const secretSyncService = secretSyncServiceFactory({
|
||||||
|
secretSyncDAL,
|
||||||
|
permissionService,
|
||||||
|
appConnectionService,
|
||||||
|
folderDAL,
|
||||||
|
secretSyncQueue,
|
||||||
|
projectBotService,
|
||||||
|
keyStore
|
||||||
});
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
@@ -1468,7 +1516,8 @@ export const registerRoutes = async (
|
|||||||
externalGroupOrgRoleMapping: externalGroupOrgRoleMappingService,
|
externalGroupOrgRoleMapping: externalGroupOrgRoleMappingService,
|
||||||
projectTemplate: projectTemplateService,
|
projectTemplate: projectTemplateService,
|
||||||
totp: totpService,
|
totp: totpService,
|
||||||
appConnection: appConnectionService
|
appConnection: appConnectionService,
|
||||||
|
secretSync: secretSyncService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
@@ -110,7 +110,6 @@ export const secretRawSchema = z.object({
|
|||||||
secretReminderNote: z.string().nullable().optional(),
|
secretReminderNote: z.string().nullable().optional(),
|
||||||
secretReminderRepeatDays: z.number().nullable().optional(),
|
secretReminderRepeatDays: z.number().nullable().optional(),
|
||||||
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
||||||
metadata: z.unknown().nullable().optional(),
|
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
app,
|
app,
|
||||||
createSchema,
|
createSchema,
|
||||||
updateSchema,
|
updateSchema,
|
||||||
responseSchema
|
sanitizedResponseSchema
|
||||||
}: {
|
}: {
|
||||||
app: AppConnection;
|
app: AppConnection;
|
||||||
server: FastifyZodProvider;
|
server: FastifyZodProvider;
|
||||||
@@ -26,7 +26,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
description?: string | null;
|
description?: string | null;
|
||||||
}>;
|
}>;
|
||||||
updateSchema: z.ZodType<{ name?: string; credentials?: I["credentials"]; description?: string | null }>;
|
updateSchema: z.ZodType<{ name?: string; credentials?: I["credentials"]; description?: string | null }>;
|
||||||
responseSchema: z.ZodTypeAny;
|
sanitizedResponseSchema: z.ZodTypeAny;
|
||||||
}) => {
|
}) => {
|
||||||
const appName = APP_CONNECTION_NAME_MAP[app];
|
const appName = APP_CONNECTION_NAME_MAP[app];
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
schema: {
|
schema: {
|
||||||
description: `List the ${appName} Connections for the current organization.`,
|
description: `List the ${appName} Connections for the current organization.`,
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ appConnections: responseSchema.array() })
|
200: z.object({ appConnections: sanitizedResponseSchema.array() })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -63,6 +63,50 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/available",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: `List the ${appName} Connections the current user has permission to establish connections with.`,
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
appConnections: z
|
||||||
|
.object({
|
||||||
|
app: z.literal(app),
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string().uuid()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const appConnections = await server.services.appConnection.listAvailableAppConnectionsForUser(
|
||||||
|
app,
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_AVAILABLE_APP_CONNECTIONS_DETAILS,
|
||||||
|
metadata: {
|
||||||
|
app,
|
||||||
|
count: appConnections.length,
|
||||||
|
connectionIds: appConnections.map((connection) => connection.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { appConnections };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:connectionId",
|
url: "/:connectionId",
|
||||||
@@ -75,7 +119,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
connectionId: z.string().uuid().describe(AppConnections.GET_BY_ID(app).connectionId)
|
connectionId: z.string().uuid().describe(AppConnections.GET_BY_ID(app).connectionId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ appConnection: responseSchema })
|
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -105,7 +149,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: `/name/:connectionName`,
|
url: `/connection-name/:connectionName`,
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
@@ -114,11 +158,12 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
connectionName: z
|
connectionName: z
|
||||||
.string()
|
.string()
|
||||||
.min(0, "Connection name required")
|
.trim()
|
||||||
|
.min(1, "Connection name required")
|
||||||
.describe(AppConnections.GET_BY_NAME(app).connectionName)
|
.describe(AppConnections.GET_BY_NAME(app).connectionName)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ appConnection: responseSchema })
|
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -158,7 +203,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
} ${appName} Connection for the current organization.`,
|
} ${appName} Connection for the current organization.`,
|
||||||
body: createSchema,
|
body: createSchema,
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ appConnection: responseSchema })
|
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -168,7 +213,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
const appConnection = (await server.services.appConnection.createAppConnection(
|
const appConnection = (await server.services.appConnection.createAppConnection(
|
||||||
{ name, method, app, credentials, description },
|
{ name, method, app, credentials, description },
|
||||||
req.permission
|
req.permission
|
||||||
)) as TAppConnection;
|
)) as T;
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
@@ -201,7 +246,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
}),
|
}),
|
||||||
body: updateSchema,
|
body: updateSchema,
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ appConnection: responseSchema })
|
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
@@ -244,7 +289,7 @@ export const registerAppConnectionEndpoints = <T extends TAppConnection, I exten
|
|||||||
connectionId: z.string().uuid().describe(AppConnections.DELETE(app).connectionId)
|
connectionId: z.string().uuid().describe(AppConnections.DELETE(app).connectionId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({ appConnection: responseSchema })
|
200: z.object({ appConnection: sanitizedResponseSchema })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
@@ -4,18 +4,33 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
|||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AwsConnectionListItemSchema, SanitizedAwsConnectionSchema } from "@app/services/app-connection/aws";
|
import { AwsConnectionListItemSchema, SanitizedAwsConnectionSchema } from "@app/services/app-connection/aws";
|
||||||
|
import {
|
||||||
|
AzureAppConfigurationConnectionListItemSchema,
|
||||||
|
SanitizedAzureAppConfigurationConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-app-configuration";
|
||||||
|
import {
|
||||||
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
|
SanitizedAzureKeyVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-key-vault";
|
||||||
|
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
||||||
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
import { GitHubConnectionListItemSchema, SanitizedGitHubConnectionSchema } from "@app/services/app-connection/github";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
// can't use discriminated due to multiple schemas for certain apps
|
// can't use discriminated due to multiple schemas for certain apps
|
||||||
const SanitizedAppConnectionSchema = z.union([
|
const SanitizedAppConnectionSchema = z.union([
|
||||||
...SanitizedAwsConnectionSchema.options,
|
...SanitizedAwsConnectionSchema.options,
|
||||||
...SanitizedGitHubConnectionSchema.options
|
...SanitizedGitHubConnectionSchema.options,
|
||||||
|
...SanitizedGcpConnectionSchema.options,
|
||||||
|
...SanitizedAzureKeyVaultConnectionSchema.options,
|
||||||
|
...SanitizedAzureAppConfigurationConnectionSchema.options
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
AwsConnectionListItemSchema,
|
AwsConnectionListItemSchema,
|
||||||
GitHubConnectionListItemSchema
|
GitHubConnectionListItemSchema,
|
||||||
|
GcpConnectionListItemSchema,
|
||||||
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
|
AzureAppConfigurationConnectionListItemSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
|
||||||
CreateGitHubConnectionSchema,
|
|
||||||
SanitizedGitHubConnectionSchema,
|
|
||||||
UpdateGitHubConnectionSchema
|
|
||||||
} from "@app/services/app-connection/github";
|
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
|
||||||
|
|
||||||
export const registerGitHubConnectionRouter = async (server: FastifyZodProvider) =>
|
|
||||||
registerAppConnectionEndpoints({
|
|
||||||
app: AppConnection.GitHub,
|
|
||||||
server,
|
|
||||||
responseSchema: SanitizedGitHubConnectionSchema,
|
|
||||||
createSchema: CreateGitHubConnectionSchema,
|
|
||||||
updateSchema: UpdateGitHubConnectionSchema
|
|
||||||
});
|
|
@@ -1,8 +0,0 @@
|
|||||||
import { registerAwsConnectionRouter } from "@app/server/routes/v1/app-connection-routers/apps/aws-connection-router";
|
|
||||||
import { registerGitHubConnectionRouter } from "@app/server/routes/v1/app-connection-routers/apps/github-connection-router";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
|
|
||||||
export const APP_CONNECTION_REGISTER_MAP: Record<AppConnection, (server: FastifyZodProvider) => Promise<void>> = {
|
|
||||||
[AppConnection.AWS]: registerAwsConnectionRouter,
|
|
||||||
[AppConnection.GitHub]: registerGitHubConnectionRouter
|
|
||||||
};
|
|
@@ -11,7 +11,7 @@ export const registerAwsConnectionRouter = async (server: FastifyZodProvider) =>
|
|||||||
registerAppConnectionEndpoints({
|
registerAppConnectionEndpoints({
|
||||||
app: AppConnection.AWS,
|
app: AppConnection.AWS,
|
||||||
server,
|
server,
|
||||||
responseSchema: SanitizedAwsConnectionSchema,
|
sanitizedResponseSchema: SanitizedAwsConnectionSchema,
|
||||||
createSchema: CreateAwsConnectionSchema,
|
createSchema: CreateAwsConnectionSchema,
|
||||||
updateSchema: UpdateAwsConnectionSchema
|
updateSchema: UpdateAwsConnectionSchema
|
||||||
});
|
});
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateAzureAppConfigurationConnectionSchema,
|
||||||
|
SanitizedAzureAppConfigurationConnectionSchema,
|
||||||
|
UpdateAzureAppConfigurationConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-app-configuration";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureAppConfigurationConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.AzureAppConfiguration,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedAzureAppConfigurationConnectionSchema,
|
||||||
|
createSchema: CreateAzureAppConfigurationConnectionSchema,
|
||||||
|
updateSchema: UpdateAzureAppConfigurationConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
@@ -0,0 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateAzureKeyVaultConnectionSchema,
|
||||||
|
SanitizedAzureKeyVaultConnectionSchema,
|
||||||
|
UpdateAzureKeyVaultConnectionSchema
|
||||||
|
} from "@app/services/app-connection/azure-key-vault";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerAzureKeyVaultConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.AzureKeyVault,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedAzureKeyVaultConnectionSchema,
|
||||||
|
createSchema: CreateAzureKeyVaultConnectionSchema,
|
||||||
|
updateSchema: UpdateAzureKeyVaultConnectionSchema
|
||||||
|
});
|
||||||
|
};
|
@@ -0,0 +1,48 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateGcpConnectionSchema,
|
||||||
|
SanitizedGcpConnectionSchema,
|
||||||
|
UpdateGcpConnectionSchema
|
||||||
|
} from "@app/services/app-connection/gcp";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerGcpConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.GCP,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedGcpConnectionSchema,
|
||||||
|
createSchema: CreateGcpConnectionSchema,
|
||||||
|
updateSchema: UpdateGcpConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The below endpoints are not exposed and for Infisical App use
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/secret-manager-projects`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({ id: z.string(), name: z.string() }).array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const projects = await server.services.appConnection.gcp.listSecretManagerProjects(connectionId, req.permission);
|
||||||
|
|
||||||
|
return projects;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -0,0 +1,117 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
import {
|
||||||
|
CreateGitHubConnectionSchema,
|
||||||
|
SanitizedGitHubConnectionSchema,
|
||||||
|
UpdateGitHubConnectionSchema
|
||||||
|
} from "@app/services/app-connection/github";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
||||||
|
|
||||||
|
export const registerGitHubConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
registerAppConnectionEndpoints({
|
||||||
|
app: AppConnection.GitHub,
|
||||||
|
server,
|
||||||
|
sanitizedResponseSchema: SanitizedGitHubConnectionSchema,
|
||||||
|
createSchema: CreateGitHubConnectionSchema,
|
||||||
|
updateSchema: UpdateGitHubConnectionSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
// The below endpoints are not exposed and for Infisical App use
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/repositories`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
repositories: z
|
||||||
|
.object({ id: z.number(), name: z.string(), owner: z.object({ login: z.string(), id: z.number() }) })
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const repositories = await server.services.appConnection.github.listRepositories(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { repositories };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/organizations`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organizations: z.object({ id: z.number(), login: z.string() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
|
||||||
|
const organizations = await server.services.appConnection.github.listOrganizations(connectionId, req.permission);
|
||||||
|
|
||||||
|
return { organizations };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: `/:connectionId/environments`,
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
connectionId: z.string().uuid()
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
repo: z.string().min(1, "Repository name is required"),
|
||||||
|
owner: z.string().min(1, "Repository owner name is required")
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
environments: z.object({ id: z.number(), name: z.string() }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const { connectionId } = req.params;
|
||||||
|
const { repo, owner } = req.query;
|
||||||
|
|
||||||
|
const environments = await server.services.appConnection.github.listEnvironments(
|
||||||
|
{
|
||||||
|
connectionId,
|
||||||
|
repo,
|
||||||
|
owner
|
||||||
|
},
|
||||||
|
req.permission
|
||||||
|
);
|
||||||
|
|
||||||
|
return { environments };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -1,2 +1,18 @@
|
|||||||
|
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||||
|
|
||||||
|
import { registerAwsConnectionRouter } from "./aws-connection-router";
|
||||||
|
import { registerAzureAppConfigurationConnectionRouter } from "./azure-app-configuration-connection-router";
|
||||||
|
import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connection-router";
|
||||||
|
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
||||||
|
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
||||||
|
|
||||||
export * from "./app-connection-router";
|
export * from "./app-connection-router";
|
||||||
export * from "./apps";
|
|
||||||
|
export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server: FastifyZodProvider) => Promise<void>> =
|
||||||
|
{
|
||||||
|
[AppConnection.AWS]: registerAwsConnectionRouter,
|
||||||
|
[AppConnection.GitHub]: registerGitHubConnectionRouter,
|
||||||
|
[AppConnection.GCP]: registerGcpConnectionRouter,
|
||||||
|
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
||||||
|
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter
|
||||||
|
};
|
||||||
|
@@ -15,6 +15,10 @@ import { CmekOrderBy } from "@app/services/cmek/cmek-types";
|
|||||||
const keyNameSchema = slugSchema({ min: 1, max: 32, field: "Name" });
|
const keyNameSchema = slugSchema({ min: 1, max: 32, field: "Name" });
|
||||||
const keyDescriptionSchema = z.string().trim().max(500).optional();
|
const keyDescriptionSchema = z.string().trim().max(500).optional();
|
||||||
|
|
||||||
|
const CmekSchema = KmsKeysSchema.merge(InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true })).omit({
|
||||||
|
isReserved: true
|
||||||
|
});
|
||||||
|
|
||||||
const base64Schema = z.string().superRefine((val, ctx) => {
|
const base64Schema = z.string().superRefine((val, ctx) => {
|
||||||
if (!isBase64(val)) {
|
if (!isBase64(val)) {
|
||||||
ctx.addIssue({
|
ctx.addIssue({
|
||||||
@@ -53,7 +57,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
key: KmsKeysSchema
|
key: CmekSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -106,7 +110,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
key: KmsKeysSchema
|
key: CmekSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -150,7 +154,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
key: KmsKeysSchema
|
key: CmekSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -201,7 +205,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
keys: KmsKeysSchema.merge(InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true })).array(),
|
keys: CmekSchema.array(),
|
||||||
totalCount: z.number()
|
totalCount: z.number()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -230,6 +234,92 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/keys/:keyId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get KMS key by ID",
|
||||||
|
params: z.object({
|
||||||
|
keyId: z.string().uuid().describe(KMS.GET_KEY_BY_ID.keyId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
key: CmekSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
params: { keyId },
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const key = await server.services.cmek.findCmekById(keyId, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: key.projectId!,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_CMEK,
|
||||||
|
metadata: {
|
||||||
|
keyId: key.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { key };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/keys/key-name/:keyName",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get KMS key by Name",
|
||||||
|
params: z.object({
|
||||||
|
keyName: slugSchema({ field: "Key name" }).describe(KMS.GET_KEY_BY_NAME.keyName)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string().min(1, "Project ID is required").describe(KMS.GET_KEY_BY_NAME.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
key: CmekSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
params: { keyName },
|
||||||
|
query: { projectId },
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const key = await server.services.cmek.findCmekByName(keyName, projectId, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: key.projectId!,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_CMEK,
|
||||||
|
metadata: {
|
||||||
|
keyId: key.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { key };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// encrypt data
|
// encrypt data
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
|
import { ActionProjectType, SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import {
|
import {
|
||||||
ProjectPermissionDynamicSecretActions,
|
ProjectPermissionDynamicSecretActions,
|
||||||
@@ -220,13 +220,14 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
totalCount: totalFolderCount ?? 0
|
totalCount: totalFolderCount ?? 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const { permission } = await server.services.permission.getProjectPermission(
|
const { permission } = await server.services.permission.getProjectPermission({
|
||||||
req.permission.type,
|
actor: req.permission.type,
|
||||||
req.permission.id,
|
actorId: req.permission.id,
|
||||||
projectId,
|
projectId,
|
||||||
req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
req.permission.orgId
|
actorOrgId: req.permission.orgId,
|
||||||
);
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
|
});
|
||||||
|
|
||||||
const allowedDynamicSecretEnvironments = // filter envs user has access to
|
const allowedDynamicSecretEnvironments = // filter envs user has access to
|
||||||
environments.filter((environment) =>
|
environments.filter((environment) =>
|
||||||
|
@@ -79,44 +79,44 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(AWS_AUTH.ATTACH.identityId)
|
identityId: z.string().trim().describe(AWS_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
stsEndpoint: z
|
.object({
|
||||||
.string()
|
stsEndpoint: z
|
||||||
.trim()
|
.string()
|
||||||
.min(1)
|
.trim()
|
||||||
.default("https://sts.amazonaws.com/")
|
.min(1)
|
||||||
.describe(AWS_AUTH.ATTACH.stsEndpoint),
|
.default("https://sts.amazonaws.com/")
|
||||||
allowedPrincipalArns: validatePrincipalArns.describe(AWS_AUTH.ATTACH.allowedPrincipalArns),
|
.describe(AWS_AUTH.ATTACH.stsEndpoint),
|
||||||
allowedAccountIds: validateAccountIds.describe(AWS_AUTH.ATTACH.allowedAccountIds),
|
allowedPrincipalArns: validatePrincipalArns.describe(AWS_AUTH.ATTACH.allowedPrincipalArns),
|
||||||
accessTokenTrustedIps: z
|
allowedAccountIds: validateAccountIds.describe(AWS_AUTH.ATTACH.allowedAccountIds),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(AWS_AUTH.ATTACH.accessTokenTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTTL: z
|
.describe(AWS_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(1)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenTTL must have a non zero number"
|
.default(2592000)
|
||||||
})
|
.describe(AWS_AUTH.ATTACH.accessTokenTTL),
|
||||||
.default(2592000)
|
accessTokenMaxTTL: z
|
||||||
.describe(AWS_AUTH.ATTACH.accessTokenTTL),
|
.number()
|
||||||
accessTokenMaxTTL: z
|
.int()
|
||||||
.number()
|
.min(1)
|
||||||
.int()
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.refine((value) => value !== 0, {
|
.describe(AWS_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(AWS_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
})
|
})
|
||||||
.default(2592000)
|
.refine(
|
||||||
.describe(AWS_AUTH.ATTACH.accessTokenMaxTTL),
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(AWS_AUTH.ATTACH.accessTokenNumUsesLimit)
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
}),
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityAwsAuth: IdentityAwsAuthsSchema
|
identityAwsAuth: IdentityAwsAuthsSchema
|
||||||
@@ -172,30 +172,33 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().describe(AWS_AUTH.UPDATE.identityId)
|
identityId: z.string().describe(AWS_AUTH.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
stsEndpoint: z.string().trim().min(1).optional().describe(AWS_AUTH.UPDATE.stsEndpoint),
|
.object({
|
||||||
allowedPrincipalArns: validatePrincipalArns.describe(AWS_AUTH.UPDATE.allowedPrincipalArns),
|
stsEndpoint: z.string().trim().min(1).optional().describe(AWS_AUTH.UPDATE.stsEndpoint),
|
||||||
allowedAccountIds: validateAccountIds.describe(AWS_AUTH.UPDATE.allowedAccountIds),
|
allowedPrincipalArns: validatePrincipalArns.describe(AWS_AUTH.UPDATE.allowedPrincipalArns),
|
||||||
accessTokenTrustedIps: z
|
allowedAccountIds: validateAccountIds.describe(AWS_AUTH.UPDATE.allowedAccountIds),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.optional()
|
.min(1)
|
||||||
.describe(AWS_AUTH.UPDATE.accessTokenTrustedIps),
|
.optional()
|
||||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
|
.describe(AWS_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenNumUsesLimit),
|
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AWS_AUTH.UPDATE.accessTokenTTL),
|
||||||
accessTokenMaxTTL: z
|
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AWS_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
.number()
|
accessTokenMaxTTL: z
|
||||||
.int()
|
.number()
|
||||||
.max(315360000)
|
.int()
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
.min(0)
|
||||||
})
|
.optional()
|
||||||
.optional()
|
.describe(AWS_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
.describe(AWS_AUTH.UPDATE.accessTokenMaxTTL)
|
})
|
||||||
}),
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityAwsAuth: IdentityAwsAuthsSchema
|
identityAwsAuth: IdentityAwsAuthsSchema
|
||||||
|
@@ -76,39 +76,44 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId)
|
identityId: z.string().trim().describe(AZURE_AUTH.LOGIN.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
tenantId: z.string().trim().describe(AZURE_AUTH.ATTACH.tenantId),
|
.object({
|
||||||
resource: z.string().trim().describe(AZURE_AUTH.ATTACH.resource),
|
tenantId: z.string().trim().describe(AZURE_AUTH.ATTACH.tenantId),
|
||||||
allowedServicePrincipalIds: validateAzureAuthField.describe(AZURE_AUTH.ATTACH.allowedServicePrincipalIds),
|
resource: z.string().trim().describe(AZURE_AUTH.ATTACH.resource),
|
||||||
accessTokenTrustedIps: z
|
allowedServicePrincipalIds: validateAzureAuthField.describe(AZURE_AUTH.ATTACH.allowedServicePrincipalIds),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(AZURE_AUTH.ATTACH.accessTokenTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTTL: z
|
.describe(AZURE_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(1)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenTTL must have a non zero number"
|
.default(2592000)
|
||||||
})
|
.describe(AZURE_AUTH.ATTACH.accessTokenTTL),
|
||||||
.default(2592000)
|
accessTokenMaxTTL: z
|
||||||
.describe(AZURE_AUTH.ATTACH.accessTokenTTL),
|
.number()
|
||||||
accessTokenMaxTTL: z
|
.int()
|
||||||
.number()
|
.min(0)
|
||||||
.int()
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.refine((value) => value !== 0, {
|
.describe(AZURE_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
accessTokenNumUsesLimit: z
|
||||||
})
|
.number()
|
||||||
.default(2592000)
|
.int()
|
||||||
.describe(AZURE_AUTH.ATTACH.accessTokenMaxTTL),
|
.min(0)
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(AZURE_AUTH.ATTACH.accessTokenNumUsesLimit)
|
.default(0)
|
||||||
}),
|
.describe(AZURE_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityAzureAuth: IdentityAzureAuthsSchema
|
identityAzureAuth: IdentityAzureAuthsSchema
|
||||||
@@ -163,32 +168,40 @@ export const registerIdentityAzureAuthRouter = async (server: FastifyZodProvider
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(AZURE_AUTH.UPDATE.identityId)
|
identityId: z.string().trim().describe(AZURE_AUTH.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
tenantId: z.string().trim().optional().describe(AZURE_AUTH.UPDATE.tenantId),
|
.object({
|
||||||
resource: z.string().trim().optional().describe(AZURE_AUTH.UPDATE.resource),
|
tenantId: z.string().trim().optional().describe(AZURE_AUTH.UPDATE.tenantId),
|
||||||
allowedServicePrincipalIds: validateAzureAuthField
|
resource: z.string().trim().optional().describe(AZURE_AUTH.UPDATE.resource),
|
||||||
.optional()
|
allowedServicePrincipalIds: validateAzureAuthField
|
||||||
.describe(AZURE_AUTH.UPDATE.allowedServicePrincipalIds),
|
.optional()
|
||||||
accessTokenTrustedIps: z
|
.describe(AZURE_AUTH.UPDATE.allowedServicePrincipalIds),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.optional()
|
.min(1)
|
||||||
.describe(AZURE_AUTH.UPDATE.accessTokenTrustedIps),
|
.optional()
|
||||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
|
.describe(AZURE_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(AZURE_AUTH.UPDATE.accessTokenNumUsesLimit),
|
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(AZURE_AUTH.UPDATE.accessTokenTTL),
|
||||||
accessTokenMaxTTL: z
|
accessTokenNumUsesLimit: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.optional()
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
.describe(AZURE_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
})
|
accessTokenMaxTTL: z
|
||||||
.optional()
|
.number()
|
||||||
.describe(AZURE_AUTH.UPDATE.accessTokenMaxTTL)
|
.int()
|
||||||
}),
|
.max(315360000)
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.describe(AZURE_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityAzureAuth: IdentityAzureAuthsSchema
|
identityAzureAuth: IdentityAzureAuthsSchema
|
||||||
|
@@ -74,40 +74,40 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(GCP_AUTH.ATTACH.identityId)
|
identityId: z.string().trim().describe(GCP_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
type: z.enum(["iam", "gce"]),
|
.object({
|
||||||
allowedServiceAccounts: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedServiceAccounts),
|
type: z.enum(["iam", "gce"]),
|
||||||
allowedProjects: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedProjects),
|
allowedServiceAccounts: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedServiceAccounts),
|
||||||
allowedZones: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedZones),
|
allowedProjects: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedProjects),
|
||||||
accessTokenTrustedIps: z
|
allowedZones: validateGcpAuthField.describe(GCP_AUTH.ATTACH.allowedZones),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(GCP_AUTH.ATTACH.accessTokenTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTTL: z
|
.describe(GCP_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(1)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenTTL must have a non zero number"
|
.default(2592000)
|
||||||
})
|
.describe(GCP_AUTH.ATTACH.accessTokenTTL),
|
||||||
.default(2592000)
|
accessTokenMaxTTL: z
|
||||||
.describe(GCP_AUTH.ATTACH.accessTokenTTL),
|
.number()
|
||||||
accessTokenMaxTTL: z
|
.int()
|
||||||
.number()
|
.min(0)
|
||||||
.int()
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.refine((value) => value !== 0, {
|
.describe(GCP_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(GCP_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
})
|
})
|
||||||
.default(2592000)
|
.refine(
|
||||||
.describe(GCP_AUTH.ATTACH.accessTokenMaxTTL),
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(GCP_AUTH.ATTACH.accessTokenNumUsesLimit)
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
}),
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityGcpAuth: IdentityGcpAuthsSchema
|
identityGcpAuth: IdentityGcpAuthsSchema
|
||||||
@@ -164,31 +164,34 @@ export const registerIdentityGcpAuthRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(GCP_AUTH.UPDATE.identityId)
|
identityId: z.string().trim().describe(GCP_AUTH.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
type: z.enum(["iam", "gce"]).optional(),
|
.object({
|
||||||
allowedServiceAccounts: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedServiceAccounts),
|
type: z.enum(["iam", "gce"]).optional(),
|
||||||
allowedProjects: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedProjects),
|
allowedServiceAccounts: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedServiceAccounts),
|
||||||
allowedZones: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedZones),
|
allowedProjects: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedProjects),
|
||||||
accessTokenTrustedIps: z
|
allowedZones: validateGcpAuthField.optional().describe(GCP_AUTH.UPDATE.allowedZones),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.optional()
|
.min(1)
|
||||||
.describe(GCP_AUTH.UPDATE.accessTokenTrustedIps),
|
.optional()
|
||||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
|
.describe(GCP_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenNumUsesLimit),
|
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(GCP_AUTH.UPDATE.accessTokenTTL),
|
||||||
accessTokenMaxTTL: z
|
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(GCP_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
.number()
|
accessTokenMaxTTL: z
|
||||||
.int()
|
.number()
|
||||||
.max(315360000)
|
.int()
|
||||||
.refine((value) => value !== 0, {
|
.min(0)
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
.max(315360000)
|
||||||
})
|
.optional()
|
||||||
.optional()
|
.describe(GCP_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
.describe(GCP_AUTH.UPDATE.accessTokenMaxTTL)
|
})
|
||||||
}),
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityGcpAuth: IdentityGcpAuthsSchema
|
identityGcpAuth: IdentityGcpAuthsSchema
|
||||||
|
@@ -34,23 +34,12 @@ const CreateBaseSchema = z.object({
|
|||||||
.min(1)
|
.min(1)
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
.describe(JWT_AUTH.ATTACH.accessTokenTrustedIps),
|
.describe(JWT_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
accessTokenTTL: z
|
accessTokenTTL: z.number().int().min(0).max(315360000).default(2592000).describe(JWT_AUTH.ATTACH.accessTokenTTL),
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.min(1)
|
|
||||||
.max(315360000)
|
|
||||||
.refine((value) => value !== 0, {
|
|
||||||
message: "accessTokenTTL must have a non zero number"
|
|
||||||
})
|
|
||||||
.default(2592000)
|
|
||||||
.describe(JWT_AUTH.ATTACH.accessTokenTTL),
|
|
||||||
accessTokenMaxTTL: z
|
accessTokenMaxTTL: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
|
.min(0)
|
||||||
.max(315360000)
|
.max(315360000)
|
||||||
.refine((value) => value !== 0, {
|
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
|
||||||
})
|
|
||||||
.default(2592000)
|
.default(2592000)
|
||||||
.describe(JWT_AUTH.ATTACH.accessTokenMaxTTL),
|
.describe(JWT_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(JWT_AUTH.ATTACH.accessTokenNumUsesLimit)
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(JWT_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
@@ -70,23 +59,12 @@ const UpdateBaseSchema = z
|
|||||||
.min(1)
|
.min(1)
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
.describe(JWT_AUTH.UPDATE.accessTokenTrustedIps),
|
.describe(JWT_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
accessTokenTTL: z
|
accessTokenTTL: z.number().int().min(0).max(315360000).default(2592000).describe(JWT_AUTH.UPDATE.accessTokenTTL),
|
||||||
.number()
|
|
||||||
.int()
|
|
||||||
.min(1)
|
|
||||||
.max(315360000)
|
|
||||||
.refine((value) => value !== 0, {
|
|
||||||
message: "accessTokenTTL must have a non zero number"
|
|
||||||
})
|
|
||||||
.default(2592000)
|
|
||||||
.describe(JWT_AUTH.UPDATE.accessTokenTTL),
|
|
||||||
accessTokenMaxTTL: z
|
accessTokenMaxTTL: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
|
.min(0)
|
||||||
.max(315360000)
|
.max(315360000)
|
||||||
.refine((value) => value !== 0, {
|
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
|
||||||
})
|
|
||||||
.default(2592000)
|
.default(2592000)
|
||||||
.describe(JWT_AUTH.UPDATE.accessTokenMaxTTL),
|
.describe(JWT_AUTH.UPDATE.accessTokenMaxTTL),
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(JWT_AUTH.UPDATE.accessTokenNumUsesLimit)
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(JWT_AUTH.UPDATE.accessTokenNumUsesLimit)
|
||||||
|
@@ -87,47 +87,47 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(KUBERNETES_AUTH.ATTACH.identityId)
|
identityId: z.string().trim().describe(KUBERNETES_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost),
|
.object({
|
||||||
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
|
kubernetesHost: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.kubernetesHost),
|
||||||
tokenReviewerJwt: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
|
caCert: z.string().trim().default("").describe(KUBERNETES_AUTH.ATTACH.caCert),
|
||||||
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
|
tokenReviewerJwt: z.string().trim().min(1).describe(KUBERNETES_AUTH.ATTACH.tokenReviewerJwt),
|
||||||
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
|
allowedNamespaces: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNamespaces), // TODO: validation
|
||||||
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
|
allowedNames: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedNames),
|
||||||
accessTokenTrustedIps: z
|
allowedAudience: z.string().describe(KUBERNETES_AUTH.ATTACH.allowedAudience),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(KUBERNETES_AUTH.ATTACH.accessTokenTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTTL: z
|
.describe(KUBERNETES_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(1)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenTTL must have a non zero number"
|
.default(2592000)
|
||||||
})
|
.describe(KUBERNETES_AUTH.ATTACH.accessTokenTTL),
|
||||||
.default(2592000)
|
accessTokenMaxTTL: z
|
||||||
.describe(KUBERNETES_AUTH.ATTACH.accessTokenTTL),
|
.number()
|
||||||
accessTokenMaxTTL: z
|
.int()
|
||||||
.number()
|
.min(0)
|
||||||
.int()
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.refine((value) => value !== 0, {
|
.describe(KUBERNETES_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
accessTokenNumUsesLimit: z
|
||||||
})
|
.number()
|
||||||
.default(2592000)
|
.int()
|
||||||
.describe(KUBERNETES_AUTH.ATTACH.accessTokenMaxTTL),
|
.min(0)
|
||||||
accessTokenNumUsesLimit: z
|
.default(0)
|
||||||
.number()
|
.describe(KUBERNETES_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
.int()
|
})
|
||||||
.min(0)
|
.refine(
|
||||||
.default(0)
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
.describe(KUBERNETES_AUTH.ATTACH.accessTokenNumUsesLimit)
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
}),
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
|
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
|
||||||
@@ -183,44 +183,47 @@ export const registerIdentityKubernetesRouter = async (server: FastifyZodProvide
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().describe(KUBERNETES_AUTH.UPDATE.identityId)
|
identityId: z.string().describe(KUBERNETES_AUTH.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost),
|
.object({
|
||||||
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
|
kubernetesHost: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.kubernetesHost),
|
||||||
tokenReviewerJwt: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
|
caCert: z.string().trim().optional().describe(KUBERNETES_AUTH.UPDATE.caCert),
|
||||||
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
|
tokenReviewerJwt: z.string().trim().min(1).optional().describe(KUBERNETES_AUTH.UPDATE.tokenReviewerJwt),
|
||||||
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
|
allowedNamespaces: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNamespaces), // TODO: validation
|
||||||
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
|
allowedNames: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedNames),
|
||||||
accessTokenTrustedIps: z
|
allowedAudience: z.string().optional().describe(KUBERNETES_AUTH.UPDATE.allowedAudience),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.optional()
|
.min(1)
|
||||||
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTrustedIps),
|
.optional()
|
||||||
accessTokenTTL: z
|
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(0)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.optional()
|
.max(315360000)
|
||||||
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
|
.optional()
|
||||||
accessTokenNumUsesLimit: z
|
.describe(KUBERNETES_AUTH.UPDATE.accessTokenTTL),
|
||||||
.number()
|
accessTokenNumUsesLimit: z
|
||||||
.int()
|
.number()
|
||||||
.min(0)
|
.int()
|
||||||
.optional()
|
.min(0)
|
||||||
.describe(KUBERNETES_AUTH.UPDATE.accessTokenNumUsesLimit),
|
.optional()
|
||||||
accessTokenMaxTTL: z
|
.describe(KUBERNETES_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
.number()
|
accessTokenMaxTTL: z
|
||||||
.int()
|
.number()
|
||||||
.max(315360000)
|
.int()
|
||||||
.refine((value) => value !== 0, {
|
.min(0)
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
.max(315360000)
|
||||||
})
|
.optional()
|
||||||
.optional()
|
.describe(KUBERNETES_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
.describe(KUBERNETES_AUTH.UPDATE.accessTokenMaxTTL)
|
})
|
||||||
}),
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
|
identityKubernetesAuth: IdentityKubernetesAuthResponseSchema
|
||||||
|
@@ -87,42 +87,42 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(OIDC_AUTH.ATTACH.identityId)
|
identityId: z.string().trim().describe(OIDC_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
oidcDiscoveryUrl: z.string().url().min(1).describe(OIDC_AUTH.ATTACH.oidcDiscoveryUrl),
|
.object({
|
||||||
caCert: z.string().trim().default("").describe(OIDC_AUTH.ATTACH.caCert),
|
oidcDiscoveryUrl: z.string().url().min(1).describe(OIDC_AUTH.ATTACH.oidcDiscoveryUrl),
|
||||||
boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer),
|
caCert: z.string().trim().default("").describe(OIDC_AUTH.ATTACH.caCert),
|
||||||
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences),
|
boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer),
|
||||||
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims),
|
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences),
|
||||||
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject),
|
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims),
|
||||||
accessTokenTrustedIps: z
|
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(OIDC_AUTH.ATTACH.accessTokenTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTTL: z
|
.describe(OIDC_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(1)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenTTL must have a non zero number"
|
.default(2592000)
|
||||||
})
|
.describe(OIDC_AUTH.ATTACH.accessTokenTTL),
|
||||||
.default(2592000)
|
accessTokenMaxTTL: z
|
||||||
.describe(OIDC_AUTH.ATTACH.accessTokenTTL),
|
.number()
|
||||||
accessTokenMaxTTL: z
|
.int()
|
||||||
.number()
|
.min(0)
|
||||||
.int()
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.refine((value) => value !== 0, {
|
.describe(OIDC_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
})
|
})
|
||||||
.default(2592000)
|
.refine(
|
||||||
.describe(OIDC_AUTH.ATTACH.accessTokenMaxTTL),
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.ATTACH.accessTokenNumUsesLimit)
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
}),
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityOidcAuth: IdentityOidcAuthResponseSchema
|
identityOidcAuth: IdentityOidcAuthResponseSchema
|
||||||
@@ -202,26 +202,24 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
|
|||||||
accessTokenTTL: z
|
accessTokenTTL: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
.min(1)
|
.min(0)
|
||||||
.max(315360000)
|
.max(315360000)
|
||||||
.refine((value) => value !== 0, {
|
|
||||||
message: "accessTokenTTL must have a non zero number"
|
|
||||||
})
|
|
||||||
.default(2592000)
|
.default(2592000)
|
||||||
.describe(OIDC_AUTH.UPDATE.accessTokenTTL),
|
.describe(OIDC_AUTH.UPDATE.accessTokenTTL),
|
||||||
accessTokenMaxTTL: z
|
accessTokenMaxTTL: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
|
.min(0)
|
||||||
.max(315360000)
|
.max(315360000)
|
||||||
.refine((value) => value !== 0, {
|
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
|
||||||
})
|
|
||||||
.default(2592000)
|
.default(2592000)
|
||||||
.describe(OIDC_AUTH.UPDATE.accessTokenMaxTTL),
|
.describe(OIDC_AUTH.UPDATE.accessTokenMaxTTL),
|
||||||
|
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.UPDATE.accessTokenNumUsesLimit)
|
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(OIDC_AUTH.UPDATE.accessTokenNumUsesLimit)
|
||||||
})
|
})
|
||||||
.partial(),
|
.partial()
|
||||||
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityOidcAuth: IdentityOidcAuthResponseSchema
|
identityOidcAuth: IdentityOidcAuthResponseSchema
|
||||||
|
@@ -26,36 +26,41 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(TOKEN_AUTH.ATTACH.identityId)
|
identityId: z.string().trim().describe(TOKEN_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
accessTokenTrustedIps: z
|
.object({
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(TOKEN_AUTH.ATTACH.accessTokenTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTTL: z
|
.describe(TOKEN_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(1)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenTTL must have a non zero number"
|
.default(2592000)
|
||||||
})
|
.describe(TOKEN_AUTH.ATTACH.accessTokenTTL),
|
||||||
.default(2592000)
|
accessTokenMaxTTL: z
|
||||||
.describe(TOKEN_AUTH.ATTACH.accessTokenTTL),
|
.number()
|
||||||
accessTokenMaxTTL: z
|
.int()
|
||||||
.number()
|
.min(0)
|
||||||
.int()
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.refine((value) => value !== 0, {
|
.describe(TOKEN_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
accessTokenNumUsesLimit: z
|
||||||
})
|
.number()
|
||||||
.default(2592000)
|
.int()
|
||||||
.describe(TOKEN_AUTH.ATTACH.accessTokenMaxTTL),
|
.min(0)
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(TOKEN_AUTH.ATTACH.accessTokenNumUsesLimit)
|
.default(0)
|
||||||
}),
|
.describe(TOKEN_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityTokenAuth: IdentityTokenAuthsSchema
|
identityTokenAuth: IdentityTokenAuthsSchema
|
||||||
@@ -110,27 +115,35 @@ export const registerIdentityTokenAuthRouter = async (server: FastifyZodProvider
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(TOKEN_AUTH.UPDATE.identityId)
|
identityId: z.string().trim().describe(TOKEN_AUTH.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
accessTokenTrustedIps: z
|
.object({
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.optional()
|
.min(1)
|
||||||
.describe(TOKEN_AUTH.UPDATE.accessTokenTrustedIps),
|
.optional()
|
||||||
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
|
.describe(TOKEN_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).optional().describe(TOKEN_AUTH.UPDATE.accessTokenNumUsesLimit),
|
accessTokenTTL: z.number().int().min(0).max(315360000).optional().describe(TOKEN_AUTH.UPDATE.accessTokenTTL),
|
||||||
accessTokenMaxTTL: z
|
accessTokenNumUsesLimit: z
|
||||||
.number()
|
.number()
|
||||||
.int()
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.optional()
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
.describe(TOKEN_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
})
|
accessTokenMaxTTL: z
|
||||||
.optional()
|
.number()
|
||||||
.describe(TOKEN_AUTH.UPDATE.accessTokenMaxTTL)
|
.int()
|
||||||
}),
|
.min(0)
|
||||||
|
.max(315360000)
|
||||||
|
.optional()
|
||||||
|
.describe(TOKEN_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityTokenAuth: IdentityTokenAuthsSchema
|
identityTokenAuth: IdentityTokenAuthsSchema
|
||||||
|
@@ -86,49 +86,49 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(UNIVERSAL_AUTH.ATTACH.identityId)
|
identityId: z.string().trim().describe(UNIVERSAL_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
clientSecretTrustedIps: z
|
.object({
|
||||||
.object({
|
clientSecretTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(UNIVERSAL_AUTH.ATTACH.clientSecretTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTrustedIps: z
|
.describe(UNIVERSAL_AUTH.ATTACH.clientSecretTrustedIps),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.min(1)
|
||||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTrustedIps),
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
accessTokenTTL: z
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(1)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.refine((value) => value !== 0, {
|
.max(315360000)
|
||||||
message: "accessTokenTTL must have a non zero number"
|
.default(2592000)
|
||||||
})
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTTL), // 30 days
|
||||||
.default(2592000)
|
accessTokenMaxTTL: z
|
||||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenTTL), // 30 days
|
.number()
|
||||||
accessTokenMaxTTL: z
|
.int()
|
||||||
.number()
|
.min(0)
|
||||||
.int()
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.refine((value) => value !== 0, {
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenMaxTTL), // 30 days
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
accessTokenNumUsesLimit: z
|
||||||
})
|
.number()
|
||||||
.default(2592000)
|
.int()
|
||||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenMaxTTL), // 30 days
|
.min(0)
|
||||||
accessTokenNumUsesLimit: z
|
.default(0)
|
||||||
.number()
|
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
.int()
|
})
|
||||||
.min(0)
|
.refine(
|
||||||
.default(0)
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
.describe(UNIVERSAL_AUTH.ATTACH.accessTokenNumUsesLimit)
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
}),
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityUniversalAuth: IdentityUniversalAuthsSchema
|
identityUniversalAuth: IdentityUniversalAuthsSchema
|
||||||
@@ -181,46 +181,49 @@ export const registerIdentityUaRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().describe(UNIVERSAL_AUTH.UPDATE.identityId)
|
identityId: z.string().describe(UNIVERSAL_AUTH.UPDATE.identityId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z
|
||||||
clientSecretTrustedIps: z
|
.object({
|
||||||
.object({
|
clientSecretTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.optional()
|
.min(1)
|
||||||
.describe(UNIVERSAL_AUTH.UPDATE.clientSecretTrustedIps),
|
.optional()
|
||||||
accessTokenTrustedIps: z
|
.describe(UNIVERSAL_AUTH.UPDATE.clientSecretTrustedIps),
|
||||||
.object({
|
accessTokenTrustedIps: z
|
||||||
ipAddress: z.string().trim()
|
.object({
|
||||||
})
|
ipAddress: z.string().trim()
|
||||||
.array()
|
})
|
||||||
.min(1)
|
.array()
|
||||||
.optional()
|
.min(1)
|
||||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
|
.optional()
|
||||||
accessTokenTTL: z
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTrustedIps),
|
||||||
.number()
|
accessTokenTTL: z
|
||||||
.int()
|
.number()
|
||||||
.min(0)
|
.int()
|
||||||
.max(315360000)
|
.min(0)
|
||||||
.optional()
|
.max(315360000)
|
||||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
|
.optional()
|
||||||
accessTokenNumUsesLimit: z
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenTTL),
|
||||||
.number()
|
accessTokenNumUsesLimit: z
|
||||||
.int()
|
.number()
|
||||||
.min(0)
|
.int()
|
||||||
.optional()
|
.min(0)
|
||||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenNumUsesLimit),
|
.optional()
|
||||||
accessTokenMaxTTL: z
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenNumUsesLimit),
|
||||||
.number()
|
accessTokenMaxTTL: z
|
||||||
.int()
|
.number()
|
||||||
.max(315360000)
|
.int()
|
||||||
.refine((value) => value !== 0, {
|
.min(0)
|
||||||
message: "accessTokenMaxTTL must have a non zero number"
|
.max(315360000)
|
||||||
})
|
.optional()
|
||||||
.optional()
|
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL)
|
||||||
.describe(UNIVERSAL_AUTH.UPDATE.accessTokenMaxTTL)
|
})
|
||||||
}),
|
.refine(
|
||||||
|
(val) => (val.accessTokenMaxTTL && val.accessTokenTTL ? val.accessTokenTTL <= val.accessTokenMaxTTL : true),
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityUniversalAuth: IdentityUniversalAuthsSchema
|
identityUniversalAuth: IdentityUniversalAuthsSchema
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user