mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-25 14:07:47 +00:00
Compare commits
2 Commits
disable-de
...
audit-log-
Author | SHA1 | Date | |
---|---|---|---|
|
2c12d8b404 | ||
|
64fe4187ab |
3
.envrc
3
.envrc
@@ -1,3 +0,0 @@
|
|||||||
# Learn more at https://direnv.net
|
|
||||||
# We instruct direnv to use our Nix flake for a consistent development environment.
|
|
||||||
use flake
|
|
@@ -35,20 +35,7 @@ jobs:
|
|||||||
echo "SECRET_SCANNING_GIT_APP_ID=793712" >> .env
|
echo "SECRET_SCANNING_GIT_APP_ID=793712" >> .env
|
||||||
echo "SECRET_SCANNING_PRIVATE_KEY=some-random" >> .env
|
echo "SECRET_SCANNING_PRIVATE_KEY=some-random" >> .env
|
||||||
echo "SECRET_SCANNING_WEBHOOK_SECRET=some-random" >> .env
|
echo "SECRET_SCANNING_WEBHOOK_SECRET=some-random" >> .env
|
||||||
|
docker run --name infisical-api -d -p 4000:4000 -e DB_CONNECTION_URI=$DB_CONNECTION_URI -e REDIS_URL=$REDIS_URL -e JWT_AUTH_SECRET=$JWT_AUTH_SECRET -e ENCRYPTION_KEY=$ENCRYPTION_KEY --env-file .env --entrypoint '/bin/sh' infisical-api
|
||||||
echo "Examining built image:"
|
|
||||||
docker image inspect infisical-api | grep -A 5 "Entrypoint"
|
|
||||||
|
|
||||||
docker run --name infisical-api -d -p 4000:4000 \
|
|
||||||
-e DB_CONNECTION_URI=$DB_CONNECTION_URI \
|
|
||||||
-e REDIS_URL=$REDIS_URL \
|
|
||||||
-e JWT_AUTH_SECRET=$JWT_AUTH_SECRET \
|
|
||||||
-e ENCRYPTION_KEY=$ENCRYPTION_KEY \
|
|
||||||
--env-file .env \
|
|
||||||
infisical-api
|
|
||||||
|
|
||||||
echo "Container status right after creation:"
|
|
||||||
docker ps -a | grep infisical-api
|
|
||||||
env:
|
env:
|
||||||
REDIS_URL: redis://172.17.0.1:6379
|
REDIS_URL: redis://172.17.0.1:6379
|
||||||
DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable
|
DB_CONNECTION_URI: postgres://infisical:infisical@172.17.0.1:5432/infisical?sslmode=disable
|
||||||
@@ -62,42 +49,29 @@ jobs:
|
|||||||
SECONDS=0
|
SECONDS=0
|
||||||
HEALTHY=0
|
HEALTHY=0
|
||||||
while [ $SECONDS -lt 60 ]; do
|
while [ $SECONDS -lt 60 ]; do
|
||||||
# Check if container is running
|
if docker ps | grep infisical-api | grep -q healthy; then
|
||||||
if docker ps | grep infisical-api; then
|
echo "Container is healthy."
|
||||||
# Try to access the API endpoint
|
HEALTHY=1
|
||||||
if curl -s -f http://localhost:4000/api/docs/json > /dev/null 2>&1; then
|
|
||||||
echo "API endpoint is responding. Container seems healthy."
|
|
||||||
HEALTHY=1
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "Container is not running!"
|
|
||||||
docker ps -a | grep infisical-api
|
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Waiting for container to be healthy... ($SECONDS seconds elapsed)"
|
echo "Waiting for container to be healthy... ($SECONDS seconds elapsed)"
|
||||||
sleep 5
|
|
||||||
SECONDS=$((SECONDS+5))
|
docker logs infisical-api
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
SECONDS=$((SECONDS+2))
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ $HEALTHY -ne 1 ]; then
|
if [ $HEALTHY -ne 1 ]; then
|
||||||
echo "Container did not become healthy in time"
|
echo "Container did not become healthy in time"
|
||||||
echo "Container status:"
|
|
||||||
docker ps -a | grep infisical-api
|
|
||||||
echo "Container logs (if any):"
|
|
||||||
docker logs infisical-api || echo "No logs available"
|
|
||||||
echo "Container inspection:"
|
|
||||||
docker inspect infisical-api | grep -A 5 "State"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
- name: Install openapi-diff
|
- name: Install openapi-diff
|
||||||
run: go install github.com/oasdiff/oasdiff@latest
|
run: go install github.com/tufin/oasdiff@latest
|
||||||
- name: Running OpenAPI Spec diff action
|
- name: Running OpenAPI Spec diff action
|
||||||
run: oasdiff breaking https://app.infisical.com/api/docs/json http://localhost:4000/api/docs/json --fail-on ERR
|
run: oasdiff breaking https://app.infisical.com/api/docs/json http://localhost:4000/api/docs/json --fail-on ERR
|
||||||
- name: cleanup
|
- name: cleanup
|
||||||
if: always()
|
|
||||||
run: |
|
run: |
|
||||||
docker compose -f "docker-compose.dev.yml" down
|
docker compose -f "docker-compose.dev.yml" down
|
||||||
docker stop infisical-api || true
|
docker stop infisical-api
|
||||||
docker rm infisical-api || true
|
docker remove infisical-api
|
||||||
|
6
.github/workflows/run-backend-tests.yml
vendored
6
.github/workflows/run-backend-tests.yml
vendored
@@ -34,10 +34,7 @@ jobs:
|
|||||||
working-directory: backend
|
working-directory: backend
|
||||||
- name: Start postgres and redis
|
- name: Start postgres and redis
|
||||||
run: touch .env && docker compose -f docker-compose.dev.yml up -d db redis
|
run: touch .env && docker compose -f docker-compose.dev.yml up -d db redis
|
||||||
- name: Run unit test
|
- name: Start integration test
|
||||||
run: npm run test:unit
|
|
||||||
working-directory: backend
|
|
||||||
- name: Run integration test
|
|
||||||
run: npm run test:e2e
|
run: npm run test:e2e
|
||||||
working-directory: backend
|
working-directory: backend
|
||||||
env:
|
env:
|
||||||
@@ -48,4 +45,3 @@ jobs:
|
|||||||
- name: cleanup
|
- name: cleanup
|
||||||
run: |
|
run: |
|
||||||
docker compose -f "docker-compose.dev.yml" down
|
docker compose -f "docker-compose.dev.yml" down
|
||||||
|
|
||||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,5 +1,3 @@
|
|||||||
.direnv/
|
|
||||||
|
|
||||||
# backend
|
# backend
|
||||||
node_modules
|
node_modules
|
||||||
.env
|
.env
|
||||||
@@ -28,6 +26,8 @@ node_modules
|
|||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
coverage
|
coverage
|
||||||
reports
|
reports
|
||||||
@@ -63,12 +63,10 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Editor specific
|
# Editor specific
|
||||||
.vscode/*
|
.vscode/*
|
||||||
**/.idea/*
|
.idea/*
|
||||||
|
|
||||||
frontend-build
|
frontend-build
|
||||||
|
|
||||||
# cli
|
|
||||||
.go/
|
|
||||||
*.tgz
|
*.tgz
|
||||||
cli/infisical-merge
|
cli/infisical-merge
|
||||||
cli/test/infisical-merge
|
cli/test/infisical-merge
|
||||||
|
@@ -120,3 +120,4 @@ export default {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
166
backend/package-lock.json
generated
166
backend/package-lock.json
generated
@@ -31,7 +31,7 @@
|
|||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
@@ -6747,35 +6747,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@node-saml/node-saml": {
|
"node_modules/@node-saml/node-saml": {
|
||||||
"version": "5.0.1",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.5.tgz",
|
||||||
"integrity": "sha512-YQzFPEC+CnsfO9AFYnwfYZKIzOLx3kITaC1HrjHVLTo6hxcQhc+LgHODOMvW4VCV95Gwrz1MshRUWCPzkDqmnA==",
|
"integrity": "sha512-J5DglElbY1tjOuaR1NPtjOXkXY5bpUhDoKVoeucYN98A3w4fwgjIOPqIGcb6cQsqFq2zZ6vTCeKn5C/hvefSaw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/debug": "^4.1.12",
|
"@types/debug": "^4.1.7",
|
||||||
"@types/qs": "^6.9.11",
|
"@types/passport": "^1.0.11",
|
||||||
"@types/xml-encryption": "^1.2.4",
|
"@types/xml-crypto": "^1.4.2",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml-encryption": "^1.2.1",
|
||||||
"@xmldom/is-dom-node": "^1.0.1",
|
"@types/xml2js": "^0.4.11",
|
||||||
"@xmldom/xmldom": "^0.8.10",
|
"@xmldom/xmldom": "^0.8.6",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"xml-crypto": "^6.0.1",
|
"xml-crypto": "^3.0.1",
|
||||||
"xml-encryption": "^3.0.2",
|
"xml-encryption": "^3.0.2",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.5.0",
|
||||||
"xmlbuilder": "^15.1.1",
|
"xmlbuilder": "^15.1.1"
|
||||||
"xpath": "^0.0.34"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@node-saml/node-saml/node_modules/debug": {
|
"node_modules/@node-saml/node-saml/node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ms": "^2.1.3"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0"
|
"node": ">=6.0"
|
||||||
@@ -6786,43 +6783,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@node-saml/node-saml/node_modules/xml2js": {
|
"node_modules/@node-saml/node-saml/node_modules/ms": {
|
||||||
"version": "0.6.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"sax": ">=0.6.0",
|
|
||||||
"xmlbuilder": "~11.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@node-saml/node-saml/node_modules/xml2js/node_modules/xmlbuilder": {
|
|
||||||
"version": "11.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
|
||||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/@node-saml/passport-saml": {
|
"node_modules/@node-saml/passport-saml": {
|
||||||
"version": "5.0.1",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@node-saml/passport-saml/-/passport-saml-4.0.4.tgz",
|
||||||
"integrity": "sha512-fMztg3zfSnjLEgxvpl6HaDMNeh0xeQX4QHiF9e2Lsie2dc4qFE37XYbQZhVmn8XJ2awPpSWLQ736UskYgGU8lQ==",
|
"integrity": "sha512-xFw3gw0yo+K1mzlkW15NeBF7cVpRHN/4vpjmBKzov5YFImCWh/G0LcTZ8krH3yk2/eRPc3Or8LRPudVJBjmYaw==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@node-saml/node-saml": "^5.0.1",
|
"@node-saml/node-saml": "^4.0.4",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.14",
|
||||||
"@types/passport": "^1.0.16",
|
"@types/passport": "^1.0.11",
|
||||||
"@types/passport-strategy": "^0.2.38",
|
"@types/passport-strategy": "^0.2.35",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.6.0",
|
||||||
"passport-strategy": "^1.0.0"
|
"passport-strategy": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
@@ -9627,7 +9606,6 @@
|
|||||||
"version": "4.1.12",
|
"version": "4.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||||
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
"integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/ms": "*"
|
"@types/ms": "*"
|
||||||
}
|
}
|
||||||
@@ -9747,10 +9725,9 @@
|
|||||||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
|
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/ms": {
|
"node_modules/@types/ms": {
|
||||||
"version": "2.1.0",
|
"version": "0.7.34",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz",
|
||||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.9.5",
|
"version": "20.9.5",
|
||||||
@@ -9930,10 +9907,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/qs": {
|
"node_modules/@types/qs": {
|
||||||
"version": "6.9.18",
|
"version": "6.9.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz",
|
||||||
"integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
|
"integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw=="
|
||||||
"license": "MIT"
|
|
||||||
},
|
},
|
||||||
"node_modules/@types/range-parser": {
|
"node_modules/@types/range-parser": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
@@ -10082,11 +10058,19 @@
|
|||||||
"@types/webidl-conversions": "*"
|
"@types/webidl-conversions": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/xml-crypto": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-A6jEW2FxLZo1CXsRWnZHUX2wzR3uDju2Bozt6rDbSmU/W8gkilaVbwFEVN0/NhnUdMVzwYobWtM6bU1QJJFb7Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*",
|
||||||
|
"xpath": "0.0.27"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/xml-encryption": {
|
"node_modules/@types/xml-encryption": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/xml-encryption/-/xml-encryption-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/xml-encryption/-/xml-encryption-1.2.4.tgz",
|
||||||
"integrity": "sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q==",
|
"integrity": "sha512-I69K/WW1Dv7j6O3jh13z0X8sLWJRXbu5xnHDl9yHzUNDUBtUoBY058eb5s+x/WG6yZC1h8aKdI2EoyEPjyEh+Q==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@@ -10095,7 +10079,6 @@
|
|||||||
"version": "0.4.14",
|
"version": "0.4.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||||
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
@@ -10539,20 +10522,10 @@
|
|||||||
"url": "https://opencollective.com/vitest"
|
"url": "https://opencollective.com/vitest"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@xmldom/is-dom-node": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/is-dom-node/-/is-dom-node-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.8.10",
|
"version": "0.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||||
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
}
|
}
|
||||||
@@ -18249,10 +18222,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/passport": {
|
"node_modules/passport": {
|
||||||
"version": "0.7.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz",
|
||||||
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
|
"integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"passport-strategy": "1.x.x",
|
"passport-strategy": "1.x.x",
|
||||||
"pause": "0.0.1",
|
"pause": "0.0.1",
|
||||||
@@ -23720,44 +23692,42 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml-crypto": {
|
"node_modules/xml-crypto": {
|
||||||
"version": "6.0.1",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz",
|
||||||
"integrity": "sha512-v05aU7NS03z4jlZ0iZGRFeZsuKO1UfEbbYiaeRMiATBFs6Jq9+wqKquEMTn4UTrYZ9iGD8yz3KT4L9o2iF682w==",
|
"integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xmldom/is-dom-node": "^1.0.1",
|
"@xmldom/xmldom": "^0.8.8",
|
||||||
"@xmldom/xmldom": "^0.8.10",
|
"xpath": "0.0.32"
|
||||||
"xpath": "^0.0.33"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml-crypto/node_modules/xpath": {
|
"node_modules/xml-crypto/node_modules/xpath": {
|
||||||
"version": "0.0.33",
|
"version": "0.0.32",
|
||||||
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.33.tgz",
|
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
|
||||||
"integrity": "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==",
|
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6.0"
|
"node": ">=0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml-encryption": {
|
"node_modules/xml-encryption": {
|
||||||
"version": "3.1.0",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.0.2.tgz",
|
||||||
"integrity": "sha512-PV7qnYpoAMXbf1kvQkqMScLeQpjCMixddAKq9PtqVrho8HnYbBOWNfG0kA4R7zxQDo7w9kiYAyzS/ullAyO55Q==",
|
"integrity": "sha512-VxYXPvsWB01/aqVLd6ZMPWZ+qaj0aIdF+cStrVJMcFj3iymwZeI0ABzB3VqMYv48DkSpRhnrXqTUkR34j+UDyg==",
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xmldom/xmldom": "^0.8.5",
|
"@xmldom/xmldom": "^0.8.5",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"xpath": "0.0.32"
|
"xpath": "0.0.32"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml-encryption/node_modules/xpath": {
|
"node_modules/xml-encryption/node_modules/xpath": {
|
||||||
"version": "0.0.32",
|
"version": "0.0.32",
|
||||||
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
|
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz",
|
||||||
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
|
"integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6.0"
|
"node": ">=0.6.0"
|
||||||
}
|
}
|
||||||
@@ -23794,7 +23764,6 @@
|
|||||||
"version": "15.1.1",
|
"version": "15.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
|
||||||
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
|
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0"
|
"node": ">=8.0"
|
||||||
}
|
}
|
||||||
@@ -23805,10 +23774,9 @@
|
|||||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||||
},
|
},
|
||||||
"node_modules/xpath": {
|
"node_modules/xpath": {
|
||||||
"version": "0.0.34",
|
"version": "0.0.27",
|
||||||
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz",
|
"resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz",
|
||||||
"integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==",
|
"integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==",
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.6.0"
|
"node": ">=0.6.0"
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,6 @@
|
|||||||
"type:check": "tsc --noEmit",
|
"type:check": "tsc --noEmit",
|
||||||
"lint:fix": "eslint --fix --ext js,ts ./src",
|
"lint:fix": "eslint --fix --ext js,ts ./src",
|
||||||
"lint": "eslint 'src/**/*.ts'",
|
"lint": "eslint 'src/**/*.ts'",
|
||||||
"test:unit": "vitest run -c vitest.unit.config.ts",
|
|
||||||
"test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1",
|
"test:e2e": "vitest run -c vitest.e2e.config.ts --bail=1",
|
||||||
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
"test:e2e-watch": "vitest -c vitest.e2e.config.ts --bail=1",
|
||||||
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
"test:e2e-coverage": "vitest run --coverage -c vitest.e2e.config.ts",
|
||||||
@@ -71,7 +70,6 @@
|
|||||||
"migrate:org": "tsx ./scripts/migrate-organization.ts",
|
"migrate:org": "tsx ./scripts/migrate-organization.ts",
|
||||||
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
"seed:new": "tsx ./scripts/create-seed-file.ts",
|
||||||
"seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run",
|
"seed": "knex --knexfile ./dist/db/knexfile.ts --client pg seed:run",
|
||||||
"seed-dev": "knex --knexfile ./src/db/knexfile.ts --client pg seed:run",
|
|
||||||
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
|
"db:reset": "npm run migration:rollback -- --all && npm run migration:latest"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -148,7 +146,7 @@
|
|||||||
"@fastify/swagger-ui": "^2.1.0",
|
"@fastify/swagger-ui": "^2.1.0",
|
||||||
"@google-cloud/kms": "^4.5.0",
|
"@google-cloud/kms": "^4.5.0",
|
||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
|
@@ -1,45 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
if (await knex.schema.hasTable(TableName.SecretVersionV2)) {
|
|
||||||
const hasSecretVersionV2UserActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "userActorId");
|
|
||||||
const hasSecretVersionV2IdentityActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "identityActorId");
|
|
||||||
const hasSecretVersionV2ActorType = await knex.schema.hasColumn(TableName.SecretVersionV2, "actorType");
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.SecretVersionV2, (t) => {
|
|
||||||
if (!hasSecretVersionV2UserActorId) {
|
|
||||||
t.uuid("userActorId");
|
|
||||||
t.foreign("userActorId").references("id").inTable(TableName.Users);
|
|
||||||
}
|
|
||||||
if (!hasSecretVersionV2IdentityActorId) {
|
|
||||||
t.uuid("identityActorId");
|
|
||||||
t.foreign("identityActorId").references("id").inTable(TableName.Identity);
|
|
||||||
}
|
|
||||||
if (!hasSecretVersionV2ActorType) {
|
|
||||||
t.string("actorType");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
if (await knex.schema.hasTable(TableName.SecretVersionV2)) {
|
|
||||||
const hasSecretVersionV2UserActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "userActorId");
|
|
||||||
const hasSecretVersionV2IdentityActorId = await knex.schema.hasColumn(TableName.SecretVersionV2, "identityActorId");
|
|
||||||
const hasSecretVersionV2ActorType = await knex.schema.hasColumn(TableName.SecretVersionV2, "actorType");
|
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.SecretVersionV2, (t) => {
|
|
||||||
if (hasSecretVersionV2UserActorId) {
|
|
||||||
t.dropColumn("userActorId");
|
|
||||||
}
|
|
||||||
if (hasSecretVersionV2IdentityActorId) {
|
|
||||||
t.dropColumn("identityActorId");
|
|
||||||
}
|
|
||||||
if (hasSecretVersionV2ActorType) {
|
|
||||||
t.dropColumn("actorType");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { TableName } from "../schemas";
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
if (await knex.schema.hasTable(TableName.Organization)) {
|
|
||||||
const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(
|
|
||||||
TableName.Organization,
|
|
||||||
"allowSecretSharingOutsideOrganization"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!hasSecretShareToAnyoneCol) {
|
|
||||||
await knex.schema.alterTable(TableName.Organization, (t) => {
|
|
||||||
t.boolean("allowSecretSharingOutsideOrganization").defaultTo(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
if (await knex.schema.hasTable(TableName.Organization)) {
|
|
||||||
const hasSecretShareToAnyoneCol = await knex.schema.hasColumn(
|
|
||||||
TableName.Organization,
|
|
||||||
"allowSecretSharingOutsideOrganization"
|
|
||||||
);
|
|
||||||
if (hasSecretShareToAnyoneCol) {
|
|
||||||
await knex.schema.alterTable(TableName.Organization, (t) => {
|
|
||||||
t.dropColumn("allowSecretSharingOutsideOrganization");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -22,8 +22,7 @@ export const OrganizationsSchema = z.object({
|
|||||||
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
defaultMembershipRole: z.string().default("member"),
|
defaultMembershipRole: z.string().default("member"),
|
||||||
enforceMfa: z.boolean().default(false),
|
enforceMfa: z.boolean().default(false),
|
||||||
selectedMfaMethod: z.string().nullable().optional(),
|
selectedMfaMethod: z.string().nullable().optional()
|
||||||
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
@@ -25,10 +25,7 @@ export const SecretVersionsV2Schema = z.object({
|
|||||||
folderId: z.string().uuid(),
|
folderId: z.string().uuid(),
|
||||||
userId: z.string().uuid().nullable().optional(),
|
userId: z.string().uuid().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date()
|
||||||
userActorId: z.string().uuid().nullable().optional(),
|
|
||||||
identityActorId: z.string().uuid().nullable().optional(),
|
|
||||||
actorType: z.string().nullable().optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretVersionsV2 = z.infer<typeof SecretVersionsV2Schema>;
|
export type TSecretVersionsV2 = z.infer<typeof SecretVersionsV2Schema>;
|
||||||
|
@@ -1,11 +1,16 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretApprovalRequestsReviewersSchema, SecretApprovalRequestsSchema, UsersSchema } from "@app/db/schemas";
|
import {
|
||||||
|
SecretApprovalRequestsReviewersSchema,
|
||||||
|
SecretApprovalRequestsSchema,
|
||||||
|
SecretTagsSchema,
|
||||||
|
UsersSchema
|
||||||
|
} from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
import { ApprovalStatus, RequestState } from "@app/ee/services/secret-approval-request/secret-approval-request-types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
|
|
||||||
@@ -245,6 +250,14 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tagSchema = SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional();
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:id",
|
url: "/:id",
|
||||||
@@ -278,7 +291,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
.omit({ _id: true, environment: true, workspace: true, type: true, version: true })
|
.omit({ _id: true, environment: true, workspace: true, type: true, version: true })
|
||||||
.extend({
|
.extend({
|
||||||
op: z.string(),
|
op: z.string(),
|
||||||
tags: SanitizedTagSchema.array().optional(),
|
tags: tagSchema,
|
||||||
secretMetadata: ResourceMetadataSchema.nullish(),
|
secretMetadata: ResourceMetadataSchema.nullish(),
|
||||||
secret: z
|
secret: z
|
||||||
.object({
|
.object({
|
||||||
@@ -297,7 +310,7 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv
|
|||||||
secretKey: z.string(),
|
secretKey: z.string(),
|
||||||
secretValue: z.string().optional(),
|
secretValue: z.string().optional(),
|
||||||
secretComment: z.string().optional(),
|
secretComment: z.string().optional(),
|
||||||
tags: SanitizedTagSchema.array().optional(),
|
tags: tagSchema,
|
||||||
secretMetadata: ResourceMetadataSchema.nullish()
|
secretMetadata: ResourceMetadataSchema.nullish()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { RAW_SECRETS } from "@app/lib/api-docs";
|
import { RAW_SECRETS } from "@app/lib/api-docs";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -9,7 +9,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
|
|
||||||
const AccessListEntrySchema = z
|
const AccessListEntrySchema = z
|
||||||
.object({
|
.object({
|
||||||
allowedActions: z.nativeEnum(ProjectPermissionSecretActions).array(),
|
allowedActions: z.nativeEnum(ProjectPermissionActions).array(),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
membershipId: z.string(),
|
membershipId: z.string(),
|
||||||
name: z.string()
|
name: z.string()
|
||||||
|
@@ -22,11 +22,7 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secretVersions: secretRawSchema
|
secretVersions: secretRawSchema.array()
|
||||||
.extend({
|
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -41,7 +37,6 @@ export const registerSecretVersionRouter = async (server: FastifyZodProvider) =>
|
|||||||
offset: req.query.offset,
|
offset: req.query.offset,
|
||||||
secretId: req.params.secretId
|
secretId: req.params.secretId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secretVersions };
|
return { secretVersions };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretSnapshotsSchema } from "@app/db/schemas";
|
import { SecretSnapshotsSchema, SecretTagsSchema } from "@app/db/schemas";
|
||||||
import { PROJECTS } from "@app/lib/api-docs";
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
||||||
@@ -31,9 +31,12 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretVersions: secretRawSchema
|
secretVersions: secretRawSchema
|
||||||
.omit({ _id: true, environment: true, workspace: true, type: true })
|
.omit({ _id: true, environment: true, workspace: true, type: true })
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretId: z.string(),
|
secretId: z.string(),
|
||||||
tags: SanitizedTagSchema.array()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
}).array()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
folderVersion: z.object({ id: z.string(), name: z.string() }).array(),
|
||||||
@@ -52,7 +55,6 @@ export const registerSnapshotRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
id: req.params.secretSnapshotId
|
id: req.params.secretSnapshotId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { secretSnapshot };
|
return { secretSnapshot };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -2,7 +2,6 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
|
|
||||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||||
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
import { PROJECT_USER_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
@@ -24,9 +23,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
||||||
slug: slugSchema({ min: 1, max: 60 }).optional().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
slug: slugSchema({ min: 1, max: 60 }).optional().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
|
||||||
.refine(checkForInvalidPermissionCombination),
|
|
||||||
type: z.discriminatedUnion("isTemporary", [
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
z.object({
|
z.object({
|
||||||
isTemporary: z.literal(false)
|
isTemporary: z.literal(false)
|
||||||
@@ -84,8 +81,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
slug: slugSchema({ min: 1, max: 60 }).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
slug: slugSchema({ min: 1, max: 60 }).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions)
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
.refine(checkForInvalidPermissionCombination),
|
|
||||||
type: z.discriminatedUnion("isTemporary", [
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
z.object({ isTemporary: z.literal(false).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary) }),
|
z.object({ isTemporary: z.literal(false).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary) }),
|
||||||
z.object({
|
z.object({
|
||||||
|
@@ -3,7 +3,6 @@ import ms from "ms";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types";
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types";
|
||||||
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
|
|
||||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
|
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@@ -31,9 +30,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.identityId),
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.identityId),
|
||||||
projectId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.projectId),
|
projectId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.projectId),
|
||||||
slug: slugSchema({ min: 1, max: 60 }).optional().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.slug),
|
slug: slugSchema({ min: 1, max: 60 }).optional().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.slug),
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.permission),
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.permission)
|
|
||||||
.refine(checkForInvalidPermissionCombination),
|
|
||||||
type: z.discriminatedUnion("isTemporary", [
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
z.object({
|
z.object({
|
||||||
isTemporary: z.literal(false)
|
isTemporary: z.literal(false)
|
||||||
@@ -97,8 +94,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
slug: slugSchema({ min: 1, max: 60 }).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.slug),
|
slug: slugSchema({ min: 1, max: 60 }).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.slug),
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
.optional()
|
.optional()
|
||||||
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.privilegePermission)
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.privilegePermission),
|
||||||
.refine(checkForInvalidPermissionCombination),
|
|
||||||
type: z.discriminatedUnion("isTemporary", [
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
z.object({ isTemporary: z.literal(false).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary) }),
|
z.object({ isTemporary: z.literal(false).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary) }),
|
||||||
z.object({
|
z.object({
|
||||||
|
@@ -2,7 +2,6 @@ import { packRules } from "@casl/ability/extra";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
import { checkForInvalidPermissionCombination } from "@app/ee/services/permission/permission-fns";
|
|
||||||
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -38,9 +37,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.describe(PROJECT_ROLE.CREATE.slug),
|
.describe(PROJECT_ROLE.CREATE.slug),
|
||||||
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
||||||
description: z.string().trim().nullish().describe(PROJECT_ROLE.CREATE.description),
|
description: z.string().trim().nullish().describe(PROJECT_ROLE.CREATE.description),
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
||||||
.describe(PROJECT_ROLE.CREATE.permissions)
|
|
||||||
.refine(checkForInvalidPermissionCombination)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -95,10 +92,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
.describe(PROJECT_ROLE.UPDATE.slug),
|
.describe(PROJECT_ROLE.UPDATE.slug),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description),
|
description: z.string().trim().nullish().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionV2Schema.array()
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
.describe(PROJECT_ROLE.UPDATE.permissions)
|
|
||||||
.optional()
|
|
||||||
.superRefine(checkForInvalidPermissionCombination)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -1,16 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export type PasswordRequirements = {
|
|
||||||
length: number;
|
|
||||||
required: {
|
|
||||||
lowercase: number;
|
|
||||||
uppercase: number;
|
|
||||||
digits: number;
|
|
||||||
symbols: number;
|
|
||||||
};
|
|
||||||
allowedSymbols?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum SqlProviders {
|
export enum SqlProviders {
|
||||||
Postgres = "postgres",
|
Postgres = "postgres",
|
||||||
MySQL = "mysql2",
|
MySQL = "mysql2",
|
||||||
@@ -111,28 +100,6 @@ export const DynamicSecretSqlDBSchema = z.object({
|
|||||||
database: z.string().trim(),
|
database: z.string().trim(),
|
||||||
username: z.string().trim(),
|
username: z.string().trim(),
|
||||||
password: z.string().trim(),
|
password: z.string().trim(),
|
||||||
passwordRequirements: z
|
|
||||||
.object({
|
|
||||||
length: z.number().min(1).max(250),
|
|
||||||
required: z
|
|
||||||
.object({
|
|
||||||
lowercase: z.number().min(0),
|
|
||||||
uppercase: z.number().min(0),
|
|
||||||
digits: z.number().min(0),
|
|
||||||
symbols: z.number().min(0)
|
|
||||||
})
|
|
||||||
.refine((data) => {
|
|
||||||
const total = Object.values(data).reduce((sum, count) => sum + count, 0);
|
|
||||||
return total <= 250;
|
|
||||||
}, "Sum of required characters cannot exceed 250"),
|
|
||||||
allowedSymbols: z.string().optional()
|
|
||||||
})
|
|
||||||
.refine((data) => {
|
|
||||||
const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
|
|
||||||
return total <= data.length;
|
|
||||||
}, "Sum of required characters cannot exceed the total length")
|
|
||||||
.optional()
|
|
||||||
.describe("Password generation requirements"),
|
|
||||||
creationStatement: z.string().trim(),
|
creationStatement: z.string().trim(),
|
||||||
revocationStatement: z.string().trim(),
|
revocationStatement: z.string().trim(),
|
||||||
renewStatement: z.string().trim().optional(),
|
renewStatement: z.string().trim().optional(),
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { randomInt } from "crypto";
|
|
||||||
import handlebars from "handlebars";
|
import handlebars from "handlebars";
|
||||||
import knex from "knex";
|
import knex from "knex";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { withGatewayProxy } from "@app/lib/gateway";
|
import { withGatewayProxy } from "@app/lib/gateway";
|
||||||
@@ -8,99 +8,16 @@ import { alphaNumericNanoId } from "@app/lib/nanoid";
|
|||||||
|
|
||||||
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
import { TGatewayServiceFactory } from "../../gateway/gateway-service";
|
||||||
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||||
import { DynamicSecretSqlDBSchema, PasswordRequirements, SqlProviders, TDynamicProviderFns } from "./models";
|
import { DynamicSecretSqlDBSchema, SqlProviders, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||||
|
|
||||||
const DEFAULT_PASSWORD_REQUIREMENTS = {
|
const generatePassword = (provider: SqlProviders) => {
|
||||||
length: 48,
|
// oracle has limit of 48 password length
|
||||||
required: {
|
const size = provider === SqlProviders.Oracle ? 30 : 48;
|
||||||
lowercase: 1,
|
|
||||||
uppercase: 1,
|
|
||||||
digits: 1,
|
|
||||||
symbols: 0
|
|
||||||
},
|
|
||||||
allowedSymbols: "-_.~!*"
|
|
||||||
};
|
|
||||||
|
|
||||||
const ORACLE_PASSWORD_REQUIREMENTS = {
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*";
|
||||||
...DEFAULT_PASSWORD_REQUIREMENTS,
|
return customAlphabet(charset, 48)(size);
|
||||||
length: 30
|
|
||||||
};
|
|
||||||
|
|
||||||
const generatePassword = (provider: SqlProviders, requirements?: PasswordRequirements) => {
|
|
||||||
const defaultReqs = provider === SqlProviders.Oracle ? ORACLE_PASSWORD_REQUIREMENTS : DEFAULT_PASSWORD_REQUIREMENTS;
|
|
||||||
const finalReqs = requirements || defaultReqs;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { length, required, allowedSymbols } = finalReqs;
|
|
||||||
|
|
||||||
const chars = {
|
|
||||||
lowercase: "abcdefghijklmnopqrstuvwxyz",
|
|
||||||
uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
||||||
digits: "0123456789",
|
|
||||||
symbols: allowedSymbols || "-_.~!*"
|
|
||||||
};
|
|
||||||
|
|
||||||
const parts: string[] = [];
|
|
||||||
|
|
||||||
if (required.lowercase > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.lowercase)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.lowercase[randomInt(chars.lowercase.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required.uppercase > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.uppercase)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.uppercase[randomInt(chars.uppercase.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required.digits > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.digits)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.digits[randomInt(chars.digits.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (required.symbols > 0) {
|
|
||||||
parts.push(
|
|
||||||
...Array(required.symbols)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => chars.symbols[randomInt(chars.symbols.length)])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const requiredTotal = Object.values(required).reduce<number>((a, b) => a + b, 0);
|
|
||||||
const remainingLength = Math.max(length - requiredTotal, 0);
|
|
||||||
|
|
||||||
const allowedChars = Object.entries(chars)
|
|
||||||
.filter(([key]) => required[key as keyof typeof required] > 0)
|
|
||||||
.map(([, value]) => value)
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
parts.push(
|
|
||||||
...Array(remainingLength)
|
|
||||||
.fill(0)
|
|
||||||
.map(() => allowedChars[randomInt(allowedChars.length)])
|
|
||||||
);
|
|
||||||
|
|
||||||
// shuffle the array to mix up the characters
|
|
||||||
for (let i = parts.length - 1; i > 0; i -= 1) {
|
|
||||||
const j = randomInt(i + 1);
|
|
||||||
[parts[i], parts[j]] = [parts[j], parts[i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts.join("");
|
|
||||||
} catch (error: unknown) {
|
|
||||||
const message = error instanceof Error ? error.message : "Unknown error";
|
|
||||||
throw new Error(`Failed to generate password: ${message}`);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateUsername = (provider: SqlProviders) => {
|
const generateUsername = (provider: SqlProviders) => {
|
||||||
@@ -198,7 +115,7 @@ export const SqlDatabaseProvider = ({ gatewayService }: TSqlDatabaseProviderDTO)
|
|||||||
const create = async (inputs: unknown, expireAt: number) => {
|
const create = async (inputs: unknown, expireAt: number) => {
|
||||||
const providerInputs = await validateProviderInputs(inputs);
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
const username = generateUsername(providerInputs.client);
|
const username = generateUsername(providerInputs.client);
|
||||||
const password = generatePassword(providerInputs.client, providerInputs.passwordRequirements);
|
const password = generatePassword(providerInputs.client);
|
||||||
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
|
const gatewayCallback = async (host = providerInputs.host, port = providerInputs.port) => {
|
||||||
const db = await $getClient({ ...providerInputs, port, host });
|
const db = await $getClient({ ...providerInputs, port, host });
|
||||||
try {
|
try {
|
||||||
|
@@ -3,7 +3,7 @@ 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 { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
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";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
@@ -87,14 +87,9 @@ export const groupServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const isCustomRole = Boolean(customRole);
|
const isCustomRole = Boolean(customRole);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!hasRequiredPriviledges)
|
||||||
if (!permissionBoundary.isValid)
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to create a more privileged group",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const group = await groupDAL.transaction(async (tx) => {
|
const group = await groupDAL.transaction(async (tx) => {
|
||||||
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
|
const existingGroup = await groupDAL.findOne({ orgId: actorOrgId, name }, tx);
|
||||||
@@ -161,13 +156,9 @@ export const groupServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isCustomRole = Boolean(customOrgRole);
|
const isCustomRole = Boolean(customOrgRole);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredNewRolePermission)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update a more privileged group",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
if (isCustomRole) customRole = customOrgRole;
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,13 +329,9 @@ export const groupServiceFactory = ({
|
|||||||
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
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, groupRolePermission);
|
const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPrivileges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to add user to more privileged group",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const user = await userDAL.findOne({ username });
|
||||||
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
||||||
@@ -409,13 +396,9 @@ export const groupServiceFactory = ({
|
|||||||
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
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, groupRolePermission);
|
const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, groupRolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPrivileges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to delete user from more privileged group",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const user = await userDAL.findOne({ username });
|
||||||
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
||||||
|
@@ -3,7 +3,7 @@ import { packRules } from "@casl/ability/extra";
|
|||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
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/sanitizedSchema/permission";
|
import { unpackPermissions } from "@app/server/routes/sanitizedSchema/permission";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
@@ -79,13 +79,9 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
// 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
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, targetIdentityPermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
@@ -165,13 +161,9 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
// 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
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, targetIdentityPermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data?.slug) {
|
if (data?.slug) {
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
@@ -247,13 +239,9 @@ export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||||
return {
|
return {
|
||||||
|
@@ -3,7 +3,7 @@ import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
|||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
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/sanitizedSchema/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
@@ -88,13 +88,9 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
// 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
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, targetIdentityPermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
@@ -176,13 +172,9 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
// 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
|
||||||
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, targetIdentityPermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
@@ -276,13 +268,9 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to edit more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to edit more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
|
@@ -32,10 +32,6 @@ export enum OrgPermissionAdminConsoleAction {
|
|||||||
AccessAllProjects = "access-all-projects"
|
AccessAllProjects = "access-all-projects"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionSecretShareAction {
|
|
||||||
ManageSettings = "manage-settings"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum OrgPermissionGatewayActions {
|
export enum OrgPermissionGatewayActions {
|
||||||
// is there a better word for this. This mean can an identity be a gateway
|
// is there a better word for this. This mean can an identity be a gateway
|
||||||
CreateGateways = "create-gateways",
|
CreateGateways = "create-gateways",
|
||||||
@@ -63,8 +59,7 @@ export enum OrgPermissionSubjects {
|
|||||||
ProjectTemplates = "project-templates",
|
ProjectTemplates = "project-templates",
|
||||||
AppConnections = "app-connections",
|
AppConnections = "app-connections",
|
||||||
Kmip = "kmip",
|
Kmip = "kmip",
|
||||||
Gateway = "gateway",
|
Gateway = "gateway"
|
||||||
SecretShare = "secret-share"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppConnectionSubjectFields = {
|
export type AppConnectionSubjectFields = {
|
||||||
@@ -96,8 +91,7 @@ export type OrgPermissionSet =
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||||
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
|
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip];
|
||||||
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
|
|
||||||
|
|
||||||
const AppConnectionConditionSchema = z
|
const AppConnectionConditionSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -191,12 +185,6 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
z.object({
|
|
||||||
subject: z.literal(OrgPermissionSubjects.SecretShare).describe("The entity this permission pertains to."),
|
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionSecretShareAction).describe(
|
|
||||||
"Describe what action an entity can take."
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."),
|
subject: z.literal(OrgPermissionSubjects.Kmip).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionKmipActions).describe(
|
||||||
@@ -304,8 +292,6 @@ const buildAdminPermission = () => {
|
|||||||
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
|
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
|
||||||
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||||
|
|
||||||
can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare);
|
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,109 +1,7 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
import { ForbiddenError, MongoAbility, PureAbility, subject } from "@casl/ability";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TOrganizations } from "@app/db/schemas";
|
import { TOrganizations } from "@app/db/schemas";
|
||||||
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import {
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSet,
|
|
||||||
ProjectPermissionSub,
|
|
||||||
ProjectPermissionV2Schema,
|
|
||||||
SecretSubjectFields
|
|
||||||
} from "./project-permission";
|
|
||||||
|
|
||||||
export function throwIfMissingSecretReadValueOrDescribePermission(
|
|
||||||
permission: MongoAbility<ProjectPermissionSet> | PureAbility,
|
|
||||||
action: Extract<
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSecretActions.ReadValue | ProjectPermissionSecretActions.DescribeSecret
|
|
||||||
>,
|
|
||||||
subjectFields?: SecretSubjectFields
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
if (subjectFields) {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, subjectFields)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
if (subjectFields) {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(action, subject(ProjectPermissionSub.Secrets, subjectFields));
|
|
||||||
} else {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(action, ProjectPermissionSub.Secrets);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasSecretReadValueOrDescribePermission(
|
|
||||||
permission: MongoAbility<ProjectPermissionSet>,
|
|
||||||
action: Extract<
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret | ProjectPermissionSecretActions.ReadValue
|
|
||||||
>,
|
|
||||||
subjectFields?: SecretSubjectFields
|
|
||||||
) {
|
|
||||||
let canNewPermission = false;
|
|
||||||
let canOldPermission = false;
|
|
||||||
|
|
||||||
if (subjectFields) {
|
|
||||||
canNewPermission = permission.can(action, subject(ProjectPermissionSub.Secrets, subjectFields));
|
|
||||||
canOldPermission = permission.can(
|
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
subject(ProjectPermissionSub.Secrets, subjectFields)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
canNewPermission = permission.can(action, ProjectPermissionSub.Secrets);
|
|
||||||
canOldPermission = permission.can(
|
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return canNewPermission || canOldPermission;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OptionalArrayPermissionSchema = ProjectPermissionV2Schema.array().optional();
|
|
||||||
export function checkForInvalidPermissionCombination(permissions: z.infer<typeof OptionalArrayPermissionSchema>) {
|
|
||||||
if (!permissions) return;
|
|
||||||
|
|
||||||
for (const permission of permissions) {
|
|
||||||
if (permission.subject === ProjectPermissionSub.Secrets) {
|
|
||||||
if (permission.action.includes(ProjectPermissionSecretActions.DescribeAndReadValue)) {
|
|
||||||
const hasReadValue = permission.action.includes(ProjectPermissionSecretActions.ReadValue);
|
|
||||||
const hasDescribeSecret = permission.action.includes(ProjectPermissionSecretActions.DescribeSecret);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
if (!hasReadValue && !hasDescribeSecret) continue;
|
|
||||||
|
|
||||||
const hasBothDescribeAndReadValue = hasReadValue && hasDescribeSecret;
|
|
||||||
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `You have selected Read, and ${
|
|
||||||
hasBothDescribeAndReadValue
|
|
||||||
? "both Read Value and Describe Secret"
|
|
||||||
: hasReadValue
|
|
||||||
? "Read Value"
|
|
||||||
: hasDescribeSecret
|
|
||||||
? "Describe Secret"
|
|
||||||
: ""
|
|
||||||
}. You cannot select Read Value or Describe Secret if you have selected Read. The Read permission is a legacy action which has been replaced by Describe Secret and Read Value.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
||||||
if (!actorAuthMethod) return false;
|
if (!actorAuthMethod) return false;
|
||||||
|
|
||||||
|
@@ -5,6 +5,22 @@ import { PermissionConditionOperators } from "@app/lib/casl";
|
|||||||
|
|
||||||
export const PermissionConditionSchema = {
|
export const PermissionConditionSchema = {
|
||||||
[PermissionConditionOperators.$IN]: z.string().trim().min(1).array(),
|
[PermissionConditionOperators.$IN]: z.string().trim().min(1).array(),
|
||||||
|
[PermissionConditionOperators.$ALL]: z.string().trim().min(1).array(),
|
||||||
|
[PermissionConditionOperators.$REGEX]: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.refine(
|
||||||
|
(el) => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
|
new RegExp(el);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ message: "Invalid regex pattern" }
|
||||||
|
),
|
||||||
[PermissionConditionOperators.$EQ]: z.string().min(1),
|
[PermissionConditionOperators.$EQ]: z.string().min(1),
|
||||||
[PermissionConditionOperators.$NEQ]: z.string().min(1),
|
[PermissionConditionOperators.$NEQ]: z.string().min(1),
|
||||||
[PermissionConditionOperators.$GLOB]: z
|
[PermissionConditionOperators.$GLOB]: z
|
||||||
|
@@ -17,15 +17,6 @@ export enum ProjectPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSecretActions {
|
|
||||||
DescribeAndReadValue = "read",
|
|
||||||
DescribeSecret = "describeSecret",
|
|
||||||
ReadValue = "readValue",
|
|
||||||
Create = "create",
|
|
||||||
Edit = "edit",
|
|
||||||
Delete = "delete"
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProjectPermissionCmekActions {
|
export enum ProjectPermissionCmekActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@@ -124,7 +115,7 @@ export type IdentityManagementSubjectFields = {
|
|||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionSecretActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
||||||
]
|
]
|
||||||
| [
|
| [
|
||||||
@@ -438,7 +429,6 @@ const GeneralPermissionSchema = [
|
|||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
// Do not update this schema anymore, as it's kept purely for backwards compatability. Update V2 schema only.
|
|
||||||
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
@@ -470,7 +460,7 @@ export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
|||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionSecretActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
),
|
),
|
||||||
conditions: SecretConditionV2Schema.describe(
|
conditions: SecretConditionV2Schema.describe(
|
||||||
@@ -527,6 +517,7 @@ const buildAdminPermissionRules = () => {
|
|||||||
|
|
||||||
// Admins get full access to everything
|
// Admins get full access to everything
|
||||||
[
|
[
|
||||||
|
ProjectPermissionSub.Secrets,
|
||||||
ProjectPermissionSub.SecretFolders,
|
ProjectPermissionSub.SecretFolders,
|
||||||
ProjectPermissionSub.SecretImports,
|
ProjectPermissionSub.SecretImports,
|
||||||
ProjectPermissionSub.SecretApproval,
|
ProjectPermissionSub.SecretApproval,
|
||||||
@@ -559,22 +550,10 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionActions.Delete
|
ProjectPermissionActions.Delete
|
||||||
],
|
],
|
||||||
el
|
el as ProjectPermissionSub
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
can(
|
|
||||||
[
|
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
|
||||||
ProjectPermissionSecretActions.Create,
|
|
||||||
ProjectPermissionSecretActions.Edit,
|
|
||||||
ProjectPermissionSecretActions.Delete
|
|
||||||
],
|
|
||||||
ProjectPermissionSub.Secrets
|
|
||||||
);
|
|
||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
@@ -634,12 +613,10 @@ const buildMemberPermissionRules = () => {
|
|||||||
|
|
||||||
can(
|
can(
|
||||||
[
|
[
|
||||||
ProjectPermissionSecretActions.DescribeAndReadValue,
|
ProjectPermissionActions.Read,
|
||||||
ProjectPermissionSecretActions.DescribeSecret,
|
ProjectPermissionActions.Edit,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
ProjectPermissionActions.Create,
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Delete
|
||||||
ProjectPermissionSecretActions.Create,
|
|
||||||
ProjectPermissionSecretActions.Delete
|
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Secrets
|
ProjectPermissionSub.Secrets
|
||||||
);
|
);
|
||||||
@@ -811,9 +788,7 @@ export const projectMemberPermissions = buildMemberPermissionRules();
|
|||||||
const buildViewerPermissionRules = () => {
|
const buildViewerPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionSecretActions.DescribeAndReadValue, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
can(ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSub.Secrets);
|
|
||||||
can(ProjectPermissionSecretActions.ReadValue, ProjectPermissionSub.Secrets);
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
@@ -862,6 +837,7 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
(subject) => {
|
(subject) => {
|
||||||
if (canWrite) {
|
if (canWrite) {
|
||||||
can(ProjectPermissionActions.Edit, subject, {
|
can(ProjectPermissionActions.Edit, subject, {
|
||||||
|
// TODO: @Akhi
|
||||||
// @ts-expect-error type
|
// @ts-expect-error type
|
||||||
secretPath: { $glob: secretPath },
|
secretPath: { $glob: secretPath },
|
||||||
environment
|
environment
|
||||||
@@ -940,17 +916,7 @@ export const backfillPermissionV1SchemaToV2Schema = (
|
|||||||
subject: ProjectPermissionSub.SecretImports as const
|
subject: ProjectPermissionSub.SecretImports as const
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const secretPolicies = secretSubjects.map(({ subject, ...el }) => ({
|
|
||||||
subject: ProjectPermissionSub.Secrets as const,
|
|
||||||
...el,
|
|
||||||
action:
|
|
||||||
el.action.includes(ProjectPermissionActions.Read) && !el.action.includes(ProjectPermissionSecretActions.ReadValue)
|
|
||||||
? el.action.concat(ProjectPermissionSecretActions.ReadValue)
|
|
||||||
: el.action
|
|
||||||
}));
|
|
||||||
|
|
||||||
const secretFolderPolicies = secretSubjects
|
const secretFolderPolicies = secretSubjects
|
||||||
|
|
||||||
.map(({ subject, ...el }) => ({
|
.map(({ subject, ...el }) => ({
|
||||||
...el,
|
...el,
|
||||||
// read permission is not needed anymore
|
// read permission is not needed anymore
|
||||||
@@ -992,7 +958,6 @@ export const backfillPermissionV1SchemaToV2Schema = (
|
|||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore-error this is valid ts
|
// @ts-ignore-error this is valid ts
|
||||||
secretImportPolicies,
|
secretImportPolicies,
|
||||||
secretPolicies,
|
|
||||||
dynamicSecretPolicies,
|
dynamicSecretPolicies,
|
||||||
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
||||||
);
|
);
|
||||||
|
@@ -3,7 +3,7 @@ import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
|||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { ActionProjectType, TableName } from "@app/db/schemas";
|
import { ActionProjectType, TableName } from "@app/db/schemas";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
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/sanitizedSchema/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
@@ -76,13 +76,9 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
// 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
|
||||||
targetUserPermission.update(targetUserPermission.rules.concat(customPermission));
|
targetUserPermission.update(targetUserPermission.rules.concat(customPermission));
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, targetUserPermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetUserPermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update more privileged user",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
@@ -167,13 +163,9 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
// 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
|
||||||
targetUserPermission.update(targetUserPermission.rules.concat(dto.permissions || []));
|
targetUserPermission.update(targetUserPermission.rules.concat(dto.permissions || []));
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, targetUserPermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetUserPermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (dto?.slug) {
|
if (dto?.slug) {
|
||||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||||
|
@@ -6,7 +6,6 @@ import {
|
|||||||
SecretEncryptionAlgo,
|
SecretEncryptionAlgo,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
SecretType,
|
SecretType,
|
||||||
TableName,
|
|
||||||
TSecretApprovalRequestsSecretsInsert,
|
TSecretApprovalRequestsSecretsInsert,
|
||||||
TSecretApprovalRequestsSecretsV2Insert
|
TSecretApprovalRequestsSecretsV2Insert
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@@ -58,9 +57,8 @@ import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
|||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { throwIfMissingSecretReadValueOrDescribePermission } from "../permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionSecretActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
import { TSecretApprovalPolicyDALFactory } from "../secret-approval-policy/secret-approval-policy-dal";
|
||||||
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
import { TSecretSnapshotServiceFactory } from "../secret-snapshot/secret-snapshot-service";
|
||||||
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
import { TSecretApprovalRequestDALFactory } from "./secret-approval-request-dal";
|
||||||
@@ -90,12 +88,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretDAL: TSecretDALFactory;
|
secretDAL: TSecretDALFactory;
|
||||||
secretTagDAL: Pick<
|
secretTagDAL: Pick<
|
||||||
TSecretTagDALFactory,
|
TSecretTagDALFactory,
|
||||||
| "findManyTagsById"
|
"findManyTagsById" | "saveTagsToSecret" | "deleteTagsManySecret" | "saveTagsToSecretV2" | "deleteTagsToSecretV2"
|
||||||
| "saveTagsToSecret"
|
|
||||||
| "deleteTagsManySecret"
|
|
||||||
| "saveTagsToSecretV2"
|
|
||||||
| "deleteTagsToSecretV2"
|
|
||||||
| "find"
|
|
||||||
>;
|
>;
|
||||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "findOne">;
|
||||||
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
snapshotService: Pick<TSecretSnapshotServiceFactory, "performSnapshot">;
|
||||||
@@ -113,7 +106,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
||||||
secretV2BridgeDAL: Pick<
|
secretV2BridgeDAL: Pick<
|
||||||
TSecretV2BridgeDALFactory,
|
TSecretV2BridgeDALFactory,
|
||||||
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany" | "find"
|
"insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "bulkUpdate" | "deleteMany"
|
||||||
>;
|
>;
|
||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
@@ -510,7 +503,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (!hasMinApproval && !isSoftEnforcement)
|
if (!hasMinApproval && !isSoftEnforcement)
|
||||||
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
throw new BadRequestError({ message: "Doesn't have minimum approvals needed" });
|
||||||
|
|
||||||
const { botKey, shouldUseSecretV2Bridge, project } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
let mergeStatus;
|
let mergeStatus;
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
// this cycle if for bridged secrets
|
// this cycle if for bridged secrets
|
||||||
@@ -868,6 +861,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
if (isSoftEnforcement) {
|
if (isSoftEnforcement) {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
|
const project = await projectDAL.findProjectById(projectId);
|
||||||
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
const requestedByUser = await userDAL.findOne({ id: actorId });
|
const requestedByUser = await userDAL.findOne({ id: actorId });
|
||||||
const approverUsers = await userDAL.find({
|
const approverUsers = await userDAL.find({
|
||||||
@@ -919,11 +913,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
});
|
});
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
ProjectPermissionActions.Read,
|
||||||
environment,
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
secretPath
|
);
|
||||||
});
|
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(projectId);
|
await projectDAL.checkProjectUpgradeStatus(projectId);
|
||||||
|
|
||||||
@@ -1008,7 +1001,6 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
: keyName2BlindIndex[secretName];
|
: keyName2BlindIndex[secretName];
|
||||||
// add tags
|
// add tags
|
||||||
if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds;
|
if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
...el,
|
...el,
|
||||||
@@ -1164,8 +1156,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
environment: env.name,
|
environment: env.name,
|
||||||
secretPath,
|
secretPath,
|
||||||
projectId,
|
projectId,
|
||||||
requestId: secretApprovalRequest.id,
|
requestId: secretApprovalRequest.id
|
||||||
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretName) ?? []))]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1336,48 +1327,17 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
// deleted secrets
|
// deleted secrets
|
||||||
const deletedSecrets = data[SecretOperations.Delete];
|
const deletedSecrets = data[SecretOperations.Delete];
|
||||||
if (deletedSecrets && deletedSecrets.length) {
|
if (deletedSecrets && deletedSecrets.length) {
|
||||||
const secretsToDeleteInDB = await secretV2BridgeDAL.find({
|
const secretsToDeleteInDB = await secretV2BridgeDAL.findBySecretKeys(
|
||||||
folderId,
|
folderId,
|
||||||
$complex: {
|
deletedSecrets.map((el) => ({
|
||||||
operator: "and",
|
key: el.secretKey,
|
||||||
value: [
|
type: SecretType.Shared
|
||||||
{
|
}))
|
||||||
operator: "or",
|
);
|
||||||
value: deletedSecrets.map((el) => ({
|
|
||||||
operator: "and",
|
|
||||||
value: [
|
|
||||||
{
|
|
||||||
operator: "eq",
|
|
||||||
field: `${TableName.SecretV2}.key` as "key",
|
|
||||||
value: el.secretKey
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: "eq",
|
|
||||||
field: "type",
|
|
||||||
value: SecretType.Shared
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (secretsToDeleteInDB.length !== deletedSecrets.length)
|
if (secretsToDeleteInDB.length !== deletedSecrets.length)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: `Secret does not exist: ${secretsToDeleteInDB.map((el) => el.key).join(",")}`
|
message: `Secret does not exist: ${secretsToDeleteInDB.map((el) => el.key).join(",")}`
|
||||||
});
|
});
|
||||||
secretsToDeleteInDB.forEach((el) => {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionSecretActions.Delete,
|
|
||||||
subject(ProjectPermissionSub.Secrets, {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
secretName: el.key,
|
|
||||||
secretTags: el.tags?.map((i) => i.slug)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
const secretsGroupedByKey = groupBy(secretsToDeleteInDB, (i) => i.key);
|
const secretsGroupedByKey = groupBy(secretsToDeleteInDB, (i) => i.key);
|
||||||
const deletedSecretIds = deletedSecrets.map((el) => secretsGroupedByKey[el.secretKey][0].id);
|
const deletedSecretIds = deletedSecrets.map((el) => secretsGroupedByKey[el.secretKey][0].id);
|
||||||
const latestSecretVersions = await secretVersionV2BridgeDAL.findLatestVersionMany(folderId, deletedSecretIds);
|
const latestSecretVersions = await secretVersionV2BridgeDAL.findLatestVersionMany(folderId, deletedSecretIds);
|
||||||
@@ -1403,9 +1363,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const tagsGroupById = groupBy(tags, (i) => i.id);
|
const tagsGroupById = groupBy(tags, (i) => i.id);
|
||||||
|
|
||||||
commits.forEach((commit) => {
|
commits.forEach((commit) => {
|
||||||
let action = ProjectPermissionSecretActions.Create;
|
let action = ProjectPermissionActions.Create;
|
||||||
if (commit.op === SecretOperations.Update) action = ProjectPermissionSecretActions.Edit;
|
if (commit.op === SecretOperations.Update) action = ProjectPermissionActions.Edit;
|
||||||
if (commit.op === SecretOperations.Delete) return; // we do the validation on top
|
if (commit.op === SecretOperations.Delete) action = ProjectPermissionActions.Delete;
|
||||||
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
action,
|
action,
|
||||||
@@ -1496,8 +1456,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
environment: env.name,
|
environment: env.name,
|
||||||
secretPath,
|
secretPath,
|
||||||
projectId,
|
projectId,
|
||||||
requestId: secretApprovalRequest.id,
|
requestId: secretApprovalRequest.id
|
||||||
secretKeys: [...new Set(Object.values(data).flatMap((arr) => arr?.map((item) => item.secretKey) ?? []))]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -265,7 +265,6 @@ export const secretReplicationServiceFactory = ({
|
|||||||
folderDAL,
|
folderDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
viewSecretValue: true,
|
|
||||||
hasSecretAccess: () => true
|
hasSecretAccess: () => true
|
||||||
});
|
});
|
||||||
// secrets that gets replicated across imports
|
// secrets that gets replicated across imports
|
||||||
|
@@ -13,7 +13,6 @@ import { NotFoundError } from "@app/lib/errors";
|
|||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
@@ -333,7 +332,6 @@ export const secretRotationQueueFactory = ({
|
|||||||
await secretVersionV2BridgeDAL.insertMany(
|
await secretVersionV2BridgeDAL.insertMany(
|
||||||
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
|
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({
|
||||||
...el,
|
...el,
|
||||||
actorType: ActorType.PLATFORM,
|
|
||||||
secretId: id
|
secretId: id
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
|
@@ -15,11 +15,7 @@ import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret
|
|||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "../permission/project-permission";
|
|
||||||
import { TSecretRotationDALFactory } from "./secret-rotation-dal";
|
import { TSecretRotationDALFactory } from "./secret-rotation-dal";
|
||||||
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
import { TSecretRotationQueueFactory } from "./secret-rotation-queue";
|
||||||
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
import { TSecretRotationEncData } from "./secret-rotation-queue/secret-rotation-queue-types";
|
||||||
@@ -110,7 +106,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionSecretActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-argument */
|
||||||
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
|
// akhilmhdh: I did this, quite strange bug with eslint. Everything do have a type stil has this error
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, 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";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { KmsDataKey } from "@app/services/kms/kms-types";
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
import { TSecretDALFactory } from "@app/services/secret/secret-dal";
|
||||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "@app/services/secret/secret-fns";
|
|
||||||
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal";
|
||||||
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
import { TSecretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
@@ -23,16 +21,8 @@ import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secre
|
|||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
import {
|
|
||||||
hasSecretReadValueOrDescribePermission,
|
|
||||||
throwIfMissingSecretReadValueOrDescribePermission
|
|
||||||
} from "../permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "../permission/project-permission";
|
|
||||||
import {
|
import {
|
||||||
TGetSnapshotDataDTO,
|
TGetSnapshotDataDTO,
|
||||||
TProjectSnapshotCountDTO,
|
TProjectSnapshotCountDTO,
|
||||||
@@ -106,10 +96,10 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
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.
|
||||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
environment,
|
ProjectPermissionActions.Read,
|
||||||
secretPath: path
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
});
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
@@ -143,10 +133,10 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
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.
|
||||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.DescribeSecret, {
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
environment,
|
ProjectPermissionActions.Read,
|
||||||
secretPath: path
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath: path })
|
||||||
});
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
@@ -171,7 +161,6 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
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;
|
||||||
let snapshotDetails;
|
let snapshotDetails;
|
||||||
if (shouldUseBridge) {
|
if (shouldUseBridge) {
|
||||||
@@ -180,112 +169,68 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
projectId: snapshot.projectId
|
projectId: snapshot.projectId
|
||||||
});
|
});
|
||||||
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotV2DataById(id);
|
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotV2DataById(id);
|
||||||
|
|
||||||
const fullFolderPath = await getFullFolderPath({
|
|
||||||
folderDAL,
|
|
||||||
folderId: encryptedSnapshotDetails.folderId,
|
|
||||||
envId: encryptedSnapshotDetails.environment.id
|
|
||||||
});
|
|
||||||
|
|
||||||
snapshotDetails = {
|
snapshotDetails = {
|
||||||
...encryptedSnapshotDetails,
|
...encryptedSnapshotDetails,
|
||||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
||||||
const canReadValue = hasSecretReadValueOrDescribePermission(
|
...el,
|
||||||
permission,
|
secretKey: el.key,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
secretValue: el.encryptedValue
|
||||||
{
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
||||||
environment: encryptedSnapshotDetails.environment.slug,
|
: "",
|
||||||
secretPath: fullFolderPath,
|
secretComment: el.encryptedComment
|
||||||
secretName: el.key,
|
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
||||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
: ""
|
||||||
}
|
}))
|
||||||
);
|
|
||||||
|
|
||||||
let secretValue = "";
|
|
||||||
if (canReadValue) {
|
|
||||||
secretValue = el.encryptedValue
|
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedValue }).toString()
|
|
||||||
: "";
|
|
||||||
} else {
|
|
||||||
secretValue = INFISICAL_SECRET_VALUE_HIDDEN_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...el,
|
|
||||||
secretKey: el.key,
|
|
||||||
secretValueHidden: !canReadValue,
|
|
||||||
secretValue,
|
|
||||||
secretComment: el.encryptedComment
|
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.encryptedComment }).toString()
|
|
||||||
: ""
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotDataById(id);
|
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotDataById(id);
|
||||||
|
|
||||||
const fullFolderPath = await getFullFolderPath({
|
|
||||||
folderDAL,
|
|
||||||
folderId: encryptedSnapshotDetails.folderId,
|
|
||||||
envId: encryptedSnapshotDetails.environment.id
|
|
||||||
});
|
|
||||||
|
|
||||||
const { botKey } = await projectBotService.getBotKey(snapshot.projectId);
|
const { botKey } = await projectBotService.getBotKey(snapshot.projectId);
|
||||||
if (!botKey)
|
if (!botKey)
|
||||||
throw new NotFoundError({ message: `Project bot key not found for project with ID '${snapshot.projectId}'` });
|
throw new NotFoundError({ message: `Project bot key not found for project with ID '${snapshot.projectId}'` });
|
||||||
snapshotDetails = {
|
snapshotDetails = {
|
||||||
...encryptedSnapshotDetails,
|
...encryptedSnapshotDetails,
|
||||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => {
|
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
||||||
const secretKey = decryptSymmetric128BitHexKeyUTF8({
|
...el,
|
||||||
|
secretKey: decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: el.secretKeyCiphertext,
|
ciphertext: el.secretKeyCiphertext,
|
||||||
iv: el.secretKeyIV,
|
iv: el.secretKeyIV,
|
||||||
tag: el.secretKeyTag,
|
tag: el.secretKeyTag,
|
||||||
key: botKey
|
key: botKey
|
||||||
});
|
}),
|
||||||
|
secretValue: decryptSymmetric128BitHexKeyUTF8({
|
||||||
const canReadValue = hasSecretReadValueOrDescribePermission(
|
ciphertext: el.secretValueCiphertext,
|
||||||
permission,
|
iv: el.secretValueIV,
|
||||||
ProjectPermissionSecretActions.ReadValue,
|
tag: el.secretValueTag,
|
||||||
{
|
key: botKey
|
||||||
environment: encryptedSnapshotDetails.environment.slug,
|
}),
|
||||||
secretPath: fullFolderPath,
|
secretComment:
|
||||||
secretName: secretKey,
|
el.secretCommentTag && el.secretCommentIV && el.secretCommentCiphertext
|
||||||
secretTags: el.tags.length ? el.tags.map((tag) => tag.slug) : undefined
|
? decryptSymmetric128BitHexKeyUTF8({
|
||||||
}
|
ciphertext: el.secretCommentCiphertext,
|
||||||
);
|
iv: el.secretCommentIV,
|
||||||
|
tag: el.secretCommentTag,
|
||||||
let secretValue = "";
|
key: botKey
|
||||||
|
})
|
||||||
if (canReadValue) {
|
: ""
|
||||||
secretValue = decryptSymmetric128BitHexKeyUTF8({
|
}))
|
||||||
ciphertext: el.secretValueCiphertext,
|
|
||||||
iv: el.secretValueIV,
|
|
||||||
tag: el.secretValueTag,
|
|
||||||
key: botKey
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
secretValue = INFISICAL_SECRET_VALUE_HIDDEN_MASK;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...el,
|
|
||||||
secretKey,
|
|
||||||
secretValueHidden: !canReadValue,
|
|
||||||
secretValue,
|
|
||||||
secretComment:
|
|
||||||
el.secretCommentTag && el.secretCommentIV && el.secretCommentCiphertext
|
|
||||||
? decryptSymmetric128BitHexKeyUTF8({
|
|
||||||
ciphertext: el.secretCommentCiphertext,
|
|
||||||
iv: el.secretCommentIV,
|
|
||||||
tag: el.secretCommentTag,
|
|
||||||
key: botKey
|
|
||||||
})
|
|
||||||
: ""
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fullFolderPath = await getFullFolderPath({
|
||||||
|
folderDAL,
|
||||||
|
folderId: snapshotDetails.folderId,
|
||||||
|
envId: snapshotDetails.environment.id
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment: snapshotDetails.environment.slug,
|
||||||
|
secretPath: fullFolderPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return snapshotDetails;
|
return snapshotDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -425,21 +370,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
const secrets = await secretV2BridgeDAL.insertMany(
|
const secrets = await secretV2BridgeDAL.insertMany(
|
||||||
rollbackSnaps.flatMap(({ secretVersions, folderId }) =>
|
rollbackSnaps.flatMap(({ secretVersions, folderId }) =>
|
||||||
secretVersions.map(
|
secretVersions.map(
|
||||||
({
|
({ latestSecretVersion, version, updatedAt, createdAt, secretId, envId, id, tags, ...el }) => ({
|
||||||
latestSecretVersion,
|
|
||||||
version,
|
|
||||||
updatedAt,
|
|
||||||
createdAt,
|
|
||||||
secretId,
|
|
||||||
envId,
|
|
||||||
id,
|
|
||||||
tags,
|
|
||||||
// exclude the bottom fields from the secret - they are for versioning only.
|
|
||||||
userActorId,
|
|
||||||
identityActorId,
|
|
||||||
actorType,
|
|
||||||
...el
|
|
||||||
}) => ({
|
|
||||||
...el,
|
...el,
|
||||||
id: secretId,
|
id: secretId,
|
||||||
version: deletedTopLevelSecsGroupById[secretId] ? latestSecretVersion + 1 : latestSecretVersion,
|
version: deletedTopLevelSecsGroupById[secretId] ? latestSecretVersion + 1 : latestSecretVersion,
|
||||||
@@ -470,18 +401,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
const userActorId = actor === ActorType.USER ? actorId : undefined;
|
|
||||||
const identityActorId = actor !== ActorType.USER ? actorId : undefined;
|
|
||||||
const actorType = actor || ActorType.PLATFORM;
|
|
||||||
|
|
||||||
const secretVersions = await secretVersionV2BridgeDAL.insertMany(
|
const secretVersions = await secretVersionV2BridgeDAL.insertMany(
|
||||||
secrets.map(({ id, updatedAt, createdAt, ...el }) => ({
|
secrets.map(({ id, updatedAt, createdAt, ...el }) => ({ ...el, secretId: id })),
|
||||||
...el,
|
|
||||||
secretId: id,
|
|
||||||
userActorId,
|
|
||||||
identityActorId,
|
|
||||||
actorType
|
|
||||||
})),
|
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await secretVersionV2TagBridgeDAL.insertMany(
|
await secretVersionV2TagBridgeDAL.insertMany(
|
||||||
|
@@ -459,8 +459,7 @@ export const PROJECTS = {
|
|||||||
workspaceId: "The ID of the project to update.",
|
workspaceId: "The ID of the project to update.",
|
||||||
name: "The new name of the project.",
|
name: "The new name of the project.",
|
||||||
projectDescription: "An optional description label for the project.",
|
projectDescription: "An optional description label for the project.",
|
||||||
autoCapitalization: "Disable or enable auto-capitalization for the project.",
|
autoCapitalization: "Disable or enable auto-capitalization for the project."
|
||||||
slug: "An optional slug for the project. (must be unique within the organization)"
|
|
||||||
},
|
},
|
||||||
GET_KEY: {
|
GET_KEY: {
|
||||||
workspaceId: "The ID of the project to get the key from."
|
workspaceId: "The ID of the project to get the key from."
|
||||||
@@ -667,7 +666,6 @@ export const SECRETS = {
|
|||||||
secretPath: "The path of the secret to attach tags to.",
|
secretPath: "The path of the secret to attach tags to.",
|
||||||
type: "The type of the secret to attach tags to. (shared/personal)",
|
type: "The type of the secret to attach tags to. (shared/personal)",
|
||||||
environment: "The slug of the environment where the secret is located",
|
environment: "The slug of the environment where the secret is located",
|
||||||
viewSecretValue: "Whether or not to retrieve the secret value.",
|
|
||||||
projectSlug: "The slug of the project where the secret is located.",
|
projectSlug: "The slug of the project where the secret is located.",
|
||||||
tagSlugs: "An array of existing tag slugs to attach to the secret."
|
tagSlugs: "An array of existing tag slugs to attach to the secret."
|
||||||
},
|
},
|
||||||
@@ -691,7 +689,6 @@ export const RAW_SECRETS = {
|
|||||||
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
||||||
environment: "The slug of the environment to list secrets from.",
|
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.",
|
||||||
viewSecretValue: "Whether or not to retrieve the secret value.",
|
|
||||||
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:
|
metadataFilter:
|
||||||
@@ -720,7 +717,6 @@ export const RAW_SECRETS = {
|
|||||||
secretPath: "The path of the secret to get.",
|
secretPath: "The path of the secret to get.",
|
||||||
version: "The version of the secret to get.",
|
version: "The version of the secret to get.",
|
||||||
type: "The type of the secret to get.",
|
type: "The type of the secret to get.",
|
||||||
viewSecretValue: "Whether or not to retrieve the secret value.",
|
|
||||||
includeImports: "Weather to include imported secrets or not."
|
includeImports: "Weather to include imported secrets or not."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@@ -1725,8 +1721,7 @@ export const SecretSyncs = {
|
|||||||
SYNC_OPTIONS: (destination: SecretSync) => {
|
SYNC_OPTIONS: (destination: SecretSync) => {
|
||||||
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
const destinationName = SECRET_SYNC_NAME_MAP[destination];
|
||||||
return {
|
return {
|
||||||
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
|
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`
|
||||||
disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
ADDITIONAL_SYNC_OPTIONS: {
|
ADDITIONAL_SYNC_OPTIONS: {
|
||||||
@@ -1772,12 +1767,6 @@ export const SecretSyncs = {
|
|||||||
},
|
},
|
||||||
DATABRICKS: {
|
DATABRICKS: {
|
||||||
scope: "The Databricks secret scope that secrets should be synced to."
|
scope: "The Databricks secret scope that secrets should be synced to."
|
||||||
},
|
|
||||||
HUMANITEC: {
|
|
||||||
app: "The ID of the Humanitec app to sync secrets to.",
|
|
||||||
org: "The ID of the Humanitec org to sync secrets to.",
|
|
||||||
env: "The ID of the Humanitec environment to sync secrets to.",
|
|
||||||
scope: "The Humanitec scope that secrets should be synced to."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,669 +0,0 @@
|
|||||||
import { createMongoAbility } from "@casl/ability";
|
|
||||||
|
|
||||||
import { PermissionConditionOperators } from ".";
|
|
||||||
import { validatePermissionBoundary } from "./boundary";
|
|
||||||
|
|
||||||
describe("Validate Permission Boundary Function", () => {
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
title: "child with equal privilege",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit", "delete", "read"],
|
|
||||||
subject: "secrets"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit", "delete", "read"],
|
|
||||||
subject: "secrets"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
expectValid: true,
|
|
||||||
missingPermissions: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "child with less privilege",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit", "delete", "read"],
|
|
||||||
subject: "secrets"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit"],
|
|
||||||
subject: "secrets"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
expectValid: true,
|
|
||||||
missingPermissions: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "child with more privilege",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit"],
|
|
||||||
subject: "secrets"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
expectValid: false,
|
|
||||||
missingPermissions: [{ action: "edit", subject: "secrets" }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "parent with multiple and child with multiple",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: ["create", "edit"],
|
|
||||||
subject: "members"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "members"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
expectValid: true,
|
|
||||||
missingPermissions: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Child with no access",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: ["create", "edit"],
|
|
||||||
subject: "members"
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([]),
|
|
||||||
expectValid: true,
|
|
||||||
missingPermissions: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Parent and child disjoint set",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit", "delete", "read"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit", "delete", "read"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
expectValid: false,
|
|
||||||
missingPermissions: ["create", "edit", "delete", "read"].map((el) => ({
|
|
||||||
action: el,
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Parent with inverted rules",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit", "delete", "read"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "read",
|
|
||||||
subject: "secrets",
|
|
||||||
inverted: true,
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" },
|
|
||||||
secretPath: { [PermissionConditionOperators.$GLOB]: "/hello/**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: "read",
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" },
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "/" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
expectValid: true,
|
|
||||||
missingPermissions: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Parent with inverted rules - child accessing invalid one",
|
|
||||||
parentPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "edit", "delete", "read"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
action: "read",
|
|
||||||
subject: "secrets",
|
|
||||||
inverted: true,
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" },
|
|
||||||
secretPath: { [PermissionConditionOperators.$GLOB]: "/hello/**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: "read",
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" },
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "/hello/world" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
expectValid: false,
|
|
||||||
missingPermissions: [
|
|
||||||
{
|
|
||||||
action: "read",
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" },
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "/hello/world" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
])("Check permission: $title", ({ parentPermission, childPermission, expectValid, missingPermissions }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
if (expectValid) {
|
|
||||||
expect(permissionBoundary.isValid).toBeTruthy();
|
|
||||||
} else {
|
|
||||||
expect(permissionBoundary.isValid).toBeFalsy();
|
|
||||||
expect(permissionBoundary.missingPermissions).toEqual(expect.arrayContaining(missingPermissions));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Validate Permission Boundary: Checking Parent $eq operator", () => {
|
|
||||||
const parentPermission = createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "read"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$IN]: ["dev"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$GLOB]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator truthy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "prod" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$IN]: ["dev", "prod"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$GLOB]: "dev**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$NEQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$GLOB]: "staging" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator falsy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Validate Permission Boundary: Checking Parent $neq operator", () => {
|
|
||||||
const parentPermission = createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "read"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$NEQ]: "/hello" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "/" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$NEQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$NEQ]: "/hello" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$IN]: ["/", "/staging"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$GLOB]: "/dev**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator truthy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "/hello" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$NEQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$NEQ]: "/" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$IN]: ["/", "/hello"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$GLOB]: "/hello**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator falsy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Validate Permission Boundary: Checking Parent $IN operator", () => {
|
|
||||||
const parentPermission = createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$IN]: ["dev", "staging"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$IN]: ["dev"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: `${PermissionConditionOperators.$IN} - 2`,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$IN]: ["dev", "staging"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$GLOB]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator truthy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$EQ]: "prod" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$NEQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$NEQ]: "dev" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$IN]: ["dev", "prod"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["edit"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
environment: { [PermissionConditionOperators.$GLOB]: "dev**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator falsy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Validate Permission Boundary: Checking Parent $GLOB operator", () => {
|
|
||||||
const parentPermission = createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create", "read"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$GLOB]: "/hello/**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "/hello/world" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$IN]: ["/hello/world", "/hello/world2"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$GLOB]: "/hello/**/world" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator truthy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
test.each([
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$EQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$EQ]: "/print" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$NEQ,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$NEQ]: "/hello/world" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$IN,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$IN]: ["/", "/hello"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
operator: PermissionConditionOperators.$GLOB,
|
|
||||||
childPermission: createMongoAbility([
|
|
||||||
{
|
|
||||||
action: ["create"],
|
|
||||||
subject: "secrets",
|
|
||||||
conditions: {
|
|
||||||
secretPath: { [PermissionConditionOperators.$GLOB]: "/hello**" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
||||||
])("Child $operator falsy cases", ({ childPermission }) => {
|
|
||||||
const permissionBoundary = validatePermissionBoundary(parentPermission, childPermission);
|
|
||||||
expect(permissionBoundary.isValid).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,249 +0,0 @@
|
|||||||
import { MongoAbility } from "@casl/ability";
|
|
||||||
import { MongoQuery } from "@ucast/mongo2js";
|
|
||||||
import picomatch from "picomatch";
|
|
||||||
|
|
||||||
import { PermissionConditionOperators } from "./index";
|
|
||||||
|
|
||||||
type TMissingPermission = {
|
|
||||||
action: string;
|
|
||||||
subject: string;
|
|
||||||
conditions?: MongoQuery;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TPermissionConditionShape = {
|
|
||||||
[PermissionConditionOperators.$EQ]: string;
|
|
||||||
[PermissionConditionOperators.$NEQ]: string;
|
|
||||||
[PermissionConditionOperators.$GLOB]: string;
|
|
||||||
[PermissionConditionOperators.$IN]: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPermissionSetID = (action: string, subject: string) => `${action}:${subject}`;
|
|
||||||
const invertTheOperation = (shouldInvert: boolean, operation: boolean) => (shouldInvert ? !operation : operation);
|
|
||||||
const formatConditionOperator = (condition: TPermissionConditionShape | string) => {
|
|
||||||
return (
|
|
||||||
typeof condition === "string" ? { [PermissionConditionOperators.$EQ]: condition } : condition
|
|
||||||
) as TPermissionConditionShape;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isOperatorsASubset = (parentSet: TPermissionConditionShape, subset: TPermissionConditionShape) => {
|
|
||||||
// we compute each operator against each other in left hand side and right hand side
|
|
||||||
if (subset[PermissionConditionOperators.$EQ] || subset[PermissionConditionOperators.$NEQ]) {
|
|
||||||
const subsetOperatorValue = subset[PermissionConditionOperators.$EQ] || subset[PermissionConditionOperators.$NEQ];
|
|
||||||
const isInverted = !subset[PermissionConditionOperators.$EQ];
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$EQ] &&
|
|
||||||
invertTheOperation(isInverted, parentSet[PermissionConditionOperators.$EQ] !== subsetOperatorValue)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$NEQ] &&
|
|
||||||
invertTheOperation(isInverted, parentSet[PermissionConditionOperators.$NEQ] === subsetOperatorValue)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$IN] &&
|
|
||||||
invertTheOperation(isInverted, !parentSet[PermissionConditionOperators.$IN].includes(subsetOperatorValue))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// ne and glob cannot match each other
|
|
||||||
if (parentSet[PermissionConditionOperators.$GLOB] && isInverted) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$GLOB] &&
|
|
||||||
!picomatch.isMatch(subsetOperatorValue, parentSet[PermissionConditionOperators.$GLOB], { strictSlashes: false })
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (subset[PermissionConditionOperators.$IN]) {
|
|
||||||
const subsetOperatorValue = subset[PermissionConditionOperators.$IN];
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$EQ] &&
|
|
||||||
(subsetOperatorValue.length !== 1 || subsetOperatorValue[0] !== parentSet[PermissionConditionOperators.$EQ])
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$NEQ] &&
|
|
||||||
subsetOperatorValue.includes(parentSet[PermissionConditionOperators.$NEQ])
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$IN] &&
|
|
||||||
!subsetOperatorValue.every((el) => parentSet[PermissionConditionOperators.$IN].includes(el))
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$GLOB] &&
|
|
||||||
!subsetOperatorValue.every((el) =>
|
|
||||||
picomatch.isMatch(el, parentSet[PermissionConditionOperators.$GLOB], {
|
|
||||||
strictSlashes: false
|
|
||||||
})
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (subset[PermissionConditionOperators.$GLOB]) {
|
|
||||||
const subsetOperatorValue = subset[PermissionConditionOperators.$GLOB];
|
|
||||||
const { isGlob } = picomatch.scan(subsetOperatorValue);
|
|
||||||
// if it's glob, all other fixed operators would make this superset because glob is powerful. like eq
|
|
||||||
// example: $in [dev, prod] => glob: dev** could mean anything starting with dev: thus is bigger
|
|
||||||
if (
|
|
||||||
isGlob &&
|
|
||||||
Object.keys(parentSet).some(
|
|
||||||
(el) => el !== PermissionConditionOperators.$GLOB && el !== PermissionConditionOperators.$NEQ
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$EQ] &&
|
|
||||||
parentSet[PermissionConditionOperators.$EQ] !== subsetOperatorValue
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$NEQ] &&
|
|
||||||
picomatch.isMatch(parentSet[PermissionConditionOperators.$NEQ], subsetOperatorValue, {
|
|
||||||
strictSlashes: false
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// if parent set is IN, glob cannot be used for children - It's a bigger scope
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$IN] &&
|
|
||||||
!parentSet[PermissionConditionOperators.$IN].includes(subsetOperatorValue)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
parentSet[PermissionConditionOperators.$GLOB] &&
|
|
||||||
!picomatch.isMatch(subsetOperatorValue, parentSet[PermissionConditionOperators.$GLOB], {
|
|
||||||
strictSlashes: false
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isSubsetForSamePermissionSubjectAction = (
|
|
||||||
parentSetRules: ReturnType<MongoAbility["possibleRulesFor"]>,
|
|
||||||
subsetRules: ReturnType<MongoAbility["possibleRulesFor"]>,
|
|
||||||
appendToMissingPermission: (condition?: MongoQuery) => void
|
|
||||||
) => {
|
|
||||||
const isMissingConditionInParent = parentSetRules.every((el) => !el.conditions);
|
|
||||||
if (isMissingConditionInParent) return true;
|
|
||||||
|
|
||||||
// all subset rules must pass in comparison to parent rul
|
|
||||||
return subsetRules.every((subsetRule) => {
|
|
||||||
const subsetRuleConditions = subsetRule.conditions as Record<string, TPermissionConditionShape | string>;
|
|
||||||
// compare subset rule with all parent rules
|
|
||||||
const isSubsetOfNonInvertedParentSet = parentSetRules
|
|
||||||
.filter((el) => !el.inverted)
|
|
||||||
.some((parentSetRule) => {
|
|
||||||
// get conditions and iterate
|
|
||||||
const parentSetRuleConditions = parentSetRule?.conditions as Record<string, TPermissionConditionShape | string>;
|
|
||||||
if (!parentSetRuleConditions) return true;
|
|
||||||
return Object.keys(parentSetRuleConditions).every((parentConditionField) => {
|
|
||||||
// if parent condition is missing then it's never a subset
|
|
||||||
if (!subsetRuleConditions?.[parentConditionField]) return false;
|
|
||||||
|
|
||||||
// standardize the conditions plain string operator => $eq function
|
|
||||||
const parentRuleConditionOperators = formatConditionOperator(parentSetRuleConditions[parentConditionField]);
|
|
||||||
const selectedSubsetRuleCondition = subsetRuleConditions?.[parentConditionField];
|
|
||||||
const subsetRuleConditionOperators = formatConditionOperator(selectedSubsetRuleCondition);
|
|
||||||
return isOperatorsASubset(parentRuleConditionOperators, subsetRuleConditionOperators);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const invertedParentSetRules = parentSetRules.filter((el) => el.inverted);
|
|
||||||
const isNotSubsetOfInvertedParentSet = invertedParentSetRules.length
|
|
||||||
? !invertedParentSetRules.some((parentSetRule) => {
|
|
||||||
// get conditions and iterate
|
|
||||||
const parentSetRuleConditions = parentSetRule?.conditions as Record<
|
|
||||||
string,
|
|
||||||
TPermissionConditionShape | string
|
|
||||||
>;
|
|
||||||
if (!parentSetRuleConditions) return true;
|
|
||||||
return Object.keys(parentSetRuleConditions).every((parentConditionField) => {
|
|
||||||
// if parent condition is missing then it's never a subset
|
|
||||||
if (!subsetRuleConditions?.[parentConditionField]) return false;
|
|
||||||
|
|
||||||
// standardize the conditions plain string operator => $eq function
|
|
||||||
const parentRuleConditionOperators = formatConditionOperator(parentSetRuleConditions[parentConditionField]);
|
|
||||||
const selectedSubsetRuleCondition = subsetRuleConditions?.[parentConditionField];
|
|
||||||
const subsetRuleConditionOperators = formatConditionOperator(selectedSubsetRuleCondition);
|
|
||||||
return isOperatorsASubset(parentRuleConditionOperators, subsetRuleConditionOperators);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
: true;
|
|
||||||
const isSubset = isSubsetOfNonInvertedParentSet && isNotSubsetOfInvertedParentSet;
|
|
||||||
if (!isSubset) {
|
|
||||||
appendToMissingPermission(subsetRule.conditions);
|
|
||||||
}
|
|
||||||
return isSubset;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validatePermissionBoundary = (parentSetPermissions: MongoAbility, subsetPermissions: MongoAbility) => {
|
|
||||||
const checkedPermissionRules = new Set<string>();
|
|
||||||
const missingPermissions: TMissingPermission[] = [];
|
|
||||||
|
|
||||||
subsetPermissions.rules.forEach((subsetPermissionRules) => {
|
|
||||||
const subsetPermissionSubject = subsetPermissionRules.subject.toString();
|
|
||||||
let subsetPermissionActions: string[] = [];
|
|
||||||
|
|
||||||
// actions can be string or string[]
|
|
||||||
if (typeof subsetPermissionRules.action === "string") {
|
|
||||||
subsetPermissionActions.push(subsetPermissionRules.action);
|
|
||||||
} else {
|
|
||||||
subsetPermissionRules.action.forEach((subsetPermissionAction) => {
|
|
||||||
subsetPermissionActions.push(subsetPermissionAction);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// if action is already processed ignore
|
|
||||||
subsetPermissionActions = subsetPermissionActions.filter(
|
|
||||||
(el) => !checkedPermissionRules.has(getPermissionSetID(el, subsetPermissionSubject))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!subsetPermissionActions.length) return;
|
|
||||||
subsetPermissionActions.forEach((subsetPermissionAction) => {
|
|
||||||
const parentSetRulesOfSubset = parentSetPermissions.possibleRulesFor(
|
|
||||||
subsetPermissionAction,
|
|
||||||
subsetPermissionSubject
|
|
||||||
);
|
|
||||||
const nonInveretedOnes = parentSetRulesOfSubset.filter((el) => !el.inverted);
|
|
||||||
if (!nonInveretedOnes.length) {
|
|
||||||
missingPermissions.push({ action: subsetPermissionAction, subject: subsetPermissionSubject });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subsetRules = subsetPermissions.possibleRulesFor(subsetPermissionAction, subsetPermissionSubject);
|
|
||||||
isSubsetForSamePermissionSubjectAction(parentSetRulesOfSubset, subsetRules, (conditions) => {
|
|
||||||
missingPermissions.push({ action: subsetPermissionAction, subject: subsetPermissionSubject, conditions });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
subsetPermissionActions.forEach((el) =>
|
|
||||||
checkedPermissionRules.add(getPermissionSetID(el, subsetPermissionSubject))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (missingPermissions.length) {
|
|
||||||
return { isValid: false as const, missingPermissions };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isValid: true };
|
|
||||||
};
|
|
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
import { buildMongoQueryMatcher } from "@casl/ability";
|
import { buildMongoQueryMatcher, MongoAbility } from "@casl/ability";
|
||||||
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
@@ -20,8 +20,45 @@ const glob: JsInterpreter<FieldCondition<string>> = (node, object, context) => {
|
|||||||
|
|
||||||
export const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
|
export const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts and formats permissions from a CASL Ability object or a raw permission set.
|
||||||
|
*/
|
||||||
|
const extractPermissions = (ability: MongoAbility) => {
|
||||||
|
const permissions: string[] = [];
|
||||||
|
ability.rules.forEach((permission) => {
|
||||||
|
if (typeof permission.action === "string") {
|
||||||
|
permissions.push(`${permission.action}_${permission.subject as string}`);
|
||||||
|
} else {
|
||||||
|
permission.action.forEach((permissionAction) => {
|
||||||
|
permissions.push(`${permissionAction}_${permission.subject as string}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return permissions;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares two sets of permissions to determine if the first set is at least as privileged as the second set.
|
||||||
|
* The function checks if all permissions in the second set are contained within the first set and if the first set has equal or more permissions.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const isAtLeastAsPrivileged = (permissions1: MongoAbility, permissions2: MongoAbility) => {
|
||||||
|
const set1 = new Set(extractPermissions(permissions1));
|
||||||
|
const set2 = new Set(extractPermissions(permissions2));
|
||||||
|
|
||||||
|
for (const perm of set2) {
|
||||||
|
if (!set1.has(perm)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return set1.size >= set2.size;
|
||||||
|
};
|
||||||
|
|
||||||
export enum PermissionConditionOperators {
|
export enum PermissionConditionOperators {
|
||||||
$IN = "$in",
|
$IN = "$in",
|
||||||
|
$ALL = "$all",
|
||||||
|
$REGEX = "$regex",
|
||||||
$EQ = "$eq",
|
$EQ = "$eq",
|
||||||
$NEQ = "$ne",
|
$NEQ = "$ne",
|
||||||
$GLOB = "$glob"
|
$GLOB = "$glob"
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
|
|
||||||
export class DatabaseError extends Error {
|
export class DatabaseError extends Error {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@@ -53,18 +52,10 @@ export class ForbiddenRequestError extends Error {
|
|||||||
|
|
||||||
error: unknown;
|
error: unknown;
|
||||||
|
|
||||||
details?: unknown;
|
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown } = {}) {
|
||||||
|
|
||||||
constructor({
|
|
||||||
name,
|
|
||||||
error,
|
|
||||||
message,
|
|
||||||
details
|
|
||||||
}: { message?: string; name?: string; error?: unknown; details?: unknown } = {}) {
|
|
||||||
super(message ?? "You are not allowed to access this resource");
|
super(message ?? "You are not allowed to access this resource");
|
||||||
this.name = name || "ForbiddenError";
|
this.name = name || "ForbiddenError";
|
||||||
this.error = error;
|
this.error = error;
|
||||||
this.details = details;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
|
|
||||||
import quicDefault, * as quicModule from "@infisical/quic";
|
import * as quic from "@infisical/quic";
|
||||||
|
|
||||||
import { BadRequestError } from "../errors";
|
import { BadRequestError } from "../errors";
|
||||||
import { logger } from "../logger";
|
import { logger } from "../logger";
|
||||||
@@ -10,8 +10,6 @@ import { logger } from "../logger";
|
|||||||
const DEFAULT_MAX_RETRIES = 3;
|
const DEFAULT_MAX_RETRIES = 3;
|
||||||
const DEFAULT_RETRY_DELAY = 1000; // 1 second
|
const DEFAULT_RETRY_DELAY = 1000; // 1 second
|
||||||
|
|
||||||
const quic = quicDefault || quicModule;
|
|
||||||
|
|
||||||
const parseSubjectDetails = (data: string) => {
|
const parseSubjectDetails = (data: string) => {
|
||||||
const values: Record<string, string> = {};
|
const values: Record<string, string> = {};
|
||||||
data.split("\n").forEach((el) => {
|
data.split("\n").forEach((el) => {
|
||||||
@@ -96,7 +94,6 @@ export const pingGatewayAndVerify = async ({
|
|||||||
error: err as Error
|
error: err as Error
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
|
for (let attempt = 1; attempt <= maxRetries; attempt += 1) {
|
||||||
try {
|
try {
|
||||||
const stream = quicClient.connection.newStream("bidi");
|
const stream = quicClient.connection.newStream("bidi");
|
||||||
@@ -109,13 +106,17 @@ export const pingGatewayAndVerify = async ({
|
|||||||
const { value, done } = await reader.read();
|
const { value, done } = await reader.read();
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
throw new Error("Gateway closed before receiving PONG");
|
throw new BadRequestError({
|
||||||
|
message: "Gateway closed before receiving PONG"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = Buffer.from(value).toString();
|
const response = Buffer.from(value).toString();
|
||||||
|
|
||||||
if (response !== "PONG\n" && response !== "PONG") {
|
if (response !== "PONG\n" && response !== "PONG") {
|
||||||
throw new Error(`Failed to Ping. Unexpected response: ${response}`);
|
throw new BadRequestError({
|
||||||
|
message: `Failed to Ping. Unexpected response: ${response}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.releaseLock();
|
reader.releaseLock();
|
||||||
@@ -143,7 +144,6 @@ interface TProxyServer {
|
|||||||
server: net.Server;
|
server: net.Server;
|
||||||
port: number;
|
port: number;
|
||||||
cleanup: () => Promise<void>;
|
cleanup: () => Promise<void>;
|
||||||
getProxyError: () => string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupProxyServer = async ({
|
const setupProxyServer = async ({
|
||||||
@@ -168,7 +168,6 @@ const setupProxyServer = async ({
|
|||||||
error: err as Error
|
error: err as Error
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const proxyErrorMsg = [""];
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const server = net.createServer();
|
const server = net.createServer();
|
||||||
@@ -184,33 +183,31 @@ const setupProxyServer = async ({
|
|||||||
const forwardWriter = stream.writable.getWriter();
|
const forwardWriter = stream.writable.getWriter();
|
||||||
await forwardWriter.write(Buffer.from(`FORWARD-TCP ${targetHost}:${targetPort}\n`));
|
await forwardWriter.write(Buffer.from(`FORWARD-TCP ${targetHost}:${targetPort}\n`));
|
||||||
forwardWriter.releaseLock();
|
forwardWriter.releaseLock();
|
||||||
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||||
// Set up bidirectional copy
|
// Set up bidirectional copy
|
||||||
const setupCopy = () => {
|
const setupCopy = async () => {
|
||||||
// Client to QUIC
|
// Client to QUIC
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
(async () => {
|
(async () => {
|
||||||
const writer = stream.writable.getWriter();
|
try {
|
||||||
|
const writer = stream.writable.getWriter();
|
||||||
|
|
||||||
// Create a handler for client data
|
// Create a handler for client data
|
||||||
clientConn.on("data", (chunk) => {
|
clientConn.on("data", async (chunk) => {
|
||||||
writer.write(chunk).catch((err) => {
|
await writer.write(chunk);
|
||||||
proxyErrorMsg.push((err as Error)?.message);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Handle client connection close
|
// Handle client connection close
|
||||||
clientConn.on("end", () => {
|
clientConn.on("end", async () => {
|
||||||
writer.close().catch((err) => {
|
await writer.close();
|
||||||
logger.error(err);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
clientConn.on("error", (clientConnErr) => {
|
clientConn.on("error", async (err) => {
|
||||||
writer.abort(clientConnErr?.message).catch((err) => {
|
await writer.abort(err);
|
||||||
proxyErrorMsg.push((err as Error)?.message);
|
|
||||||
});
|
});
|
||||||
});
|
} catch (err) {
|
||||||
|
clientConn.destroy();
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// QUIC to Client
|
// QUIC to Client
|
||||||
@@ -239,18 +236,15 @@ const setupProxyServer = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
proxyErrorMsg.push((err as Error)?.message);
|
|
||||||
clientConn.destroy();
|
clientConn.destroy();
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
};
|
};
|
||||||
|
await setupCopy();
|
||||||
setupCopy();
|
//
|
||||||
// Handle connection closure
|
// Handle connection closure
|
||||||
clientConn.on("close", () => {
|
clientConn.on("close", async () => {
|
||||||
stream.destroy().catch((err) => {
|
await stream.destroy();
|
||||||
proxyErrorMsg.push((err as Error)?.message);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const cleanup = async () => {
|
const cleanup = async () => {
|
||||||
@@ -258,18 +252,13 @@ const setupProxyServer = async ({
|
|||||||
await stream.destroy();
|
await stream.destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
clientConn.on("error", (clientConnErr) => {
|
clientConn.on("error", (err) => {
|
||||||
logger.error(clientConnErr, "Client socket error");
|
logger.error(err, "Client socket error");
|
||||||
cleanup().catch((err) => {
|
void cleanup();
|
||||||
logger.error(err, "Client conn cleanup");
|
reject(err);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
clientConn.on("end", () => {
|
clientConn.on("end", cleanup);
|
||||||
cleanup().catch((err) => {
|
|
||||||
logger.error(err, "Client conn end");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err, "Failed to establish target connection:");
|
logger.error(err, "Failed to establish target connection:");
|
||||||
clientConn.end();
|
clientConn.end();
|
||||||
@@ -281,12 +270,12 @@ const setupProxyServer = async ({
|
|||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on("close", () => {
|
server.on("close", async () => {
|
||||||
quicClient?.destroy().catch((err) => {
|
await quicClient?.destroy();
|
||||||
logger.error(err, "Failed to destroy quic client");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
server.listen(0, () => {
|
server.listen(0, () => {
|
||||||
const address = server.address();
|
const address = server.address();
|
||||||
if (!address || typeof address === "string") {
|
if (!address || typeof address === "string") {
|
||||||
@@ -302,8 +291,7 @@ const setupProxyServer = async ({
|
|||||||
cleanup: async () => {
|
cleanup: async () => {
|
||||||
server.close();
|
server.close();
|
||||||
await quicClient?.destroy();
|
await quicClient?.destroy();
|
||||||
},
|
}
|
||||||
getProxyError: () => proxyErrorMsg.join(",")
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -326,7 +314,7 @@ export const withGatewayProxy = async (
|
|||||||
const { relayHost, relayPort, targetHost, targetPort, tlsOptions, identityId, orgId } = options;
|
const { relayHost, relayPort, targetHost, targetPort, tlsOptions, identityId, orgId } = options;
|
||||||
|
|
||||||
// Setup the proxy server
|
// Setup the proxy server
|
||||||
const { port, cleanup, getProxyError } = await setupProxyServer({
|
const { port, cleanup } = await setupProxyServer({
|
||||||
targetHost,
|
targetHost,
|
||||||
targetPort,
|
targetPort,
|
||||||
relayPort,
|
relayPort,
|
||||||
@@ -340,12 +328,8 @@ export const withGatewayProxy = async (
|
|||||||
// Execute the callback with the allocated port
|
// Execute the callback with the allocated port
|
||||||
await callback(port);
|
await callback(port);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const proxyErrorMessage = getProxyError();
|
logger.error(err, "Failed to proxy");
|
||||||
if (proxyErrorMessage) {
|
throw new BadRequestError({ message: (err as Error)?.message });
|
||||||
logger.error(new Error(proxyErrorMessage), "Failed to proxy");
|
|
||||||
}
|
|
||||||
logger.error(err, "Failed to do gateway");
|
|
||||||
throw new BadRequestError({ message: proxyErrorMessage || (err as Error)?.message });
|
|
||||||
} finally {
|
} finally {
|
||||||
// Ensure cleanup happens regardless of success or failure
|
// Ensure cleanup happens regardless of success or failure
|
||||||
await cleanup();
|
await cleanup();
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
const TURN_TOKEN_TTL = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
|
const TURN_TOKEN_TTL = 60 * 60 * 1000; // 24 hours in milliseconds
|
||||||
export const getTurnCredentials = (id: string, authSecret: string, ttl = TURN_TOKEN_TTL) => {
|
export const getTurnCredentials = (id: string, authSecret: string, ttl = TURN_TOKEN_TTL) => {
|
||||||
const timestamp = Math.floor((Date.now() + ttl) / 1000);
|
const timestamp = Math.floor((Date.now() + ttl) / 1000);
|
||||||
const username = `${timestamp}:${id}`;
|
const username = `${timestamp}:${id}`;
|
||||||
|
@@ -83,14 +83,6 @@ const run = async () => {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("uncaughtException", (error) => {
|
|
||||||
logger.error(error, "CRITICAL ERROR: Uncaught Exception");
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on("unhandledRejection", (error) => {
|
|
||||||
logger.error(error, "CRITICAL ERROR: Unhandled Promise Rejection");
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.listen({
|
await server.listen({
|
||||||
port: envConfig.PORT,
|
port: envConfig.PORT,
|
||||||
host: envConfig.HOST,
|
host: envConfig.HOST,
|
||||||
|
@@ -21,7 +21,6 @@ import {
|
|||||||
TQueueSecretSyncSyncSecretsByIdDTO,
|
TQueueSecretSyncSyncSecretsByIdDTO,
|
||||||
TQueueSendSecretSyncActionFailedNotificationsDTO
|
TQueueSendSecretSyncActionFailedNotificationsDTO
|
||||||
} from "@app/services/secret-sync/secret-sync-types";
|
} from "@app/services/secret-sync/secret-sync-types";
|
||||||
import { TWebhookPayloads } from "@app/services/webhook/webhook-types";
|
|
||||||
|
|
||||||
export enum QueueName {
|
export enum QueueName {
|
||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
@@ -108,7 +107,7 @@ export type TQueueJobTypes = {
|
|||||||
};
|
};
|
||||||
[QueueName.SecretWebhook]: {
|
[QueueName.SecretWebhook]: {
|
||||||
name: QueueJobs.SecWebhook;
|
name: QueueJobs.SecWebhook;
|
||||||
payload: TWebhookPayloads;
|
payload: { projectId: string; environment: string; secretPath: string; depth?: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
[QueueName.AccessTokenStatusUpdate]:
|
[QueueName.AccessTokenStatusUpdate]:
|
||||||
|
@@ -21,10 +21,3 @@ export const slugSchema = ({ min = 1, max = 32, field = "Slug" }: SlugSchemaInpu
|
|||||||
message: `${field} field can only contain lowercase letters, numbers, and hyphens`
|
message: `${field} field can only contain lowercase letters, numbers, and hyphens`
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GenericResourceNameSchema = z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1, { message: "Name must be at least 1 character" })
|
|
||||||
.max(64, { message: "Name must be 64 or fewer characters" })
|
|
||||||
.regex(/^[a-zA-Z0-9\-_\s]+$/, "Name can only contain alphanumeric characters, dashes, underscores, and spaces");
|
|
||||||
|
8
backend/src/server/lib/utils.ts
Normal file
8
backend/src/server/lib/utils.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const QUERY_TIMEOUT = 121000; // 2 mins (query timeout) with padding
|
||||||
|
|
||||||
|
export const extendTimeout =
|
||||||
|
(timeoutMs: number) =>
|
||||||
|
(request: { raw: { socket: { setTimeout: (ms: number) => void } } }, reply: unknown, done: () => void) => {
|
||||||
|
request.raw.socket.setTimeout(timeoutMs);
|
||||||
|
done();
|
||||||
|
};
|
@@ -122,8 +122,7 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
reqId: req.id,
|
reqId: req.id,
|
||||||
statusCode: HttpStatusCodes.Forbidden,
|
statusCode: HttpStatusCodes.Forbidden,
|
||||||
message: error.message,
|
message: error.message,
|
||||||
error: error.name,
|
error: error.name
|
||||||
details: error?.details
|
|
||||||
});
|
});
|
||||||
} else if (error instanceof RateLimitError) {
|
} else if (error instanceof RateLimitError) {
|
||||||
void res.status(HttpStatusCodes.TooManyRequests).send({
|
void res.status(HttpStatusCodes.TooManyRequests).send({
|
||||||
|
@@ -635,7 +635,6 @@ export const registerRoutes = async (
|
|||||||
});
|
});
|
||||||
const superAdminService = superAdminServiceFactory({
|
const superAdminService = superAdminServiceFactory({
|
||||||
userDAL,
|
userDAL,
|
||||||
identityDAL,
|
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
authService: loginService,
|
authService: loginService,
|
||||||
serverCfgDAL: superAdminDAL,
|
serverCfgDAL: superAdminDAL,
|
||||||
|
@@ -7,7 +7,6 @@ import {
|
|||||||
ProjectRolesSchema,
|
ProjectRolesSchema,
|
||||||
ProjectsSchema,
|
ProjectsSchema,
|
||||||
SecretApprovalPoliciesSchema,
|
SecretApprovalPoliciesSchema,
|
||||||
SecretTagsSchema,
|
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
@@ -112,16 +111,7 @@ export const secretRawSchema = z.object({
|
|||||||
secretReminderRepeatDays: z.number().nullable().optional(),
|
secretReminderRepeatDays: z.number().nullable().optional(),
|
||||||
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
skipMultilineEncoding: z.boolean().default(false).nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date()
|
||||||
actor: z
|
|
||||||
.object({
|
|
||||||
actorId: z.string().nullable().optional(),
|
|
||||||
actorType: z.string().nullable().optional(),
|
|
||||||
name: z.string().nullable().optional(),
|
|
||||||
membershipId: z.string().nullable().optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
.nullable()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ProjectPermissionSchema = z.object({
|
export const ProjectPermissionSchema = z.object({
|
||||||
@@ -242,11 +232,3 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
|
|||||||
kmsCertificateKeyId: true,
|
kmsCertificateKeyId: true,
|
||||||
auditLogsRetentionDays: true
|
auditLogsRetentionDays: true
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedTagSchema = SecretTagsSchema.pick({
|
|
||||||
id: true,
|
|
||||||
slug: true,
|
|
||||||
color: true
|
|
||||||
}).extend({
|
|
||||||
name: z.string()
|
|
||||||
});
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import DOMPurify from "isomorphic-dompurify";
|
import DOMPurify from "isomorphic-dompurify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentitiesSchema, OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } 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";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -118,12 +118,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
searchTerm: z.string().default(""),
|
searchTerm: z.string().default(""),
|
||||||
offset: z.coerce.number().default(0),
|
offset: z.coerce.number().default(0),
|
||||||
limit: z.coerce.number().max(100).default(20),
|
limit: z.coerce.number().max(100).default(20)
|
||||||
// TODO: remove this once z.coerce.boolean() is supported
|
|
||||||
adminsOnly: z
|
|
||||||
.string()
|
|
||||||
.transform((val) => val === "true")
|
|
||||||
.default("false")
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -154,43 +149,6 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/identity-management/identities",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
querystring: z.object({
|
|
||||||
searchTerm: z.string().default(""),
|
|
||||||
offset: z.coerce.number().default(0),
|
|
||||||
limit: z.coerce.number().max(100).default(20)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
identities: IdentitiesSchema.pick({
|
|
||||||
name: true,
|
|
||||||
id: true
|
|
||||||
}).array()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: (req, res, done) => {
|
|
||||||
verifyAuth([AuthMode.JWT])(req, res, () => {
|
|
||||||
verifySuperAdmin(req, res, done);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
handler: async (req) => {
|
|
||||||
const identities = await server.services.superAdmin.getIdentities({
|
|
||||||
...req.query
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
identities
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/integrations/slack/config",
|
url: "/integrations/slack/config",
|
||||||
|
@@ -18,10 +18,6 @@ import {
|
|||||||
} from "@app/services/app-connection/databricks";
|
} from "@app/services/app-connection/databricks";
|
||||||
import { GcpConnectionListItemSchema, SanitizedGcpConnectionSchema } from "@app/services/app-connection/gcp";
|
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 {
|
|
||||||
HumanitecConnectionListItemSchema,
|
|
||||||
SanitizedHumanitecConnectionSchema
|
|
||||||
} from "@app/services/app-connection/humanitec";
|
|
||||||
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
|
||||||
@@ -31,8 +27,7 @@ const SanitizedAppConnectionSchema = z.union([
|
|||||||
...SanitizedGcpConnectionSchema.options,
|
...SanitizedGcpConnectionSchema.options,
|
||||||
...SanitizedAzureKeyVaultConnectionSchema.options,
|
...SanitizedAzureKeyVaultConnectionSchema.options,
|
||||||
...SanitizedAzureAppConfigurationConnectionSchema.options,
|
...SanitizedAzureAppConfigurationConnectionSchema.options,
|
||||||
...SanitizedDatabricksConnectionSchema.options,
|
...SanitizedDatabricksConnectionSchema.options
|
||||||
...SanitizedHumanitecConnectionSchema.options
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
||||||
@@ -41,8 +36,7 @@ const AppConnectionOptionsSchema = z.discriminatedUnion("app", [
|
|||||||
GcpConnectionListItemSchema,
|
GcpConnectionListItemSchema,
|
||||||
AzureKeyVaultConnectionListItemSchema,
|
AzureKeyVaultConnectionListItemSchema,
|
||||||
AzureAppConfigurationConnectionListItemSchema,
|
AzureAppConfigurationConnectionListItemSchema,
|
||||||
DatabricksConnectionListItemSchema,
|
DatabricksConnectionListItemSchema
|
||||||
HumanitecConnectionListItemSchema
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
export const registerAppConnectionRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -1,69 +0,0 @@
|
|||||||
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 {
|
|
||||||
CreateHumanitecConnectionSchema,
|
|
||||||
HumanitecOrgWithApps,
|
|
||||||
SanitizedHumanitecConnectionSchema,
|
|
||||||
UpdateHumanitecConnectionSchema
|
|
||||||
} from "@app/services/app-connection/humanitec";
|
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
|
||||||
|
|
||||||
import { registerAppConnectionEndpoints } from "./app-connection-endpoints";
|
|
||||||
|
|
||||||
export const registerHumanitecConnectionRouter = async (server: FastifyZodProvider) => {
|
|
||||||
registerAppConnectionEndpoints({
|
|
||||||
app: AppConnection.Humanitec,
|
|
||||||
server,
|
|
||||||
sanitizedResponseSchema: SanitizedHumanitecConnectionSchema,
|
|
||||||
createSchema: CreateHumanitecConnectionSchema,
|
|
||||||
updateSchema: UpdateHumanitecConnectionSchema
|
|
||||||
});
|
|
||||||
|
|
||||||
// The below endpoints are not exposed and for Infisical App use
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: `/:connectionId/organizations`,
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
connectionId: z.string().uuid()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
apps: z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string(),
|
|
||||||
envs: z
|
|
||||||
.object({
|
|
||||||
id: z.string(),
|
|
||||||
name: z.string()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { connectionId } = req.params;
|
|
||||||
|
|
||||||
const organizations: HumanitecOrgWithApps[] = await server.services.appConnection.humanitec.listOrganizations(
|
|
||||||
connectionId,
|
|
||||||
req.permission
|
|
||||||
);
|
|
||||||
|
|
||||||
return organizations;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@@ -6,7 +6,6 @@ import { registerAzureKeyVaultConnectionRouter } from "./azure-key-vault-connect
|
|||||||
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
import { registerDatabricksConnectionRouter } from "./databricks-connection-router";
|
||||||
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
import { registerGcpConnectionRouter } from "./gcp-connection-router";
|
||||||
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
import { registerGitHubConnectionRouter } from "./github-connection-router";
|
||||||
import { registerHumanitecConnectionRouter } from "./humanitec-connection-router";
|
|
||||||
|
|
||||||
export * from "./app-connection-router";
|
export * from "./app-connection-router";
|
||||||
|
|
||||||
@@ -17,6 +16,5 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record<AppConnection, (server:
|
|||||||
[AppConnection.GCP]: registerGcpConnectionRouter,
|
[AppConnection.GCP]: registerGcpConnectionRouter,
|
||||||
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
[AppConnection.AzureKeyVault]: registerAzureKeyVaultConnectionRouter,
|
||||||
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
[AppConnection.AzureAppConfiguration]: registerAzureAppConfigurationConnectionRouter,
|
||||||
[AppConnection.Databricks]: registerDatabricksConnectionRouter,
|
[AppConnection.Databricks]: registerDatabricksConnectionRouter
|
||||||
[AppConnection.Humanitec]: registerHumanitecConnectionRouter
|
|
||||||
};
|
};
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ActionProjectType, SecretFoldersSchema, SecretImportsSchema } 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,
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { DASHBOARD } from "@app/lib/api-docs";
|
import { DASHBOARD } from "@app/lib/api-docs";
|
||||||
@@ -16,7 +15,7 @@ import { secretsLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedDynamicSecretSchema, SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedDynamicSecretSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema";
|
||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
@@ -117,10 +116,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
dynamicSecrets: SanitizedDynamicSecretSchema.extend({ environment: z.string() }).array().optional(),
|
dynamicSecrets: SanitizedDynamicSecretSchema.extend({ environment: z.string() }).array().optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -289,7 +294,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
secrets = await server.services.secret.getSecretsRawMultiEnv({
|
secrets = await server.services.secret.getSecretsRawMultiEnv({
|
||||||
viewSecretValue: true,
|
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
@@ -389,7 +393,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
||||||
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
||||||
viewSecretValue: booleanSchema.default(true),
|
|
||||||
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
||||||
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
||||||
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
||||||
@@ -407,10 +410,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
dynamicSecrets: SanitizedDynamicSecretSchema.array().optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -592,25 +601,23 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
secrets = (
|
const secretsRaw = await server.services.secret.getSecretsRaw({
|
||||||
await server.services.secret.getSecretsRaw({
|
actorId: req.permission.id,
|
||||||
actorId: req.permission.id,
|
actor: req.permission.type,
|
||||||
actor: req.permission.type,
|
actorOrgId: req.permission.orgId,
|
||||||
viewSecretValue: req.query.viewSecretValue,
|
environment,
|
||||||
throwOnMissingReadValuePermission: false,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
projectId,
|
||||||
environment,
|
path: secretPath,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
orderBy,
|
||||||
projectId,
|
orderDirection,
|
||||||
path: secretPath,
|
search,
|
||||||
orderBy,
|
limit: remainingLimit,
|
||||||
orderDirection,
|
offset: adjustedOffset,
|
||||||
search,
|
tagSlugs: tags
|
||||||
limit: remainingLimit,
|
});
|
||||||
offset: adjustedOffset,
|
|
||||||
tagSlugs: tags
|
secrets = secretsRaw.secrets;
|
||||||
})
|
|
||||||
).secrets;
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -689,10 +696,16 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -736,7 +749,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
const secrets = await server.services.secret.getSecretsRawByFolderMappings(
|
const secrets = await server.services.secret.getSecretsRawByFolderMappings(
|
||||||
{
|
{
|
||||||
filterByAction: ProjectPermissionSecretActions.DescribeSecret,
|
|
||||||
projectId,
|
projectId,
|
||||||
folderMappings,
|
folderMappings,
|
||||||
filters: {
|
filters: {
|
||||||
@@ -834,52 +846,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/accessible-secrets",
|
|
||||||
config: {
|
|
||||||
rateLimit: secretsLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
querystring: z.object({
|
|
||||||
projectId: z.string().trim(),
|
|
||||||
environment: z.string().trim(),
|
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
|
||||||
filterByAction: z
|
|
||||||
.enum([ProjectPermissionSecretActions.DescribeSecret, ProjectPermissionSecretActions.ReadValue])
|
|
||||||
.default(ProjectPermissionSecretActions.ReadValue)
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
secrets: secretRawSchema
|
|
||||||
.extend({
|
|
||||||
secretPath: z.string().optional(),
|
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { projectId, environment, secretPath, filterByAction } = req.query;
|
|
||||||
|
|
||||||
const { secrets } = await server.services.secret.getAccessibleSecrets({
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
projectId,
|
|
||||||
filterByAction
|
|
||||||
});
|
|
||||||
|
|
||||||
return { secrets };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/secrets-by-keys",
|
url: "/secrets-by-keys",
|
||||||
@@ -896,17 +862,22 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: z.string().trim(),
|
projectId: z.string().trim(),
|
||||||
environment: z.string().trim(),
|
environment: z.string().trim(),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||||
keys: z.string().trim().transform(decodeURIComponent),
|
keys: z.string().trim().transform(decodeURIComponent)
|
||||||
viewSecretValue: booleanSchema.default(false)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -915,7 +886,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { secretPath, projectId, environment, viewSecretValue } = req.query;
|
const { secretPath, projectId, environment } = req.query;
|
||||||
|
|
||||||
const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? [];
|
const keys = req.query.keys?.split(",").filter((key) => Boolean(key.trim())) ?? [];
|
||||||
if (!keys.length) throw new BadRequestError({ message: "One or more keys required" });
|
if (!keys.length) throw new BadRequestError({ message: "One or more keys required" });
|
||||||
@@ -924,7 +895,6 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
viewSecretValue,
|
|
||||||
environment,
|
environment,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId,
|
projectId,
|
||||||
|
@@ -91,6 +91,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await projectRouter.register(registerProjectMembershipRouter);
|
await projectRouter.register(registerProjectMembershipRouter);
|
||||||
await projectRouter.register(registerSecretTagRouter);
|
await projectRouter.register(registerSecretTagRouter);
|
||||||
},
|
},
|
||||||
|
|
||||||
{ prefix: "/workspace" }
|
{ prefix: "/workspace" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -13,7 +13,8 @@ import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-t
|
|||||||
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
|
import { getLastMidnightDateISO, removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { GenericResourceNameSchema, slugSchema } from "@app/server/lib/schemas";
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
|
import { extendTimeout, QUERY_TIMEOUT } from "@app/server/lib/utils";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode, MfaMethod } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode, MfaMethod } from "@app/services/auth/auth-type";
|
||||||
import { sanitizedOrganizationSchema } from "@app/services/org/org-schema";
|
import { sanitizedOrganizationSchema } from "@app/services/org/org-schema";
|
||||||
@@ -108,6 +109,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
|
preHandler: extendTimeout(QUERY_TIMEOUT),
|
||||||
schema: {
|
schema: {
|
||||||
description: "Get all audit logs for an organization",
|
description: "Get all audit logs for an organization",
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
@@ -251,14 +253,13 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
params: z.object({ organizationId: z.string().trim() }),
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: GenericResourceNameSchema.optional(),
|
name: z.string().trim().max(64, { message: "Name must be 64 or fewer characters" }).optional(),
|
||||||
slug: slugSchema({ max: 64 }).optional(),
|
slug: slugSchema({ max: 64 }).optional(),
|
||||||
authEnforced: z.boolean().optional(),
|
authEnforced: z.boolean().optional(),
|
||||||
scimEnabled: z.boolean().optional(),
|
scimEnabled: z.boolean().optional(),
|
||||||
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
|
defaultMembershipRoleSlug: slugSchema({ max: 64, field: "Default Membership Role" }).optional(),
|
||||||
enforceMfa: z.boolean().optional(),
|
enforceMfa: z.boolean().optional(),
|
||||||
selectedMfaMethod: z.nativeEnum(MfaMethod).optional(),
|
selectedMfaMethod: z.nativeEnum(MfaMethod).optional()
|
||||||
allowSecretSharingOutsideOrganization: z.boolean().optional()
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -6,7 +6,6 @@ import { authRateLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
|
import { validateSignUpAuthorization } from "@app/services/auth/auth-fns";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { UserEncryption } from "@app/services/user/user-types";
|
|
||||||
|
|
||||||
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -114,16 +113,20 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
message: z.string(),
|
||||||
user: UsersSchema,
|
user: UsersSchema,
|
||||||
token: z.string(),
|
token: z.string()
|
||||||
userEncryptionVersion: z.nativeEnum(UserEncryption)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const passwordReset = await server.services.password.verifyPasswordResetEmail(req.body.email, req.body.code);
|
const { token, user } = await server.services.password.verifyPasswordResetEmail(req.body.email, req.body.code);
|
||||||
|
|
||||||
return passwordReset;
|
return {
|
||||||
|
message: "Successfully verified email",
|
||||||
|
user,
|
||||||
|
token
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -2,12 +2,10 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
IntegrationsSchema,
|
IntegrationsSchema,
|
||||||
ProjectEnvironmentsSchema,
|
|
||||||
ProjectMembershipsSchema,
|
ProjectMembershipsSchema,
|
||||||
ProjectRolesSchema,
|
ProjectRolesSchema,
|
||||||
ProjectSlackConfigsSchema,
|
ProjectSlackConfigsSchema,
|
||||||
ProjectType,
|
ProjectType,
|
||||||
SecretFoldersSchema,
|
|
||||||
UserEncryptionKeysSchema,
|
UserEncryptionKeysSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@@ -309,17 +307,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
.max(256, { message: "Description must be 256 or fewer characters" })
|
.max(256, { message: "Description must be 256 or fewer characters" })
|
||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECTS.UPDATE.projectDescription),
|
.describe(PROJECTS.UPDATE.projectDescription),
|
||||||
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization),
|
autoCapitalization: z.boolean().optional().describe(PROJECTS.UPDATE.autoCapitalization)
|
||||||
slug: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.regex(
|
|
||||||
/^[a-z0-9]+(?:[_-][a-z0-9]+)*$/,
|
|
||||||
"Project slug can only contain lowercase letters and numbers, with optional single hyphens (-) or underscores (_) between words. Cannot start or end with a hyphen or underscore."
|
|
||||||
)
|
|
||||||
.max(64, { message: "Slug must be 64 characters or fewer" })
|
|
||||||
.optional()
|
|
||||||
.describe(PROJECTS.UPDATE.slug)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -337,8 +325,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
update: {
|
update: {
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
description: req.body.description,
|
description: req.body.description,
|
||||||
autoCapitalization: req.body.autoCapitalization,
|
autoCapitalization: req.body.autoCapitalization
|
||||||
slug: req.body.slug
|
|
||||||
},
|
},
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -677,31 +664,4 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
return slackConfig;
|
return slackConfig;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/:workspaceId/environment-folder-tree",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
workspaceId: z.string().trim()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.record(
|
|
||||||
ProjectEnvironmentsSchema.extend({ folders: SecretFoldersSchema.extend({ path: z.string() }).array() })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const environmentsFolders = await server.services.folder.getProjectEnvironmentsFolders(
|
|
||||||
req.params.workspaceId,
|
|
||||||
req.permission
|
|
||||||
);
|
|
||||||
|
|
||||||
return environmentsFolders;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@@ -1,17 +0,0 @@
|
|||||||
import {
|
|
||||||
CreateHumanitecSyncSchema,
|
|
||||||
HumanitecSyncSchema,
|
|
||||||
UpdateHumanitecSyncSchema
|
|
||||||
} from "@app/services/secret-sync/humanitec";
|
|
||||||
import { SecretSync } from "@app/services/secret-sync/secret-sync-enums";
|
|
||||||
|
|
||||||
import { registerSyncSecretsEndpoints } from "./secret-sync-endpoints";
|
|
||||||
|
|
||||||
export const registerHumanitecSyncRouter = async (server: FastifyZodProvider) =>
|
|
||||||
registerSyncSecretsEndpoints({
|
|
||||||
destination: SecretSync.Humanitec,
|
|
||||||
server,
|
|
||||||
responseSchema: HumanitecSyncSchema,
|
|
||||||
createSchema: CreateHumanitecSyncSchema,
|
|
||||||
updateSchema: UpdateHumanitecSyncSchema
|
|
||||||
});
|
|
@@ -7,7 +7,6 @@ import { registerAzureKeyVaultSyncRouter } from "./azure-key-vault-sync-router";
|
|||||||
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
import { registerDatabricksSyncRouter } from "./databricks-sync-router";
|
||||||
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
import { registerGcpSyncRouter } from "./gcp-sync-router";
|
||||||
import { registerGitHubSyncRouter } from "./github-sync-router";
|
import { registerGitHubSyncRouter } from "./github-sync-router";
|
||||||
import { registerHumanitecSyncRouter } from "./humanitec-sync-router";
|
|
||||||
|
|
||||||
export * from "./secret-sync-router";
|
export * from "./secret-sync-router";
|
||||||
|
|
||||||
@@ -18,6 +17,5 @@ export const SECRET_SYNC_REGISTER_ROUTER_MAP: Record<SecretSync, (server: Fastif
|
|||||||
[SecretSync.GCPSecretManager]: registerGcpSyncRouter,
|
[SecretSync.GCPSecretManager]: registerGcpSyncRouter,
|
||||||
[SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter,
|
[SecretSync.AzureKeyVault]: registerAzureKeyVaultSyncRouter,
|
||||||
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter,
|
[SecretSync.AzureAppConfiguration]: registerAzureAppConfigurationSyncRouter,
|
||||||
[SecretSync.Databricks]: registerDatabricksSyncRouter,
|
[SecretSync.Databricks]: registerDatabricksSyncRouter
|
||||||
[SecretSync.Humanitec]: registerHumanitecSyncRouter
|
|
||||||
};
|
};
|
||||||
|
@@ -21,7 +21,6 @@ import { AzureKeyVaultSyncListItemSchema, AzureKeyVaultSyncSchema } from "@app/s
|
|||||||
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
import { DatabricksSyncListItemSchema, DatabricksSyncSchema } from "@app/services/secret-sync/databricks";
|
||||||
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
import { GcpSyncListItemSchema, GcpSyncSchema } from "@app/services/secret-sync/gcp";
|
||||||
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
import { GitHubSyncListItemSchema, GitHubSyncSchema } from "@app/services/secret-sync/github";
|
||||||
import { HumanitecSyncListItemSchema, HumanitecSyncSchema } from "@app/services/secret-sync/humanitec";
|
|
||||||
|
|
||||||
const SecretSyncSchema = z.discriminatedUnion("destination", [
|
const SecretSyncSchema = z.discriminatedUnion("destination", [
|
||||||
AwsParameterStoreSyncSchema,
|
AwsParameterStoreSyncSchema,
|
||||||
@@ -30,8 +29,7 @@ const SecretSyncSchema = z.discriminatedUnion("destination", [
|
|||||||
GcpSyncSchema,
|
GcpSyncSchema,
|
||||||
AzureKeyVaultSyncSchema,
|
AzureKeyVaultSyncSchema,
|
||||||
AzureAppConfigurationSyncSchema,
|
AzureAppConfigurationSyncSchema,
|
||||||
DatabricksSyncSchema,
|
DatabricksSyncSchema
|
||||||
HumanitecSyncSchema
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
||||||
@@ -41,8 +39,7 @@ const SecretSyncOptionsSchema = z.discriminatedUnion("destination", [
|
|||||||
GcpSyncListItemSchema,
|
GcpSyncListItemSchema,
|
||||||
AzureKeyVaultSyncListItemSchema,
|
AzureKeyVaultSyncListItemSchema,
|
||||||
AzureAppConfigurationSyncListItemSchema,
|
AzureAppConfigurationSyncListItemSchema,
|
||||||
DatabricksSyncListItemSchema,
|
DatabricksSyncListItemSchema
|
||||||
HumanitecSyncListItemSchema
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
export const registerSecretSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -3,7 +3,6 @@ import { registerIdentityOrgRouter } from "./identity-org-router";
|
|||||||
import { registerIdentityProjectRouter } from "./identity-project-router";
|
import { registerIdentityProjectRouter } from "./identity-project-router";
|
||||||
import { registerMfaRouter } from "./mfa-router";
|
import { registerMfaRouter } from "./mfa-router";
|
||||||
import { registerOrgRouter } from "./organization-router";
|
import { registerOrgRouter } from "./organization-router";
|
||||||
import { registerPasswordRouter } from "./password-router";
|
|
||||||
import { registerProjectMembershipRouter } from "./project-membership-router";
|
import { registerProjectMembershipRouter } from "./project-membership-router";
|
||||||
import { registerProjectRouter } from "./project-router";
|
import { registerProjectRouter } from "./project-router";
|
||||||
import { registerServiceTokenRouter } from "./service-token-router";
|
import { registerServiceTokenRouter } from "./service-token-router";
|
||||||
@@ -13,7 +12,6 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerMfaRouter, { prefix: "/auth" });
|
await server.register(registerMfaRouter, { prefix: "/auth" });
|
||||||
await server.register(registerUserRouter, { prefix: "/users" });
|
await server.register(registerUserRouter, { prefix: "/users" });
|
||||||
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
|
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
|
||||||
await server.register(registerPasswordRouter, { prefix: "/password" });
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (orgRouter) => {
|
async (orgRouter) => {
|
||||||
await orgRouter.register(registerOrgRouter);
|
await orgRouter.register(registerOrgRouter);
|
||||||
|
@@ -12,7 +12,6 @@ import {
|
|||||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { GenericResourceNameSchema } from "@app/server/lib/schemas";
|
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -331,7 +330,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: GenericResourceNameSchema
|
name: z.string().trim()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|
||||||
import { validatePasswordResetAuthorization } from "@app/services/auth/auth-fns";
|
|
||||||
import { ResetPasswordV2Type } from "@app/services/auth/auth-password-type";
|
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
|
||||||
|
|
||||||
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/password-reset",
|
|
||||||
config: {
|
|
||||||
rateLimit: authRateLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
newPassword: z.string().trim()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handler: async (req) => {
|
|
||||||
const token = validatePasswordResetAuthorization(req.headers.authorization);
|
|
||||||
await server.services.password.resetPasswordV2({
|
|
||||||
type: ResetPasswordV2Type.Recovery,
|
|
||||||
newPassword: req.body.newPassword,
|
|
||||||
userId: token.userId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/user/password-reset",
|
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
oldPassword: z.string().trim(),
|
|
||||||
newPassword: z.string().trim()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
rateLimit: authRateLimit
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }),
|
|
||||||
handler: async (req) => {
|
|
||||||
await server.services.password.resetPasswordV2({
|
|
||||||
type: ResetPasswordV2Type.LoggedInReset,
|
|
||||||
userId: req.permission.id,
|
|
||||||
newPassword: req.body.newPassword,
|
|
||||||
oldPassword: req.body.oldPassword
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@@ -1,7 +1,13 @@
|
|||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretApprovalRequestsSchema, SecretsSchema, SecretType, ServiceTokenScopes } from "@app/db/schemas";
|
import {
|
||||||
|
SecretApprovalRequestsSchema,
|
||||||
|
SecretsSchema,
|
||||||
|
SecretTagsSchema,
|
||||||
|
SecretType,
|
||||||
|
ServiceTokenScopes
|
||||||
|
} 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 { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
import { RAW_SECRETS, SECRETS } from "@app/lib/api-docs";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -17,7 +23,7 @@ import { SecretOperations, SecretProtectionType } from "@app/services/secret/sec
|
|||||||
import { SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
import { SecretUpdateMode } from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
import { SanitizedTagSchema, secretRawSchema } from "../sanitizedSchemas";
|
import { secretRawSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
const SecretReferenceNode = z.object({
|
const SecretReferenceNode = z.object({
|
||||||
key: z.string(),
|
key: z.string(),
|
||||||
@@ -25,14 +31,6 @@ const SecretReferenceNode = z.object({
|
|||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
secretPath: z.string()
|
secretPath: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
const convertStringBoolean = (defaultValue: boolean = false) => {
|
|
||||||
return z
|
|
||||||
.enum(["true", "false"])
|
|
||||||
.default(defaultValue ? "true" : "false")
|
|
||||||
.transform((value) => value === "true");
|
|
||||||
};
|
|
||||||
|
|
||||||
type TSecretReferenceNode = z.infer<typeof SecretReferenceNode> & { children: TSecretReferenceNode[] };
|
type TSecretReferenceNode = z.infer<typeof SecretReferenceNode> & { children: TSecretReferenceNode[] };
|
||||||
|
|
||||||
const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReferenceNode.extend({
|
const SecretReferenceNodeTree: z.ZodType<TSecretReferenceNode> = SecretReferenceNode.extend({
|
||||||
@@ -77,9 +75,17 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||||
tags: SanitizedTagSchema.array()
|
z.object({
|
||||||
})
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -133,7 +139,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
||||||
tags: SanitizedTagSchema.array()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -235,10 +247,21 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
|
workspaceSlug: z.string().trim().optional().describe(RAW_SECRETS.LIST.workspaceSlug),
|
||||||
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
environment: z.string().trim().optional().describe(RAW_SECRETS.LIST.environment),
|
||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.LIST.secretPath),
|
||||||
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.LIST.viewSecretValue),
|
expandSecretReferences: z
|
||||||
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.LIST.expand),
|
.enum(["true", "false"])
|
||||||
recursive: convertStringBoolean().describe(RAW_SECRETS.LIST.recursive),
|
.default("false")
|
||||||
include_imports: convertStringBoolean().describe(RAW_SECRETS.LIST.includeImports),
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.LIST.expand),
|
||||||
|
recursive: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.LIST.recursive),
|
||||||
|
include_imports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.LIST.includeImports),
|
||||||
tagSlugs: z
|
tagSlugs: z
|
||||||
.string()
|
.string()
|
||||||
.describe(RAW_SECRETS.LIST.tagSlugs)
|
.describe(RAW_SECRETS.LIST.tagSlugs)
|
||||||
@@ -251,9 +274,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
tags: SanitizedTagSchema.array().optional()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
imports: z
|
imports: z
|
||||||
@@ -264,7 +293,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.omit({ createdAt: true, updatedAt: true })
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
.extend({
|
.extend({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
secretMetadata: ResourceMetadataSchema.optional()
|
secretMetadata: ResourceMetadataSchema.optional()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
@@ -314,7 +342,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
expandSecretReferences: req.query.expandSecretReferences,
|
expandSecretReferences: req.query.expandSecretReferences,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
viewSecretValue: req.query.viewSecretValue,
|
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
metadataFilter: req.query.metadataFilter,
|
metadataFilter: req.query.metadataFilter,
|
||||||
includeImports: req.query.include_imports,
|
includeImports: req.query.include_imports,
|
||||||
@@ -349,46 +376,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { secrets, imports };
|
return { secrets, imports };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/raw/id/:secretId",
|
|
||||||
config: {
|
|
||||||
rateLimit: secretsLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
params: z.object({
|
|
||||||
secretId: z.string()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
secret: secretRawSchema.extend({
|
|
||||||
secretPath: z.string(),
|
|
||||||
tags: SanitizedTagSchema.array().optional(),
|
|
||||||
secretMetadata: ResourceMetadataSchema.optional()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { secretId } = req.params;
|
|
||||||
const secret = await server.services.secret.getSecretByIdRaw({
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
secretId
|
|
||||||
});
|
|
||||||
|
|
||||||
return { secret };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/raw/:secretName",
|
url: "/raw/:secretName",
|
||||||
@@ -412,15 +403,28 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash).describe(RAW_SECRETS.GET.secretPath),
|
||||||
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
version: z.coerce.number().optional().describe(RAW_SECRETS.GET.version),
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.GET.type),
|
||||||
viewSecretValue: convertStringBoolean(true).describe(RAW_SECRETS.GET.viewSecretValue),
|
expandSecretReferences: z
|
||||||
expandSecretReferences: convertStringBoolean().describe(RAW_SECRETS.GET.expand),
|
.enum(["true", "false"])
|
||||||
include_imports: convertStringBoolean().describe(RAW_SECRETS.GET.includeImports)
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.GET.expand),
|
||||||
|
include_imports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
|
.describe(RAW_SECRETS.GET.includeImports)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: secretRawSchema.extend({
|
secret: secretRawSchema.extend({
|
||||||
secretValueHidden: z.boolean(),
|
tags: SecretTagsSchema.pick({
|
||||||
tags: SanitizedTagSchema.array().optional(),
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional()
|
secretMetadata: ResourceMetadataSchema.optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -452,7 +456,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
expandSecretReferences: req.query.expandSecretReferences,
|
expandSecretReferences: req.query.expandSecretReferences,
|
||||||
environment,
|
environment,
|
||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
viewSecretValue: req.query.viewSecretValue,
|
|
||||||
projectSlug: workspaceSlug,
|
projectSlug: workspaceSlug,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
secretName: req.params.secretName,
|
secretName: req.params.secretName,
|
||||||
@@ -659,9 +662,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secret: secretRawSchema.extend({
|
secret: secretRawSchema
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -757,9 +758,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secret: secretRawSchema.extend({
|
secret: secretRawSchema
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -781,7 +780,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
if (secretOperation.type === SecretProtectionType.Approval) {
|
if (secretOperation.type === SecretProtectionType.Approval) {
|
||||||
return { approval: secretOperation.approval };
|
return { approval: secretOperation.approval };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { secret } = secretOperation;
|
const { secret } = secretOperation;
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -844,7 +842,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
workspace: z.string(),
|
workspace: z.string(),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
tags: SanitizedTagSchema.array()
|
tags: SecretTagsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slug: true,
|
||||||
|
color: true
|
||||||
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
imports: z
|
imports: z
|
||||||
@@ -940,7 +944,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
secretPath: z.string().trim().default("/").transform(removeTrailingSlash),
|
||||||
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
type: z.nativeEnum(SecretType).default(SecretType.Shared),
|
||||||
version: z.coerce.number().optional(),
|
version: z.coerce.number().optional(),
|
||||||
include_imports: convertStringBoolean()
|
include_imports: z
|
||||||
|
.enum(["true", "false"])
|
||||||
|
.default("false")
|
||||||
|
.transform((value) => value === "true")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -1211,7 +1218,6 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
z.object({
|
z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||||
z.object({
|
z.object({
|
||||||
secretValueHidden: z.boolean(),
|
|
||||||
_id: z.string(),
|
_id: z.string(),
|
||||||
workspace: z.string(),
|
workspace: z.string(),
|
||||||
environment: z.string()
|
environment: z.string()
|
||||||
@@ -1381,12 +1387,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
||||||
_id: z.string(),
|
z.object({
|
||||||
secretValueHidden: z.boolean(),
|
_id: z.string(),
|
||||||
workspace: z.string(),
|
workspace: z.string(),
|
||||||
environment: z.string()
|
environment: z.string()
|
||||||
})
|
})
|
||||||
|
)
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -1698,7 +1705,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true }).extend({ secretValueHidden: z.boolean() }).array()
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -1813,11 +1820,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: SecretsSchema.omit({ secretBlindIndex: true })
|
secrets: SecretsSchema.omit({ secretBlindIndex: true }).array()
|
||||||
.extend({
|
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -2079,7 +2082,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: secretRawSchema.extend({ secretValueHidden: z.boolean() }).array()
|
secrets: secretRawSchema.array()
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
@@ -2201,11 +2204,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema.array()
|
||||||
.extend({
|
|
||||||
secretValueHidden: z.boolean()
|
|
||||||
})
|
|
||||||
.array()
|
|
||||||
}),
|
}),
|
||||||
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
z.object({ approval: SecretApprovalRequestsSchema }).describe("When secret protection policy is enabled")
|
||||||
])
|
])
|
||||||
|
@@ -4,7 +4,6 @@ import { UsersSchema } from "@app/db/schemas";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { ForbiddenRequestError } from "@app/lib/errors";
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { GenericResourceNameSchema } from "@app/server/lib/schemas";
|
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
@@ -101,7 +100,7 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
encryptedPrivateKeyTag: z.string().trim(),
|
encryptedPrivateKeyTag: z.string().trim(),
|
||||||
salt: z.string().trim(),
|
salt: z.string().trim(),
|
||||||
verifier: z.string().trim(),
|
verifier: z.string().trim(),
|
||||||
organizationName: GenericResourceNameSchema,
|
organizationName: z.string().trim().min(1),
|
||||||
providerAuthToken: z.string().trim().optional().nullish(),
|
providerAuthToken: z.string().trim().optional().nullish(),
|
||||||
attributionSource: z.string().trim().optional(),
|
attributionSource: z.string().trim().optional(),
|
||||||
password: z.string()
|
password: z.string()
|
||||||
|
@@ -4,8 +4,7 @@ export enum AppConnection {
|
|||||||
Databricks = "databricks",
|
Databricks = "databricks",
|
||||||
GCP = "gcp",
|
GCP = "gcp",
|
||||||
AzureKeyVault = "azure-key-vault",
|
AzureKeyVault = "azure-key-vault",
|
||||||
AzureAppConfiguration = "azure-app-configuration",
|
AzureAppConfiguration = "azure-app-configuration"
|
||||||
Humanitec = "humanitec"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AWSRegion {
|
export enum AWSRegion {
|
||||||
|
@@ -35,11 +35,6 @@ import {
|
|||||||
getAzureKeyVaultConnectionListItem,
|
getAzureKeyVaultConnectionListItem,
|
||||||
validateAzureKeyVaultConnectionCredentials
|
validateAzureKeyVaultConnectionCredentials
|
||||||
} from "./azure-key-vault";
|
} from "./azure-key-vault";
|
||||||
import {
|
|
||||||
getHumanitecConnectionListItem,
|
|
||||||
HumanitecConnectionMethod,
|
|
||||||
validateHumanitecConnectionCredentials
|
|
||||||
} from "./humanitec";
|
|
||||||
|
|
||||||
export const listAppConnectionOptions = () => {
|
export const listAppConnectionOptions = () => {
|
||||||
return [
|
return [
|
||||||
@@ -48,8 +43,7 @@ export const listAppConnectionOptions = () => {
|
|||||||
getGcpConnectionListItem(),
|
getGcpConnectionListItem(),
|
||||||
getAzureKeyVaultConnectionListItem(),
|
getAzureKeyVaultConnectionListItem(),
|
||||||
getAzureAppConfigurationConnectionListItem(),
|
getAzureAppConfigurationConnectionListItem(),
|
||||||
getDatabricksConnectionListItem(),
|
getDatabricksConnectionListItem()
|
||||||
getHumanitecConnectionListItem()
|
|
||||||
].sort((a, b) => a.name.localeCompare(b.name));
|
].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -112,8 +106,6 @@ export const validateAppConnectionCredentials = async (
|
|||||||
return validateAzureKeyVaultConnectionCredentials(appConnection);
|
return validateAzureKeyVaultConnectionCredentials(appConnection);
|
||||||
case AppConnection.AzureAppConfiguration:
|
case AppConnection.AzureAppConfiguration:
|
||||||
return validateAzureAppConfigurationConnectionCredentials(appConnection);
|
return validateAzureAppConfigurationConnectionCredentials(appConnection);
|
||||||
case AppConnection.Humanitec:
|
|
||||||
return validateHumanitecConnectionCredentials(appConnection);
|
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
throw new Error(`Unhandled App Connection ${app}`);
|
throw new Error(`Unhandled App Connection ${app}`);
|
||||||
@@ -136,8 +128,6 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
return "Service Account Impersonation";
|
return "Service Account Impersonation";
|
||||||
case DatabricksConnectionMethod.ServicePrincipal:
|
case DatabricksConnectionMethod.ServicePrincipal:
|
||||||
return "Service Principal";
|
return "Service Principal";
|
||||||
case HumanitecConnectionMethod.API_TOKEN:
|
|
||||||
return "API Token";
|
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||||
|
@@ -6,6 +6,5 @@ export const APP_CONNECTION_NAME_MAP: Record<AppConnection, string> = {
|
|||||||
[AppConnection.GCP]: "GCP",
|
[AppConnection.GCP]: "GCP",
|
||||||
[AppConnection.AzureKeyVault]: "Azure Key Vault",
|
[AppConnection.AzureKeyVault]: "Azure Key Vault",
|
||||||
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
|
[AppConnection.AzureAppConfiguration]: "Azure App Configuration",
|
||||||
[AppConnection.Databricks]: "Databricks",
|
[AppConnection.Databricks]: "Databricks"
|
||||||
[AppConnection.Humanitec]: "Humanitec"
|
|
||||||
};
|
};
|
||||||
|
@@ -35,8 +35,6 @@ import { ValidateGcpConnectionCredentialsSchema } from "./gcp";
|
|||||||
import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
import { gcpConnectionService } from "./gcp/gcp-connection-service";
|
||||||
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
import { ValidateGitHubConnectionCredentialsSchema } from "./github";
|
||||||
import { githubConnectionService } from "./github/github-connection-service";
|
import { githubConnectionService } from "./github/github-connection-service";
|
||||||
import { ValidateHumanitecConnectionCredentialsSchema } from "./humanitec";
|
|
||||||
import { humanitecConnectionService } from "./humanitec/humanitec-connection-service";
|
|
||||||
|
|
||||||
export type TAppConnectionServiceFactoryDep = {
|
export type TAppConnectionServiceFactoryDep = {
|
||||||
appConnectionDAL: TAppConnectionDALFactory;
|
appConnectionDAL: TAppConnectionDALFactory;
|
||||||
@@ -52,8 +50,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record<AppConnection, TValidateAp
|
|||||||
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema,
|
[AppConnection.GCP]: ValidateGcpConnectionCredentialsSchema,
|
||||||
[AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema,
|
[AppConnection.AzureKeyVault]: ValidateAzureKeyVaultConnectionCredentialsSchema,
|
||||||
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema,
|
[AppConnection.AzureAppConfiguration]: ValidateAzureAppConfigurationConnectionCredentialsSchema,
|
||||||
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema,
|
[AppConnection.Databricks]: ValidateDatabricksConnectionCredentialsSchema
|
||||||
[AppConnection.Humanitec]: ValidateHumanitecConnectionCredentialsSchema
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appConnectionServiceFactory = ({
|
export const appConnectionServiceFactory = ({
|
||||||
@@ -374,7 +371,6 @@ export const appConnectionServiceFactory = ({
|
|||||||
github: githubConnectionService(connectAppConnectionById),
|
github: githubConnectionService(connectAppConnectionById),
|
||||||
gcp: gcpConnectionService(connectAppConnectionById),
|
gcp: gcpConnectionService(connectAppConnectionById),
|
||||||
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
databricks: databricksConnectionService(connectAppConnectionById, appConnectionDAL, kmsService),
|
||||||
aws: awsConnectionService(connectAppConnectionById),
|
aws: awsConnectionService(connectAppConnectionById)
|
||||||
humanitec: humanitecConnectionService(connectAppConnectionById)
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -32,12 +32,6 @@ import {
|
|||||||
TValidateAzureKeyVaultConnectionCredentials
|
TValidateAzureKeyVaultConnectionCredentials
|
||||||
} from "./azure-key-vault";
|
} from "./azure-key-vault";
|
||||||
import { TGcpConnection, TGcpConnectionConfig, TGcpConnectionInput, TValidateGcpConnectionCredentials } from "./gcp";
|
import { TGcpConnection, TGcpConnectionConfig, TGcpConnectionInput, TValidateGcpConnectionCredentials } from "./gcp";
|
||||||
import {
|
|
||||||
THumanitecConnection,
|
|
||||||
THumanitecConnectionConfig,
|
|
||||||
THumanitecConnectionInput,
|
|
||||||
TValidateHumanitecConnectionCredentials
|
|
||||||
} from "./humanitec";
|
|
||||||
|
|
||||||
export type TAppConnection = { id: string } & (
|
export type TAppConnection = { id: string } & (
|
||||||
| TAwsConnection
|
| TAwsConnection
|
||||||
@@ -46,7 +40,6 @@ export type TAppConnection = { id: string } & (
|
|||||||
| TAzureKeyVaultConnection
|
| TAzureKeyVaultConnection
|
||||||
| TAzureAppConfigurationConnection
|
| TAzureAppConfigurationConnection
|
||||||
| TDatabricksConnection
|
| TDatabricksConnection
|
||||||
| THumanitecConnection
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TAppConnectionInput = { id: string } & (
|
export type TAppConnectionInput = { id: string } & (
|
||||||
@@ -56,7 +49,6 @@ export type TAppConnectionInput = { id: string } & (
|
|||||||
| TAzureKeyVaultConnectionInput
|
| TAzureKeyVaultConnectionInput
|
||||||
| TAzureAppConfigurationConnectionInput
|
| TAzureAppConfigurationConnectionInput
|
||||||
| TDatabricksConnectionInput
|
| TDatabricksConnectionInput
|
||||||
| THumanitecConnectionInput
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export type TCreateAppConnectionDTO = Pick<
|
export type TCreateAppConnectionDTO = Pick<
|
||||||
@@ -74,8 +66,7 @@ export type TAppConnectionConfig =
|
|||||||
| TGcpConnectionConfig
|
| TGcpConnectionConfig
|
||||||
| TAzureKeyVaultConnectionConfig
|
| TAzureKeyVaultConnectionConfig
|
||||||
| TAzureAppConfigurationConnectionConfig
|
| TAzureAppConfigurationConnectionConfig
|
||||||
| TDatabricksConnectionConfig
|
| TDatabricksConnectionConfig;
|
||||||
| THumanitecConnectionConfig;
|
|
||||||
|
|
||||||
export type TValidateAppConnectionCredentials =
|
export type TValidateAppConnectionCredentials =
|
||||||
| TValidateAwsConnectionCredentials
|
| TValidateAwsConnectionCredentials
|
||||||
@@ -83,8 +74,7 @@ export type TValidateAppConnectionCredentials =
|
|||||||
| TValidateGcpConnectionCredentials
|
| TValidateGcpConnectionCredentials
|
||||||
| TValidateAzureKeyVaultConnectionCredentials
|
| TValidateAzureKeyVaultConnectionCredentials
|
||||||
| TValidateAzureAppConfigurationConnectionCredentials
|
| TValidateAzureAppConfigurationConnectionCredentials
|
||||||
| TValidateDatabricksConnectionCredentials
|
| TValidateDatabricksConnectionCredentials;
|
||||||
| TValidateHumanitecConnectionCredentials;
|
|
||||||
|
|
||||||
export type TListAwsConnectionKmsKeys = {
|
export type TListAwsConnectionKmsKeys = {
|
||||||
connectionId: string;
|
connectionId: string;
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
export enum HumanitecConnectionMethod {
|
|
||||||
API_TOKEN = "api-token"
|
|
||||||
}
|
|
@@ -1,95 +0,0 @@
|
|||||||
import { AxiosError, AxiosResponse } from "axios";
|
|
||||||
|
|
||||||
import { request } from "@app/lib/config/request";
|
|
||||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
|
||||||
|
|
||||||
import { HumanitecConnectionMethod } from "./humanitec-connection-enums";
|
|
||||||
import {
|
|
||||||
HumanitecApp,
|
|
||||||
HumanitecOrg,
|
|
||||||
HumanitecOrgWithApps,
|
|
||||||
THumanitecConnection,
|
|
||||||
THumanitecConnectionConfig
|
|
||||||
} from "./humanitec-connection-types";
|
|
||||||
|
|
||||||
export const getHumanitecConnectionListItem = () => {
|
|
||||||
return {
|
|
||||||
name: "Humanitec" as const,
|
|
||||||
app: AppConnection.Humanitec as const,
|
|
||||||
methods: Object.values(HumanitecConnectionMethod) as [HumanitecConnectionMethod.API_TOKEN]
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateHumanitecConnectionCredentials = async (config: THumanitecConnectionConfig) => {
|
|
||||||
const { credentials: inputCredentials } = config;
|
|
||||||
|
|
||||||
let response: AxiosResponse<HumanitecOrg[]> | null = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await request.get<HumanitecOrg[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${inputCredentials.apiToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error: unknown) {
|
|
||||||
if (error instanceof AxiosError) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to validate credentials: ${error.message || "Unknown error"}`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Unable to validate connection - verify credentials"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response?.data) {
|
|
||||||
throw new InternalServerError({
|
|
||||||
message: "Failed to get organizations: Response was empty"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputCredentials;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const listOrganizations = async (appConnection: THumanitecConnection): Promise<HumanitecOrgWithApps[]> => {
|
|
||||||
const {
|
|
||||||
credentials: { apiToken }
|
|
||||||
} = appConnection;
|
|
||||||
const response = await request.get<HumanitecOrg[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.data) {
|
|
||||||
throw new InternalServerError({
|
|
||||||
message: "Failed to get organizations: Response was empty"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const orgs = response.data;
|
|
||||||
const orgsWithApps: HumanitecOrgWithApps[] = [];
|
|
||||||
|
|
||||||
for (const org of orgs) {
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
|
||||||
const appsResponse = await request.get<HumanitecApp[]>(`${IntegrationUrls.HUMANITEC_API_URL}/orgs/${org.id}/apps`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${apiToken}`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (appsResponse.data) {
|
|
||||||
const apps = appsResponse.data;
|
|
||||||
orgsWithApps.push({
|
|
||||||
...org,
|
|
||||||
apps: apps.map((app) => ({
|
|
||||||
name: app.name,
|
|
||||||
id: app.id,
|
|
||||||
envs: app.envs
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return orgsWithApps;
|
|
||||||
};
|
|
@@ -1,58 +0,0 @@
|
|||||||
import z from "zod";
|
|
||||||
|
|
||||||
import { AppConnections } from "@app/lib/api-docs";
|
|
||||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
|
||||||
import {
|
|
||||||
BaseAppConnectionSchema,
|
|
||||||
GenericCreateAppConnectionFieldsSchema,
|
|
||||||
GenericUpdateAppConnectionFieldsSchema
|
|
||||||
} from "@app/services/app-connection/app-connection-schemas";
|
|
||||||
|
|
||||||
import { HumanitecConnectionMethod } from "./humanitec-connection-enums";
|
|
||||||
|
|
||||||
export const HumanitecConnectionAccessTokenCredentialsSchema = z.object({
|
|
||||||
apiToken: z.string().trim().min(1, "API Token required")
|
|
||||||
});
|
|
||||||
|
|
||||||
const BaseHumanitecConnectionSchema = BaseAppConnectionSchema.extend({ app: z.literal(AppConnection.Humanitec) });
|
|
||||||
|
|
||||||
export const HumanitecConnectionSchema = BaseHumanitecConnectionSchema.extend({
|
|
||||||
method: z.literal(HumanitecConnectionMethod.API_TOKEN),
|
|
||||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema
|
|
||||||
});
|
|
||||||
|
|
||||||
export const SanitizedHumanitecConnectionSchema = z.discriminatedUnion("method", [
|
|
||||||
BaseHumanitecConnectionSchema.extend({
|
|
||||||
method: z.literal(HumanitecConnectionMethod.API_TOKEN),
|
|
||||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema.pick({})
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const ValidateHumanitecConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
|
||||||
z.object({
|
|
||||||
method: z
|
|
||||||
.literal(HumanitecConnectionMethod.API_TOKEN)
|
|
||||||
.describe(AppConnections?.CREATE(AppConnection.Humanitec).method),
|
|
||||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema.describe(
|
|
||||||
AppConnections.CREATE(AppConnection.Humanitec).credentials
|
|
||||||
)
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const CreateHumanitecConnectionSchema = ValidateHumanitecConnectionCredentialsSchema.and(
|
|
||||||
GenericCreateAppConnectionFieldsSchema(AppConnection.Humanitec)
|
|
||||||
);
|
|
||||||
|
|
||||||
export const UpdateHumanitecConnectionSchema = z
|
|
||||||
.object({
|
|
||||||
credentials: HumanitecConnectionAccessTokenCredentialsSchema.optional().describe(
|
|
||||||
AppConnections.UPDATE(AppConnection.Humanitec).credentials
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Humanitec));
|
|
||||||
|
|
||||||
export const HumanitecConnectionListItemSchema = z.object({
|
|
||||||
name: z.literal("Humanitec"),
|
|
||||||
app: z.literal(AppConnection.Humanitec),
|
|
||||||
methods: z.nativeEnum(HumanitecConnectionMethod).array()
|
|
||||||
});
|
|
@@ -1,29 +0,0 @@
|
|||||||
import { logger } from "@app/lib/logger";
|
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
|
||||||
import { listOrganizations as getHumanitecOrganizations } from "./humanitec-connection-fns";
|
|
||||||
import { THumanitecConnection } from "./humanitec-connection-types";
|
|
||||||
|
|
||||||
type TGetAppConnectionFunc = (
|
|
||||||
app: AppConnection,
|
|
||||||
connectionId: string,
|
|
||||||
actor: OrgServiceActor
|
|
||||||
) => Promise<THumanitecConnection>;
|
|
||||||
|
|
||||||
export const humanitecConnectionService = (getAppConnection: TGetAppConnectionFunc) => {
|
|
||||||
const listOrganizations = async (connectionId: string, actor: OrgServiceActor) => {
|
|
||||||
const appConnection = await getAppConnection(AppConnection.Humanitec, connectionId, actor);
|
|
||||||
try {
|
|
||||||
const organizations = await getHumanitecOrganizations(appConnection);
|
|
||||||
return organizations;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error, "Failed to establish connection with Humanitec");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
listOrganizations
|
|
||||||
};
|
|
||||||
};
|
|
@@ -1,40 +0,0 @@
|
|||||||
import z from "zod";
|
|
||||||
|
|
||||||
import { DiscriminativePick } from "@app/lib/types";
|
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
|
||||||
import {
|
|
||||||
CreateHumanitecConnectionSchema,
|
|
||||||
HumanitecConnectionSchema,
|
|
||||||
ValidateHumanitecConnectionCredentialsSchema
|
|
||||||
} from "./humanitec-connection-schemas";
|
|
||||||
|
|
||||||
export type THumanitecConnection = z.infer<typeof HumanitecConnectionSchema>;
|
|
||||||
|
|
||||||
export type THumanitecConnectionInput = z.infer<typeof CreateHumanitecConnectionSchema> & {
|
|
||||||
app: AppConnection.Humanitec;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TValidateHumanitecConnectionCredentials = typeof ValidateHumanitecConnectionCredentialsSchema;
|
|
||||||
|
|
||||||
export type THumanitecConnectionConfig = DiscriminativePick<
|
|
||||||
THumanitecConnectionInput,
|
|
||||||
"method" | "app" | "credentials"
|
|
||||||
> & {
|
|
||||||
orgId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HumanitecOrg = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HumanitecApp = {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
envs: { name: string; id: string }[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HumanitecOrgWithApps = HumanitecOrg & {
|
|
||||||
apps: HumanitecApp[];
|
|
||||||
};
|
|
@@ -1,4 +0,0 @@
|
|||||||
export * from "./humanitec-connection-enums";
|
|
||||||
export * from "./humanitec-connection-fns";
|
|
||||||
export * from "./humanitec-connection-schemas";
|
|
||||||
export * from "./humanitec-connection-types";
|
|
@@ -45,36 +45,6 @@ export const validateSignUpAuthorization = (token: string, userId: string, valid
|
|||||||
if (decodedToken.userId !== userId) throw new UnauthorizedError();
|
if (decodedToken.userId !== userId) throw new UnauthorizedError();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validatePasswordResetAuthorization = (token?: string) => {
|
|
||||||
if (!token) throw new UnauthorizedError();
|
|
||||||
|
|
||||||
const appCfg = getConfig();
|
|
||||||
const [AUTH_TOKEN_TYPE, AUTH_TOKEN_VALUE] = <[string, string]>token?.split(" ", 2) ?? [null, null];
|
|
||||||
if (AUTH_TOKEN_TYPE === null) {
|
|
||||||
throw new UnauthorizedError({ message: "Missing Authorization Header in the request header." });
|
|
||||||
}
|
|
||||||
if (AUTH_TOKEN_TYPE.toLowerCase() !== "bearer") {
|
|
||||||
throw new UnauthorizedError({
|
|
||||||
message: `The provided authentication type '${AUTH_TOKEN_TYPE}' is not supported.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (AUTH_TOKEN_VALUE === null) {
|
|
||||||
throw new UnauthorizedError({
|
|
||||||
message: "Missing Authorization Body in the request header"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedToken = jwt.verify(AUTH_TOKEN_VALUE, appCfg.AUTH_SECRET) as AuthModeProviderSignUpTokenPayload;
|
|
||||||
|
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.SIGNUP_TOKEN) {
|
|
||||||
throw new UnauthorizedError({
|
|
||||||
message: `The provided authentication token type is not supported.`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodedToken;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const enforceUserLockStatus = (isLocked: boolean, temporaryLockDateEnd?: Date | null) => {
|
export const enforceUserLockStatus = (isLocked: boolean, temporaryLockDateEnd?: Date | null) => {
|
||||||
if (isLocked) {
|
if (isLocked) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
|
@@ -4,10 +4,7 @@ import jwt from "jsonwebtoken";
|
|||||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
||||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
|
||||||
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||||
@@ -15,13 +12,10 @@ import { TokenType } from "../auth-token/auth-token-types";
|
|||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TTotpConfigDALFactory } from "../totp/totp-config-dal";
|
import { TTotpConfigDALFactory } from "../totp/totp-config-dal";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { UserEncryption } from "../user/user-types";
|
|
||||||
import { TAuthDALFactory } from "./auth-dal";
|
import { TAuthDALFactory } from "./auth-dal";
|
||||||
import {
|
import {
|
||||||
ResetPasswordV2Type,
|
|
||||||
TChangePasswordDTO,
|
TChangePasswordDTO,
|
||||||
TCreateBackupPrivateKeyDTO,
|
TCreateBackupPrivateKeyDTO,
|
||||||
TResetPasswordV2DTO,
|
|
||||||
TResetPasswordViaBackupKeyDTO,
|
TResetPasswordViaBackupKeyDTO,
|
||||||
TSetupPasswordViaBackupKeyDTO
|
TSetupPasswordViaBackupKeyDTO
|
||||||
} from "./auth-password-type";
|
} from "./auth-password-type";
|
||||||
@@ -120,31 +114,26 @@ export const authPaswordServiceFactory = ({
|
|||||||
* Email password reset flow via email. Step 1 send email
|
* Email password reset flow via email. Step 1 send email
|
||||||
*/
|
*/
|
||||||
const sendPasswordResetEmail = async (email: string) => {
|
const sendPasswordResetEmail = async (email: string) => {
|
||||||
const sendEmail = async () => {
|
const user = await userDAL.findUserByUsername(email);
|
||||||
const user = await userDAL.findUserByUsername(email);
|
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
||||||
|
if (!user || (user && !user.isAccepted)) return;
|
||||||
|
|
||||||
if (user && user.isAccepted) {
|
const cfg = getConfig();
|
||||||
const cfg = getConfig();
|
const token = await tokenService.createTokenForUser({
|
||||||
const token = await tokenService.createTokenForUser({
|
type: TokenType.TOKEN_EMAIL_PASSWORD_RESET,
|
||||||
type: TokenType.TOKEN_EMAIL_PASSWORD_RESET,
|
userId: user.id
|
||||||
userId: user.id
|
});
|
||||||
});
|
|
||||||
|
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.ResetPassword,
|
template: SmtpTemplates.ResetPassword,
|
||||||
recipients: [email],
|
recipients: [email],
|
||||||
subjectLine: "Infisical password reset",
|
subjectLine: "Infisical password reset",
|
||||||
substitutions: {
|
substitutions: {
|
||||||
email,
|
email,
|
||||||
token,
|
token,
|
||||||
callback_url: cfg.SITE_URL ? `${cfg.SITE_URL}/password-reset` : ""
|
callback_url: cfg.SITE_URL ? `${cfg.SITE_URL}/password-reset` : ""
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
// note(daniel): run in background to prevent timing attacks
|
|
||||||
void sendEmail().catch((err) => logger.error(err, "Failed to send password reset email"));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -153,11 +142,6 @@ export const authPaswordServiceFactory = ({
|
|||||||
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
const verifyPasswordResetEmail = async (email: string, code: string) => {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const user = await userDAL.findUserByUsername(email);
|
const user = await userDAL.findUserByUsername(email);
|
||||||
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUserId(user.id);
|
|
||||||
|
|
||||||
if (!userEnc) throw new BadRequestError({ message: "Failed to find user encryption data" });
|
|
||||||
|
|
||||||
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
// ignore as user is not found to avoid an outside entity to identify infisical registered accounts
|
||||||
if (!user || (user && !user.isAccepted)) {
|
if (!user || (user && !user.isAccepted)) {
|
||||||
throw new Error("Failed email verification for pass reset");
|
throw new Error("Failed email verification for pass reset");
|
||||||
@@ -178,91 +162,8 @@ export const authPaswordServiceFactory = ({
|
|||||||
{ expiresIn: cfg.JWT_SIGNUP_LIFETIME }
|
{ expiresIn: cfg.JWT_SIGNUP_LIFETIME }
|
||||||
);
|
);
|
||||||
|
|
||||||
return { token, user, userEncryptionVersion: userEnc.encryptionVersion as UserEncryption };
|
return { token, user };
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetPasswordV2 = async ({ userId, newPassword, type, oldPassword }: TResetPasswordV2DTO) => {
|
|
||||||
const cfg = getConfig();
|
|
||||||
|
|
||||||
const user = await userDAL.findUserEncKeyByUserId(userId);
|
|
||||||
if (!user) {
|
|
||||||
throw new BadRequestError({ message: `User encryption key not found for user with ID '${userId}'` });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user.hashedPassword) {
|
|
||||||
throw new BadRequestError({ message: "Unable to reset password, no password is set" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user.authMethods?.includes(AuthMethod.EMAIL)) {
|
|
||||||
throw new BadRequestError({ message: "Unable to reset password, no email authentication method is configured" });
|
|
||||||
}
|
|
||||||
|
|
||||||
// we check the old password if the user is resetting their password while logged in
|
|
||||||
if (type === ResetPasswordV2Type.LoggedInReset) {
|
|
||||||
if (!oldPassword) {
|
|
||||||
throw new BadRequestError({ message: "Current password is required." });
|
|
||||||
}
|
|
||||||
|
|
||||||
const isValid = await bcrypt.compare(oldPassword, user.hashedPassword);
|
|
||||||
if (!isValid) {
|
|
||||||
throw new BadRequestError({ message: "Incorrect current password." });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const newHashedPassword = await bcrypt.hash(newPassword, cfg.BCRYPT_SALT_ROUND);
|
|
||||||
|
|
||||||
// we need to get the original private key first for v2
|
|
||||||
let privateKey: string;
|
|
||||||
if (
|
|
||||||
user.serverEncryptedPrivateKey &&
|
|
||||||
user.serverEncryptedPrivateKeyTag &&
|
|
||||||
user.serverEncryptedPrivateKeyIV &&
|
|
||||||
user.serverEncryptedPrivateKeyEncoding &&
|
|
||||||
user.encryptionVersion === UserEncryption.V2
|
|
||||||
) {
|
|
||||||
privateKey = infisicalSymmetricDecrypt({
|
|
||||||
iv: user.serverEncryptedPrivateKeyIV,
|
|
||||||
tag: user.serverEncryptedPrivateKeyTag,
|
|
||||||
ciphertext: user.serverEncryptedPrivateKey,
|
|
||||||
keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Cannot reset password without current credentials or recovery method",
|
|
||||||
name: "Reset password"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const encKeys = await generateUserSrpKeys(user.username, newPassword, {
|
|
||||||
publicKey: user.publicKey,
|
|
||||||
privateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const { tag, iv, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey);
|
|
||||||
|
|
||||||
await userDAL.updateUserEncryptionByUserId(userId, {
|
|
||||||
hashedPassword: newHashedPassword,
|
|
||||||
|
|
||||||
// srp params
|
|
||||||
salt: encKeys.salt,
|
|
||||||
verifier: encKeys.verifier,
|
|
||||||
|
|
||||||
protectedKey: encKeys.protectedKey,
|
|
||||||
protectedKeyIV: encKeys.protectedKeyIV,
|
|
||||||
protectedKeyTag: encKeys.protectedKeyTag,
|
|
||||||
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
|
||||||
iv: encKeys.encryptedPrivateKeyIV,
|
|
||||||
tag: encKeys.encryptedPrivateKeyTag,
|
|
||||||
|
|
||||||
serverEncryptedPrivateKey: ciphertext,
|
|
||||||
serverEncryptedPrivateKeyIV: iv,
|
|
||||||
serverEncryptedPrivateKeyTag: tag,
|
|
||||||
serverEncryptedPrivateKeyEncoding: encoding
|
|
||||||
});
|
|
||||||
|
|
||||||
await tokenService.revokeAllMySessions(userId);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reset password of a user via backup key
|
* Reset password of a user via backup key
|
||||||
* */
|
* */
|
||||||
@@ -490,7 +391,6 @@ export const authPaswordServiceFactory = ({
|
|||||||
createBackupPrivateKey,
|
createBackupPrivateKey,
|
||||||
getBackupPrivateKeyOfUser,
|
getBackupPrivateKeyOfUser,
|
||||||
sendPasswordSetupEmail,
|
sendPasswordSetupEmail,
|
||||||
setupPassword,
|
setupPassword
|
||||||
resetPasswordV2
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -13,18 +13,6 @@ export type TChangePasswordDTO = {
|
|||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum ResetPasswordV2Type {
|
|
||||||
Recovery = "recovery",
|
|
||||||
LoggedInReset = "logged-in-reset"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TResetPasswordV2DTO = {
|
|
||||||
type: ResetPasswordV2Type;
|
|
||||||
userId: string;
|
|
||||||
newPassword: string;
|
|
||||||
oldPassword?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TResetPasswordViaBackupKeyDTO = {
|
export type TResetPasswordViaBackupKeyDTO = {
|
||||||
userId: string;
|
userId: string;
|
||||||
protectedKey: string;
|
protectedKey: string;
|
||||||
|
@@ -31,9 +31,9 @@ export type TImportDataIntoInfisicalDTO = {
|
|||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "find">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany">;
|
||||||
@@ -772,10 +772,6 @@ export const importDataIntoInfisicalFn = async ({
|
|||||||
secretVersionDAL,
|
secretVersionDAL,
|
||||||
secretTagDAL,
|
secretTagDAL,
|
||||||
secretVersionTagDAL,
|
secretVersionTagDAL,
|
||||||
actor: {
|
|
||||||
type: actor,
|
|
||||||
actorId
|
|
||||||
},
|
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -27,9 +27,9 @@ export type TExternalMigrationQueueFactoryDep = {
|
|||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "find" | "findLastEnvPosition" | "create" | "findOne">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys" | "find">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "insertMany" | "upsertSecretReferences" | "findBySecretKeys">;
|
||||||
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
secretVersionDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "create">;
|
||||||
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create" | "find">;
|
secretTagDAL: Pick<TSecretTagDALFactory, "saveTagsToSecretV2" | "create">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
secretVersionTagDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany" | "create">;
|
||||||
|
|
||||||
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findOne" | "findById">;
|
folderDAL: Pick<TSecretFolderDALFactory, "create" | "findBySecretPath" | "findOne" | "findById">;
|
||||||
|
@@ -4,7 +4,7 @@ import ms from "ms";
|
|||||||
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
|
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } 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 { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
|
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@@ -102,13 +102,11 @@ export const groupProjectServiceFactory = ({
|
|||||||
project.id
|
project.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
if (!hasRequiredPrivileges) {
|
||||||
name: "PermissionBoundaryError",
|
throw new ForbiddenRequestError({ message: "Failed to assign group to a more privileged role" });
|
||||||
message: "Failed to assign group to a more privileged role",
|
}
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate custom roles input
|
// validate custom roles input
|
||||||
@@ -269,13 +267,12 @@ export const groupProjectServiceFactory = ({
|
|||||||
requestedRoleChange,
|
requestedRoleChange,
|
||||||
project.id
|
project.id
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
|
||||||
if (!permissionBoundary.isValid)
|
const hasRequiredPrivileges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
name: "PermissionBoundaryError",
|
if (!hasRequiredPrivileges) {
|
||||||
message: "Failed to assign group to a more privileged role",
|
throw new ForbiddenRequestError({ message: "Failed to assign group to a more privileged role" });
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate custom roles input
|
// validate custom roles input
|
||||||
|
@@ -78,7 +78,9 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => {
|
const renewAccessToken = async ({ accessToken }: TRenewAccessTokenDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as TIdentityAccessTokenJwtPayload;
|
const decodedToken = jwt.verify(accessToken, appCfg.AUTH_SECRET) as JwtPayload & {
|
||||||
|
identityAccessTokenId: string;
|
||||||
|
};
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) {
|
if (decodedToken.authTokenType !== AuthTokenType.IDENTITY_ACCESS_TOKEN) {
|
||||||
throw new BadRequestError({ message: "Only identity access tokens can be renewed" });
|
throw new BadRequestError({ message: "Only identity access tokens can be renewed" });
|
||||||
}
|
}
|
||||||
@@ -125,23 +127,7 @@ export const identityAccessTokenServiceFactory = ({
|
|||||||
accessTokenLastRenewedAt: new Date()
|
accessTokenLastRenewedAt: new Date()
|
||||||
});
|
});
|
||||||
|
|
||||||
const renewedToken = jwt.sign(
|
return { accessToken, identityAccessToken: updatedIdentityAccessToken };
|
||||||
{
|
|
||||||
identityId: decodedToken.identityId,
|
|
||||||
clientSecretId: decodedToken.clientSecretId,
|
|
||||||
identityAccessTokenId: decodedToken.identityAccessTokenId,
|
|
||||||
authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
|
|
||||||
} as TIdentityAccessTokenJwtPayload,
|
|
||||||
appCfg.AUTH_SECRET,
|
|
||||||
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
|
|
||||||
Number(identityAccessToken.accessTokenTTL) === 0
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
expiresIn: Number(identityAccessToken.accessTokenTTL)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return { accessToken: renewedToken, identityAccessToken: updatedIdentityAccessToken };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const revokeAccessToken = async (accessToken: string) => {
|
const revokeAccessToken = async (accessToken: string) => {
|
||||||
|
@@ -7,7 +7,7 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
@@ -339,12 +339,9 @@ export const identityAwsAuthServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke aws auth of identity with more privileged role"
|
||||||
message: "Failed to revoke aws auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const revokedIdentityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
|
const revokedIdentityAwsAuth = await identityAwsAuthDAL.transaction(async (tx) => {
|
||||||
|
@@ -5,7 +5,7 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
@@ -312,12 +312,9 @@ export const identityAzureAuthServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke azure auth of identity with more privileged role"
|
||||||
message: "Failed to revoke azure auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const revokedIdentityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => {
|
const revokedIdentityAzureAuth = await identityAzureAuthDAL.transaction(async (tx) => {
|
||||||
|
@@ -5,7 +5,7 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
@@ -358,12 +358,9 @@ export const identityGcpAuthServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke gcp auth of identity with more privileged role"
|
||||||
message: "Failed to revoke gcp auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => {
|
const revokedIdentityGcpAuth = await identityGcpAuthDAL.transaction(async (tx) => {
|
||||||
|
@@ -7,7 +7,7 @@ import { IdentityAuthMethod, TIdentityJwtAuthsUpdate } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
@@ -78,22 +78,14 @@ export const identityJwtAuthServiceFactory = ({
|
|||||||
let tokenData: Record<string, string | boolean | number> = {};
|
let tokenData: Record<string, string | boolean | number> = {};
|
||||||
|
|
||||||
if (identityJwtAuth.configurationType === JwtConfigurationType.JWKS) {
|
if (identityJwtAuth.configurationType === JwtConfigurationType.JWKS) {
|
||||||
let client: JwksClient;
|
const decryptedJwksCaCert = orgDataKeyDecryptor({
|
||||||
if (identityJwtAuth.jwksUrl.includes("https:")) {
|
cipherTextBlob: identityJwtAuth.encryptedJwksCaCert
|
||||||
const decryptedJwksCaCert = orgDataKeyDecryptor({
|
}).toString();
|
||||||
cipherTextBlob: identityJwtAuth.encryptedJwksCaCert
|
const requestAgent = new https.Agent({ ca: decryptedJwksCaCert, rejectUnauthorized: !!decryptedJwksCaCert });
|
||||||
}).toString();
|
const client = new JwksClient({
|
||||||
|
jwksUri: identityJwtAuth.jwksUrl,
|
||||||
const requestAgent = new https.Agent({ ca: decryptedJwksCaCert, rejectUnauthorized: !!decryptedJwksCaCert });
|
requestAgent
|
||||||
client = new JwksClient({
|
});
|
||||||
jwksUri: identityJwtAuth.jwksUrl,
|
|
||||||
requestAgent
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
client = new JwksClient({
|
|
||||||
jwksUri: identityJwtAuth.jwksUrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { kid } = decodedToken.header;
|
const { kid } = decodedToken.header;
|
||||||
const jwtSigningKey = await client.getSigningKey(kid);
|
const jwtSigningKey = await client.getSigningKey(kid);
|
||||||
@@ -516,13 +508,11 @@ export const identityJwtAuthServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission)) {
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke JWT auth of identity with more privileged role"
|
||||||
message: "Failed to revoke jwt auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const revokedIdentityJwtAuth = await identityJwtAuthDAL.transaction(async (tx) => {
|
const revokedIdentityJwtAuth = await identityJwtAuthDAL.transaction(async (tx) => {
|
||||||
const deletedJwtAuth = await identityJwtAuthDAL.delete({ identityId }, tx);
|
const deletedJwtAuth = await identityJwtAuthDAL.delete({ identityId }, tx);
|
||||||
|
@@ -7,7 +7,7 @@ import { IdentityAuthMethod, TIdentityKubernetesAuthsUpdate } from "@app/db/sche
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
@@ -487,12 +487,9 @@ export const identityKubernetesAuthServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke kubernetes auth of identity with more privileged role"
|
||||||
message: "Failed to revoke kubernetes auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const revokedIdentityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => {
|
const revokedIdentityKubernetesAuth = await identityKubernetesAuthDAL.transaction(async (tx) => {
|
||||||
|
@@ -8,7 +8,7 @@ import { IdentityAuthMethod, TIdentityOidcAuthsUpdate } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
@@ -428,13 +428,11 @@ export const identityOidcAuthServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission)) {
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke OIDC auth of identity with more privileged role"
|
||||||
message: "Failed to revoke oidc auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const revokedIdentityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => {
|
const revokedIdentityOidcAuth = await identityOidcAuthDAL.transaction(async (tx) => {
|
||||||
const deletedOidcAuth = await identityOidcAuthDAL.delete({ identityId }, tx);
|
const deletedOidcAuth = await identityOidcAuthDAL.delete({ identityId }, tx);
|
||||||
|
@@ -4,7 +4,7 @@ import ms from "ms";
|
|||||||
import { ActionProjectType, ProjectMembershipRole } from "@app/db/schemas";
|
import { ActionProjectType, ProjectMembershipRole } 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 { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
|
|
||||||
@@ -91,13 +91,11 @@ export const identityProjectServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
if (!hasRequiredPriviledges) {
|
||||||
name: "PermissionBoundaryError",
|
throw new ForbiddenRequestError({ message: "Failed to change to a more privileged role" });
|
||||||
message: "Failed to assign to a more privileged role",
|
}
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate custom roles input
|
// validate custom roles input
|
||||||
@@ -187,13 +185,9 @@ export const identityProjectServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission)) {
|
||||||
if (!permissionBoundary.isValid)
|
throw new ForbiddenRequestError({ message: "Failed to change to a more privileged role" });
|
||||||
throw new ForbiddenRequestError({
|
}
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to change to a more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate custom roles input
|
// validate custom roles input
|
||||||
@@ -283,13 +277,8 @@ export const identityProjectServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
|
if (!isAtLeastAsPrivileged(permission, identityRolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
|
||||||
throw new ForbiddenRequestError({
|
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to remove more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const [deletedIdentity] = await identityProjectDAL.delete({ identityId, projectId });
|
const [deletedIdentity] = await identityProjectDAL.delete({ identityId, projectId });
|
||||||
return deletedIdentity;
|
return deletedIdentity;
|
||||||
|
@@ -5,7 +5,7 @@ import { IdentityAuthMethod, TableName } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
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 { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
|
||||||
@@ -245,13 +245,11 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission)) {
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke Token Auth of identity with more privileged role"
|
||||||
message: "Failed to revoke token auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const revokedIdentityTokenAuth = await identityTokenAuthDAL.transaction(async (tx) => {
|
const revokedIdentityTokenAuth = await identityTokenAuthDAL.transaction(async (tx) => {
|
||||||
const deletedTokenAuth = await identityTokenAuthDAL.delete({ identityId }, tx);
|
const deletedTokenAuth = await identityTokenAuthDAL.delete({ identityId }, tx);
|
||||||
@@ -297,12 +295,10 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasPriviledge)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to create token for identity with more privileged role"
|
||||||
message: "Failed to create token for identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId });
|
const identityTokenAuth = await identityTokenAuthDAL.findOne({ identityId });
|
||||||
@@ -419,12 +415,10 @@ export const identityTokenAuthServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasPriviledge)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to update token for identity with more privileged role"
|
||||||
message: "Failed to update token for identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [token] = await identityAccessTokenDAL.update(
|
const [token] = await identityAccessTokenDAL.update(
|
||||||
|
@@ -8,7 +8,7 @@ import { IdentityAuthMethod } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from "@app/lib/ip";
|
import { checkIPAgainstBlocklist, extractIPDetails, isValidIpOrCidr, TIp } from "@app/lib/ip";
|
||||||
@@ -367,12 +367,9 @@ export const identityUaServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke universal auth of identity with more privileged role"
|
||||||
message: "Failed to revoke universal auth of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const revokedIdentityUniversalAuth = await identityUaDAL.transaction(async (tx) => {
|
const revokedIdentityUniversalAuth = await identityUaDAL.transaction(async (tx) => {
|
||||||
@@ -417,12 +414,10 @@ export const identityUaServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasPriviledge = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasPriviledge)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to add identity to project with more privileged role"
|
||||||
message: "Failed to create client secret for a more privileged identity.",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
@@ -480,12 +475,9 @@ export const identityUaServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to add identity to project with more privileged role"
|
||||||
message: "Failed to get identity client secret with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityUniversalAuth = await identityUaDAL.findOne({
|
const identityUniversalAuth = await identityUaDAL.findOne({
|
||||||
@@ -532,12 +524,9 @@ export const identityUaServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to read identity client secret of project with more privileged role"
|
||||||
message: "Failed to read identity client secret of identity with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId);
|
const clientSecret = await identityUaClientSecretDAL.findById(clientSecretId);
|
||||||
@@ -577,12 +566,10 @@ export const identityUaServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
|
||||||
if (!permissionBoundary.isValid)
|
if (!isAtLeastAsPrivileged(permission, rolePermission))
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: "Failed to revoke identity client secret with more privileged role"
|
||||||
message: "Failed to revoke identity client secret with more privileged role",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
|
const clientSecret = await identityUaClientSecretDAL.updateById(clientSecretId, {
|
||||||
|
@@ -1,42 +1,10 @@
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TIdentities } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
|
||||||
|
|
||||||
export type TIdentityDALFactory = ReturnType<typeof identityDALFactory>;
|
export type TIdentityDALFactory = ReturnType<typeof identityDALFactory>;
|
||||||
|
|
||||||
export const identityDALFactory = (db: TDbClient) => {
|
export const identityDALFactory = (db: TDbClient) => {
|
||||||
const identityOrm = ormify(db, TableName.Identity);
|
const identityOrm = ormify(db, TableName.Identity);
|
||||||
|
return identityOrm;
|
||||||
const getIdentitiesByFilter = async ({
|
|
||||||
limit,
|
|
||||||
offset,
|
|
||||||
searchTerm,
|
|
||||||
sortBy
|
|
||||||
}: {
|
|
||||||
limit: number;
|
|
||||||
offset: number;
|
|
||||||
searchTerm: string;
|
|
||||||
sortBy?: keyof TIdentities;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
let query = db.replicaNode()(TableName.Identity);
|
|
||||||
|
|
||||||
if (searchTerm) {
|
|
||||||
query = query.where((qb) => {
|
|
||||||
void qb.whereILike("name", `%${searchTerm}%`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortBy) {
|
|
||||||
query = query.orderBy(sortBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await query.limit(limit).offset(offset).select(selectAllTableCols(TableName.Identity));
|
|
||||||
} catch (error) {
|
|
||||||
throw new DatabaseError({ error, name: "Get identities by filter" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return { ...identityOrm, getIdentitiesByFilter };
|
|
||||||
};
|
};
|
||||||
|
@@ -4,7 +4,7 @@ import { OrgMembershipRole, TableName, TOrgRoles } from "@app/db/schemas";
|
|||||||
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";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
|
||||||
@@ -58,13 +58,9 @@ export const identityServiceFactory = ({
|
|||||||
orgId
|
orgId
|
||||||
);
|
);
|
||||||
const isCustomRole = Boolean(customRole);
|
const isCustomRole = Boolean(customRole);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to create a more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
|
||||||
@@ -133,13 +129,9 @@ export const identityServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to update a more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
let customRole: TOrgRoles | undefined;
|
let customRole: TOrgRoles | undefined;
|
||||||
if (role) {
|
if (role) {
|
||||||
@@ -149,13 +141,9 @@ export const identityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const isCustomRole = Boolean(customOrgRole);
|
const isCustomRole = Boolean(customOrgRole);
|
||||||
const appliedRolePermissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!appliedRolePermissionBoundary.isValid)
|
if (!hasRequiredNewRolePermission)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to create a more privileged identity",
|
|
||||||
details: { missingPermissions: appliedRolePermissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
if (isCustomRole) customRole = customOrgRole;
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,13 +216,9 @@ export const identityServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, identityRolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({ message: "Failed to delete more privileged identity" });
|
||||||
name: "PermissionBoundaryError",
|
|
||||||
message: "Failed to delete more privileged identity",
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
|
||||||
|
|
||||||
const deletedIdentity = await identityDAL.deleteById(id);
|
const deletedIdentity = await identityDAL.deleteById(id);
|
||||||
|
|
||||||
|
@@ -114,27 +114,20 @@ export const integrationAuthServiceFactory = ({
|
|||||||
const listOrgIntegrationAuth = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGenericPermission) => {
|
const listOrgIntegrationAuth = async ({ actorId, actor, actorOrgId, actorAuthMethod }: TGenericPermission) => {
|
||||||
const authorizations = await integrationAuthDAL.getByOrg(actorOrgId as string);
|
const authorizations = await integrationAuthDAL.getByOrg(actorOrgId as string);
|
||||||
|
|
||||||
const filteredAuthorizations = await Promise.all(
|
return Promise.all(
|
||||||
authorizations.map(async (auth) => {
|
authorizations.filter(async (auth) => {
|
||||||
try {
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
actor,
|
||||||
actor,
|
actorId,
|
||||||
actorId,
|
projectId: auth.projectId,
|
||||||
projectId: auth.projectId,
|
actorAuthMethod,
|
||||||
actorAuthMethod,
|
actorOrgId,
|
||||||
actorOrgId,
|
actionProjectType: ActionProjectType.SecretManager
|
||||||
actionProjectType: ActionProjectType.SecretManager
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations) ? auth : null;
|
return permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
||||||
} catch (error) {
|
|
||||||
// user does not belong to the project that the integration auth belongs to
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return filteredAuthorizations.filter((auth): auth is NonNullable<typeof auth> => auth !== null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIntegrationAuth = async ({ actor, id, actorId, actorAuthMethod, actorOrgId }: TGetIntegrationAuthDTO) => {
|
const getIntegrationAuth = async ({ actor, id, actorId, actorAuthMethod, actorOrgId }: TGetIntegrationAuthDTO) => {
|
||||||
|
@@ -68,8 +68,7 @@ const getIntegrationSecretsV2 = async (
|
|||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
secretImports,
|
secretImports,
|
||||||
hasSecretAccess: () => true,
|
hasSecretAccess: () => true
|
||||||
viewSecretValue: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
for (let i = importedSecrets.length - 1; i >= 0; i -= 1) {
|
||||||
|
@@ -93,7 +93,6 @@ export enum IntegrationUrls {
|
|||||||
NORTHFLANK_API_URL = "https://api.northflank.com",
|
NORTHFLANK_API_URL = "https://api.northflank.com",
|
||||||
HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql",
|
HASURA_CLOUD_API_URL = "https://data.pro.hasura.io/v1/graphql",
|
||||||
AZURE_DEVOPS_API_URL = "https://dev.azure.com",
|
AZURE_DEVOPS_API_URL = "https://dev.azure.com",
|
||||||
HUMANITEC_API_URL = "https://api.humanitec.io",
|
|
||||||
|
|
||||||
GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com",
|
GCP_SECRET_MANAGER_SERVICE_NAME = "secretmanager.googleapis.com",
|
||||||
GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`,
|
GCP_SECRET_MANAGER_URL = `https://${GCP_SECRET_MANAGER_SERVICE_NAME}`,
|
||||||
|
@@ -1,13 +1,8 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
import { throwIfMissingSecretReadValueOrDescribePermission } from "@app/ee/services/permission/permission-fns";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import {
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
ProjectPermissionActions,
|
|
||||||
ProjectPermissionSecretActions,
|
|
||||||
ProjectPermissionSub
|
|
||||||
} from "@app/ee/services/permission/project-permission";
|
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
@@ -96,10 +91,13 @@ export const integrationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
||||||
|
|
||||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
environment: sourceEnvironment,
|
ProjectPermissionActions.Read,
|
||||||
secretPath
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
});
|
environment: sourceEnvironment,
|
||||||
|
secretPath
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
|
const folder = await folderDAL.findBySecretPath(integrationAuth.projectId, sourceEnvironment, secretPath);
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
@@ -176,10 +174,13 @@ export const integrationServiceFactory = ({
|
|||||||
const newSecretPath = secretPath || integration.secretPath;
|
const newSecretPath = secretPath || integration.secretPath;
|
||||||
|
|
||||||
if (environment || secretPath) {
|
if (environment || secretPath) {
|
||||||
throwIfMissingSecretReadValueOrDescribePermission(permission, ProjectPermissionSecretActions.ReadValue, {
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
environment: newEnvironment,
|
ProjectPermissionActions.Read,
|
||||||
secretPath: newSecretPath
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
});
|
environment: newEnvironment,
|
||||||
|
secretPath: newSecretPath
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(integration.projectId, newEnvironment, newSecretPath);
|
const folder = await folderDAL.findBySecretPath(integration.projectId, newEnvironment, newSecretPath);
|
||||||
|
@@ -19,11 +19,7 @@ import {
|
|||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
|
||||||
import {
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
OrgPermissionActions,
|
|
||||||
OrgPermissionSecretShareAction,
|
|
||||||
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";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
@@ -290,27 +286,12 @@ export const orgServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
orgId,
|
orgId,
|
||||||
data: {
|
data: { name, slug, authEnforced, scimEnabled, defaultMembershipRoleSlug, enforceMfa, selectedMfaMethod }
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
authEnforced,
|
|
||||||
scimEnabled,
|
|
||||||
defaultMembershipRoleSlug,
|
|
||||||
enforceMfa,
|
|
||||||
selectedMfaMethod,
|
|
||||||
allowSecretSharingOutsideOrganization
|
|
||||||
}
|
|
||||||
}: TUpdateOrgDTO) => {
|
}: TUpdateOrgDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
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.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
if (allowSecretSharingOutsideOrganization !== undefined) {
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
OrgPermissionSecretShareAction.ManageSettings,
|
|
||||||
OrgPermissionSubjects.SecretShare
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
const currentOrg = await orgDAL.findOrgById(actorOrgId);
|
const currentOrg = await orgDAL.findOrgById(actorOrgId);
|
||||||
|
|
||||||
@@ -377,8 +358,7 @@ export const orgServiceFactory = ({
|
|||||||
scimEnabled,
|
scimEnabled,
|
||||||
defaultMembershipRole,
|
defaultMembershipRole,
|
||||||
enforceMfa,
|
enforceMfa,
|
||||||
selectedMfaMethod,
|
selectedMfaMethod
|
||||||
allowSecretSharingOutsideOrganization
|
|
||||||
});
|
});
|
||||||
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
return org;
|
return org;
|
||||||
|
@@ -72,7 +72,6 @@ export type TUpdateOrgDTO = {
|
|||||||
defaultMembershipRoleSlug: string;
|
defaultMembershipRoleSlug: string;
|
||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
selectedMfaMethod: MfaMethod;
|
selectedMfaMethod: MfaMethod;
|
||||||
allowSecretSharingOutsideOrganization: boolean;
|
|
||||||
}>;
|
}>;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import { validatePermissionBoundary } from "@app/lib/casl/boundary";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
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 { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
@@ -274,13 +274,13 @@ export const projectMembershipServiceFactory = ({
|
|||||||
projectId
|
projectId
|
||||||
);
|
);
|
||||||
|
|
||||||
const permissionBoundary = validatePermissionBoundary(permission, rolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!permissionBoundary.isValid)
|
|
||||||
|
if (!hasRequiredPriviledges) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
name: "PermissionBoundaryError",
|
message: `Failed to change to a more privileged role ${requestedRoleChange}`
|
||||||
message: `Failed to change to a more privileged role ${requestedRoleChange}`,
|
|
||||||
details: { missingPermissions: permissionBoundary.missingPermissions }
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate custom roles input
|
// validate custom roles input
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user