mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-05 04:29:09 +00:00
Compare commits
259 Commits
misc/expor
...
misc/final
Author | SHA1 | Date | |
---|---|---|---|
ada63b9e7d | |||
f91f9c9487 | |||
f0d19e4701 | |||
7eeff6c406 | |||
132c3080bb | |||
bf09fa33fa | |||
a87e7b792c | |||
e8ca020903 | |||
a603938488 | |||
cff7981fe0 | |||
b39d5c6682 | |||
dd1f1d07cc | |||
c3f8c55672 | |||
75aeef3897 | |||
c97fe77aec | |||
3e16d7e160 | |||
6bf4b4a380 | |||
9dedaa6779 | |||
8eab7d2f01 | |||
4e796e7e41 | |||
c6fa647825 | |||
496cebb08f | |||
33db6df7f2 | |||
88d25e97e9 | |||
4ad9fa1ad1 | |||
1642fb42d8 | |||
3983c2bc4a | |||
34d87ca30f | |||
12b6f27151 | |||
ea426e8b2d | |||
4d567f0b08 | |||
6548372e3b | |||
77af640c4c | |||
90f85152bc | |||
cfa8770bdc | |||
be8562824d | |||
4f1fe8a9fa | |||
b0031b71e0 | |||
7503876ca0 | |||
36b5a3dc90 | |||
dfe36f346f | |||
b1b61842c6 | |||
f9ca9b51b2 | |||
7e7e6ade5c | |||
4010817916 | |||
eea367c3bc | |||
860ebb73a9 | |||
56567ee7c9 | |||
1cd17a451c | |||
6b7bc2a3c4 | |||
cb52568ebd | |||
9d30fb3870 | |||
161ac5e097 | |||
bb5b585cf6 | |||
fa94191c40 | |||
6a5eabc411 | |||
c956a0f91f | |||
df7b55606e | |||
5f14b27f41 | |||
02b2395276 | |||
402fa2b0e0 | |||
3725241f52 | |||
10b457a695 | |||
3912e2082d | |||
7dd6eac20a | |||
5664e1ff26 | |||
a27a428329 | |||
b196251c19 | |||
b18d8d542f | |||
3c287600ab | |||
759d11ff21 | |||
2bd817765c | |||
7aa9c5dd00 | |||
b693c035ce | |||
c65a991943 | |||
3a3811cb3c | |||
332ca61f5d | |||
64f43e59d0 | |||
ccaf4c00af | |||
e3ba1c59bf | |||
ce0bc191d8 | |||
489ccb8e15 | |||
ae8f695b6f | |||
19357d4bd7 | |||
776d0a0fe1 | |||
85dec28667 | |||
21ea7dd317 | |||
57e214ef50 | |||
1986fe9617 | |||
1309f30af9 | |||
89a4fc91ca | |||
af0ec2400d | |||
770e73e40b | |||
39fdeabdea | |||
25c26f2cde | |||
1ca8b9ba08 | |||
14d9fe01e0 | |||
216810f289 | |||
f530b78eb8 | |||
c3809ed22b | |||
9f85d8bba1 | |||
1056645ee3 | |||
5e9914b738 | |||
1ea52e6a80 | |||
20da697de8 | |||
16abf48081 | |||
e73ae485bc | |||
621f73e223 | |||
93e69bd34e | |||
e382135384 | |||
f2a554b5fd | |||
df5bdf3773 | |||
8401048daf | |||
335a87d856 | |||
1add9dd965 | |||
df46daf93d | |||
f82f7ae8d0 | |||
8536a1c987 | |||
b3cf43b46d | |||
9d4dbb63ae | |||
9c6f23fba6 | |||
babe483ca9 | |||
38ede687cd | |||
5f465c4832 | |||
a0618086b0 | |||
9a9bb4ca43 | |||
b68ddfae1b | |||
7646670378 | |||
d18be0f74c | |||
ec96db3503 | |||
7245aaa9ec | |||
d32f69e052 | |||
726477e3d7 | |||
a4ca996a1b | |||
303312fe91 | |||
f3f2879d6d | |||
d0f3d96b3e | |||
70d2a21fbc | |||
418ae42d94 | |||
273c6b3842 | |||
6be8d5d2a7 | |||
9eb7640755 | |||
741138c4bd | |||
bed620aad0 | |||
2ddf75d2e6 | |||
02d9dbb987 | |||
0ed333c2b2 | |||
55db45cd36 | |||
2d82273158 | |||
b3e61f579d | |||
d0bcbe15c6 | |||
657130eb80 | |||
3841394eb7 | |||
b1ba770a71 | |||
3552119c7d | |||
7a46725523 | |||
0515c994c7 | |||
e0d0e22e39 | |||
2f79ae42ab | |||
3bc39c6cec | |||
b5b1e57fe7 | |||
1a5f66fe46 | |||
a01f235808 | |||
b9a1629db0 | |||
203422c131 | |||
35826c288e | |||
fae4e1fa55 | |||
8094ef607a | |||
104bff0586 | |||
0fb5fa0c8b | |||
f407022e16 | |||
34d6525418 | |||
911479baff | |||
05bdbbf59d | |||
c8e47771d4 | |||
e0cbcb0318 | |||
f8d65f44e3 | |||
58ce623a2c | |||
7ae28596ec | |||
833398ef39 | |||
4e6ebcc8d9 | |||
ce8689f568 | |||
e9ab19b7f9 | |||
f2b852a09e | |||
a1c2bc695c | |||
00573ebfda | |||
3b2b8ca013 | |||
2afc6b133e | |||
b6a1ab2376 | |||
d03f890471 | |||
5ef81cd935 | |||
3e8f1d8de7 | |||
558a809b4c | |||
a749e70815 | |||
6f44f3ae21 | |||
b062ca3075 | |||
a1397f0a66 | |||
91c11d61f1 | |||
93218d5a3f | |||
5f2144eca5 | |||
45b9de63f0 | |||
114966ded4 | |||
71081d8e9a | |||
dad3d50f3e | |||
e5ca5d3da2 | |||
301cd54dc3 | |||
593bda8bc6 | |||
4db79edf19 | |||
e3a356cda9 | |||
521b24debf | |||
d6ffd4fa5f | |||
ca3b64bf6c | |||
b7e48fd556 | |||
c01ea048ce | |||
7e7d9a2bd5 | |||
782e3a8985 | |||
1c32dd5d8a | |||
8497ac831f | |||
e5821122d5 | |||
c183ef2b4f | |||
340693cfcd | |||
014b9585e0 | |||
67373b0883 | |||
2101040a77 | |||
2e2fea304b | |||
571709370d | |||
e1dbe769a8 | |||
e7e0d84c8e | |||
4c2ed1cc8b | |||
067b0f4232 | |||
6ed786e6d0 | |||
d187cc3d4d | |||
764446a2d9 | |||
614e4934a2 | |||
14e92f895a | |||
0a38374a73 | |||
ec3b94a335 | |||
ca0241bb51 | |||
7403385e7c | |||
b6955d0e9b | |||
f4ba441ec3 | |||
2cd1141a65 | |||
256627b2cc | |||
fd7e196f8b | |||
9ca8da152b | |||
7fb3076238 | |||
c6f66226c8 | |||
a0865cda2e | |||
1e7b1ccf22 | |||
d677654311 | |||
5debeb421d | |||
25b30e441a | |||
0f314c45b4 | |||
8eb668cd72 | |||
bb079b3e46 | |||
7a77dc7343 | |||
bd1ed2614e | |||
9192c5caa2 | |||
8da2213bf1 |
@ -78,3 +78,5 @@ PLAIN_API_KEY=
|
|||||||
PLAIN_WISH_LABEL_IDS=
|
PLAIN_WISH_LABEL_IDS=
|
||||||
|
|
||||||
SSL_CLIENT_CERTIFICATE_HEADER_KEY=
|
SSL_CLIENT_CERTIFICATE_HEADER_KEY=
|
||||||
|
|
||||||
|
ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT=
|
||||||
|
@ -7,12 +7,12 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
infisical-tests:
|
infisical-tests:
|
||||||
name: Run tests before deployment
|
name: Integration tests
|
||||||
# https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview
|
# https://docs.github.com/en/actions/using-workflows/reusing-workflows#overview
|
||||||
uses: ./.github/workflows/run-backend-tests.yml
|
uses: ./.github/workflows/run-backend-tests.yml
|
||||||
|
|
||||||
infisical-image:
|
infisical-image:
|
||||||
name: Build backend image
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [infisical-tests]
|
needs: [infisical-tests]
|
||||||
steps:
|
steps:
|
||||||
@ -104,8 +104,8 @@ jobs:
|
|||||||
cluster: infisical-gamma-stage
|
cluster: infisical-gamma-stage
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
|
||||||
production-postgres-deployment:
|
production-us:
|
||||||
name: Deploy to production
|
name: US production deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [gamma-deployment]
|
needs: [gamma-deployment]
|
||||||
environment:
|
environment:
|
||||||
@ -159,3 +159,54 @@ jobs:
|
|||||||
service: infisical-core-platform
|
service: infisical-core-platform
|
||||||
cluster: infisical-core-platform
|
cluster: infisical-core-platform
|
||||||
wait-for-service-stability: true
|
wait-for-service-stability: true
|
||||||
|
|
||||||
|
production-eu:
|
||||||
|
name: EU production deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [production-us]
|
||||||
|
environment:
|
||||||
|
name: production-eu
|
||||||
|
steps:
|
||||||
|
- uses: twingate/github-action@v1
|
||||||
|
with:
|
||||||
|
service-key: ${{ secrets.TWINGATE_SERVICE_KEY }}
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
uses: aws-actions/configure-aws-credentials@v4
|
||||||
|
with:
|
||||||
|
audience: sts.amazonaws.com
|
||||||
|
aws-region: eu-central-1
|
||||||
|
role-to-assume: arn:aws:iam::345594589636:role/gha-make-prod-deployment
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Setup Node.js environment
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
- name: Change directory to backend and install dependencies
|
||||||
|
env:
|
||||||
|
DB_CONNECTION_URI: ${{ secrets.DB_CONNECTION_URI }}
|
||||||
|
run: |
|
||||||
|
cd backend
|
||||||
|
npm install
|
||||||
|
npm run migration:latest
|
||||||
|
- name: Save commit hashes for tag
|
||||||
|
id: commit
|
||||||
|
uses: pr-mpt/actions-commit-hash@v2
|
||||||
|
- name: Download task definition
|
||||||
|
run: |
|
||||||
|
aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json
|
||||||
|
- name: Render Amazon ECS task definition
|
||||||
|
id: render-web-container
|
||||||
|
uses: aws-actions/amazon-ecs-render-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: task-definition.json
|
||||||
|
container-name: infisical-core-platform
|
||||||
|
image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
|
||||||
|
environment-variables: "LOG_LEVEL=info"
|
||||||
|
- name: Deploy to Amazon ECS service
|
||||||
|
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
|
||||||
|
with:
|
||||||
|
task-definition: ${{ steps.render-web-container.outputs.task-definition }}
|
||||||
|
service: infisical-core-platform
|
||||||
|
cluster: infisical-core-platform
|
||||||
|
wait-for-service-stability: true
|
@ -34,7 +34,7 @@ describe("Identity v1", async () => {
|
|||||||
test("Create identity", async () => {
|
test("Create identity", async () => {
|
||||||
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
||||||
expect(newIdentity.name).toBe("mac1");
|
expect(newIdentity.name).toBe("mac1");
|
||||||
expect(newIdentity.authMethod).toBeNull();
|
expect(newIdentity.authMethods).toEqual([]);
|
||||||
|
|
||||||
await deleteIdentity(newIdentity.id);
|
await deleteIdentity(newIdentity.id);
|
||||||
});
|
});
|
||||||
@ -42,7 +42,7 @@ describe("Identity v1", async () => {
|
|||||||
test("Update identity", async () => {
|
test("Update identity", async () => {
|
||||||
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
const newIdentity = await createIdentity("mac1", OrgMembershipRole.Admin);
|
||||||
expect(newIdentity.name).toBe("mac1");
|
expect(newIdentity.name).toBe("mac1");
|
||||||
expect(newIdentity.authMethod).toBeNull();
|
expect(newIdentity.authMethods).toEqual([]);
|
||||||
|
|
||||||
const updatedIdentity = await testServer.inject({
|
const updatedIdentity = await testServer.inject({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
@ -39,8 +39,6 @@ describe("Login V1 Router", async () => {
|
|||||||
});
|
});
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
const payload = JSON.parse(res.payload);
|
const payload = JSON.parse(res.payload);
|
||||||
expect(payload).toHaveProperty("mfaEnabled");
|
|
||||||
expect(payload).toHaveProperty("token");
|
expect(payload).toHaveProperty("token");
|
||||||
expect(payload.mfaEnabled).toBeFalsy();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -118,9 +118,9 @@ describe.each([{ secretPath: "/" }, { secretPath: "/deep" }])(
|
|||||||
value: "stage-value"
|
value: "stage-value"
|
||||||
});
|
});
|
||||||
|
|
||||||
// wait for 5 second for replication to finish
|
// wait for 10 second for replication to finish
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 5000); // time to breathe for db
|
setTimeout(resolve, 10000); // time to breathe for db
|
||||||
});
|
});
|
||||||
|
|
||||||
const secret = await getSecretByNameV2({
|
const secret = await getSecretByNameV2({
|
||||||
@ -173,9 +173,9 @@ describe.each([{ secretPath: "/" }, { secretPath: "/deep" }])(
|
|||||||
value: "prod-value"
|
value: "prod-value"
|
||||||
});
|
});
|
||||||
|
|
||||||
// wait for 5 second for replication to finish
|
// wait for 10 second for replication to finish
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 5000); // time to breathe for db
|
setTimeout(resolve, 10000); // time to breathe for db
|
||||||
});
|
});
|
||||||
|
|
||||||
const secret = await getSecretByNameV2({
|
const secret = await getSecretByNameV2({
|
||||||
@ -343,9 +343,9 @@ describe.each([{ path: "/" }, { path: "/deep" }])(
|
|||||||
value: "prod-value"
|
value: "prod-value"
|
||||||
});
|
});
|
||||||
|
|
||||||
// wait for 5 second for replication to finish
|
// wait for 10 second for replication to finish
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 5000); // time to breathe for db
|
setTimeout(resolve, 10000); // time to breathe for db
|
||||||
});
|
});
|
||||||
|
|
||||||
const secret = await getSecretByNameV2({
|
const secret = await getSecretByNameV2({
|
||||||
|
@ -56,7 +56,10 @@ describe("Secret expansion", () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(secrets.map((el) => createSecretV2(el)));
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
const expandedSecret = await getSecretByNameV2({
|
const expandedSecret = await getSecretByNameV2({
|
||||||
environmentSlug: seedData1.environment.slug,
|
environmentSlug: seedData1.environment.slug,
|
||||||
@ -123,7 +126,10 @@ describe("Secret expansion", () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(secrets.map((el) => createSecretV2(el)));
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
const expandedSecret = await getSecretByNameV2({
|
const expandedSecret = await getSecretByNameV2({
|
||||||
environmentSlug: seedData1.environment.slug,
|
environmentSlug: seedData1.environment.slug,
|
||||||
@ -190,7 +196,11 @@ describe("Secret expansion", () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(secrets.map((el) => createSecretV2(el)));
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
const secretImportFromProdToDev = await createSecretImport({
|
const secretImportFromProdToDev = await createSecretImport({
|
||||||
environmentSlug: seedData1.environment.slug,
|
environmentSlug: seedData1.environment.slug,
|
||||||
workspaceId: projectId,
|
workspaceId: projectId,
|
||||||
@ -275,7 +285,11 @@ describe("Secret expansion", () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await Promise.all(secrets.map((el) => createSecretV2(el)));
|
for (const secret of secrets) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await createSecretV2(secret);
|
||||||
|
}
|
||||||
|
|
||||||
const secretImportFromProdToDev = await createSecretImport({
|
const secretImportFromProdToDev = await createSecretImport({
|
||||||
environmentSlug: seedData1.environment.slug,
|
environmentSlug: seedData1.environment.slug,
|
||||||
workspaceId: projectId,
|
workspaceId: projectId,
|
||||||
|
3323
backend/package-lock.json
generated
3323
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -44,7 +44,7 @@
|
|||||||
"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",
|
||||||
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
"generate:component": "tsx ./scripts/create-backend-file.ts",
|
||||||
"generate:schema": "tsx ./scripts/generate-schema-types.ts",
|
"generate:schema": "tsx ./scripts/generate-schema-types.ts && eslint --fix --ext ts ./src/db/schemas",
|
||||||
"auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest",
|
"auditlog-migration:latest": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:latest",
|
||||||
"auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up",
|
"auditlog-migration:up": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:up",
|
||||||
"auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down",
|
"auditlog-migration:down": "knex --knexfile ./src/db/auditlog-knexfile.ts --client pg migrate:down",
|
||||||
@ -156,11 +156,12 @@
|
|||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
"cron": "^3.1.7",
|
"cron": "^3.1.7",
|
||||||
"dotenv": "^16.4.1",
|
"dotenv": "^16.4.1",
|
||||||
"fastify": "^4.26.0",
|
"fastify": "^4.28.1",
|
||||||
"fastify-plugin": "^4.5.1",
|
"fastify-plugin": "^4.5.1",
|
||||||
"google-auth-library": "^9.9.0",
|
"google-auth-library": "^9.9.0",
|
||||||
"googleapis": "^137.1.0",
|
"googleapis": "^137.1.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"hdb": "^0.19.10",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
@ -195,6 +196,7 @@
|
|||||||
"scim2-parse-filter": "^0.2.10",
|
"scim2-parse-filter": "^0.2.10",
|
||||||
"sjcl": "^1.0.8",
|
"sjcl": "^1.0.8",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
|
"snowflake-sdk": "^1.14.0",
|
||||||
"tedious": "^18.2.1",
|
"tedious": "^18.2.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
"tweetnacl-util": "^0.15.1",
|
"tweetnacl-util": "^0.15.1",
|
||||||
|
@ -8,61 +8,80 @@ const prompt = promptSync({
|
|||||||
sigint: true
|
sigint: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sanitizeInputParam = (value: string) => {
|
||||||
|
// Escape double quotes and wrap the entire value in double quotes
|
||||||
|
if (value) {
|
||||||
|
return `"${value.replace(/"/g, '\\"')}"`;
|
||||||
|
}
|
||||||
|
return '""';
|
||||||
|
};
|
||||||
|
|
||||||
const exportDb = () => {
|
const exportDb = () => {
|
||||||
const exportHost = prompt("Enter your Postgres Host to migrate from: ");
|
const exportHost = sanitizeInputParam(prompt("Enter your Postgres Host to migrate from: "));
|
||||||
const exportPort = prompt("Enter your Postgres Port to migrate from [Default = 5432]: ") ?? "5432";
|
const exportPort = sanitizeInputParam(
|
||||||
const exportUser = prompt("Enter your Postgres User to migrate from: [Default = infisical]: ") ?? "infisical";
|
prompt("Enter your Postgres Port to migrate from [Default = 5432]: ") ?? "5432"
|
||||||
const exportPassword = prompt("Enter your Postgres Password to migrate from: ");
|
);
|
||||||
const exportDatabase = prompt("Enter your Postgres Database to migrate from [Default = infisical]: ") ?? "infisical";
|
const exportUser = sanitizeInputParam(
|
||||||
|
prompt("Enter your Postgres User to migrate from: [Default = infisical]: ") ?? "infisical"
|
||||||
|
);
|
||||||
|
const exportPassword = sanitizeInputParam(prompt("Enter your Postgres Password to migrate from: "));
|
||||||
|
const exportDatabase = sanitizeInputParam(
|
||||||
|
prompt("Enter your Postgres Database to migrate from [Default = infisical]: ") ?? "infisical"
|
||||||
|
);
|
||||||
|
|
||||||
// we do not include the audit_log and secret_sharing entries
|
// we do not include the audit_log and secret_sharing entries
|
||||||
execSync(
|
execSync(
|
||||||
`PGDATABASE="${exportDatabase}" PGPASSWORD="${exportPassword}" PGHOST="${exportHost}" PGPORT=${exportPort} PGUSER=${exportUser} pg_dump infisical --exclude-table-data="secret_sharing" --exclude-table-data="audit_log*" > ${path.join(
|
`PGDATABASE=${exportDatabase} PGPASSWORD=${exportPassword} PGHOST=${exportHost} PGPORT=${exportPort} PGUSER=${exportUser} pg_dump -Fc infisical --exclude-table-data="secret_sharing" --exclude-table-data="audit_log*" > ${path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
"../src/db/dump.sql"
|
"../src/db/backup.dump"
|
||||||
)}`,
|
)}`,
|
||||||
{ stdio: "inherit" }
|
{ stdio: "inherit" }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const importDbForOrg = () => {
|
const importDbForOrg = () => {
|
||||||
const importHost = prompt("Enter your Postgres Host to migrate to: ");
|
const importHost = sanitizeInputParam(prompt("Enter your Postgres Host to migrate to: "));
|
||||||
const importPort = prompt("Enter your Postgres Port to migrate to [Default = 5432]: ") ?? "5432";
|
const importPort = sanitizeInputParam(prompt("Enter your Postgres Port to migrate to [Default = 5432]: ") ?? "5432");
|
||||||
const importUser = prompt("Enter your Postgres User to migrate to: [Default = infisical]: ") ?? "infisical";
|
const importUser = sanitizeInputParam(
|
||||||
const importPassword = prompt("Enter your Postgres Password to migrate to: ");
|
prompt("Enter your Postgres User to migrate to: [Default = infisical]: ") ?? "infisical"
|
||||||
const importDatabase = prompt("Enter your Postgres Database to migrate to [Default = infisical]: ") ?? "infisical";
|
);
|
||||||
const orgId = prompt("Enter the organization ID to migrate: ");
|
const importPassword = sanitizeInputParam(prompt("Enter your Postgres Password to migrate to: "));
|
||||||
|
const importDatabase = sanitizeInputParam(
|
||||||
|
prompt("Enter your Postgres Database to migrate to [Default = infisical]: ") ?? "infisical"
|
||||||
|
);
|
||||||
|
const orgId = sanitizeInputParam(prompt("Enter the organization ID to migrate: "));
|
||||||
|
|
||||||
if (!existsSync(path.join(__dirname, "../src/db/dump.sql"))) {
|
if (!existsSync(path.join(__dirname, "../src/db/backup.dump"))) {
|
||||||
console.log("File not found, please export the database first.");
|
console.log("File not found, please export the database first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
execSync(
|
execSync(
|
||||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -f ${path.join(
|
`PGDATABASE=${importDatabase} PGPASSWORD=${importPassword} PGHOST=${importHost} PGPORT=${importPort} PGUSER=${importUser} pg_restore -d ${importDatabase} --verbose ${path.join(
|
||||||
__dirname,
|
__dirname,
|
||||||
"../src/db/dump.sql"
|
"../src/db/backup.dump"
|
||||||
)}`
|
)}`,
|
||||||
|
{ maxBuffer: 1024 * 1024 * 4096 }
|
||||||
);
|
);
|
||||||
|
|
||||||
execSync(
|
execSync(
|
||||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c "DELETE FROM public.organizations WHERE id != '${orgId}'"`
|
`PGDATABASE=${importDatabase} PGPASSWORD=${importPassword} PGHOST=${importHost} PGPORT=${importPort} PGUSER=${importUser} psql -c "DELETE FROM public.organizations WHERE id != '${orgId}'"`
|
||||||
);
|
);
|
||||||
|
|
||||||
// delete global/instance-level resources not relevant to the organization to migrate
|
// delete global/instance-level resources not relevant to the organization to migrate
|
||||||
// users
|
// users
|
||||||
execSync(
|
execSync(
|
||||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM users WHERE users.id NOT IN (SELECT org_memberships."userId" FROM org_memberships)'`
|
`PGDATABASE=${importDatabase} PGPASSWORD=${importPassword} PGHOST=${importHost} PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM users WHERE users.id NOT IN (SELECT org_memberships."userId" FROM org_memberships)'`
|
||||||
);
|
);
|
||||||
|
|
||||||
// identities
|
// identities
|
||||||
execSync(
|
execSync(
|
||||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM identities WHERE id NOT IN (SELECT "identityId" FROM identity_org_memberships)'`
|
`PGDATABASE=${importDatabase} PGPASSWORD=${importPassword} PGHOST=${importHost} PGPORT=${importPort} PGUSER=${importUser} psql -c 'DELETE FROM identities WHERE id NOT IN (SELECT "identityId" FROM identity_org_memberships)'`
|
||||||
);
|
);
|
||||||
|
|
||||||
// reset slack configuration in superAdmin
|
// reset slack configuration in superAdmin
|
||||||
execSync(
|
execSync(
|
||||||
`PGDATABASE="${importDatabase}" PGPASSWORD="${importPassword}" PGHOST="${importHost}" PGPORT=${importPort} PGUSER=${importUser} psql -c 'UPDATE super_admin SET "encryptedSlackClientId" = null, "encryptedSlackClientSecret" = null'`
|
`PGDATABASE=${importDatabase} PGPASSWORD=${importPassword} PGHOST=${importHost} PGPORT=${importPort} PGUSER=${importUser} psql -c 'UPDATE super_admin SET "encryptedSlackClientId" = null, "encryptedSlackClientSecret" = null'`
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("Organization migrated successfully.");
|
console.log("Organization migrated successfully.");
|
||||||
|
2
backend/src/@types/fastify.d.ts
vendored
2
backend/src/@types/fastify.d.ts
vendored
@ -13,6 +13,7 @@ import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secr
|
|||||||
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
||||||
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
|
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { TLdapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
|
import { TOidcConfigServiceFactory } from "@app/ee/services/oidc/oidc-config-service";
|
||||||
@ -177,6 +178,7 @@ declare module "fastify" {
|
|||||||
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
dynamicSecretLease: TDynamicSecretLeaseServiceFactory;
|
||||||
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
projectUserAdditionalPrivilege: TProjectUserAdditionalPrivilegeServiceFactory;
|
||||||
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
identityProjectAdditionalPrivilege: TIdentityProjectAdditionalPrivilegeServiceFactory;
|
||||||
|
identityProjectAdditionalPrivilegeV2: TIdentityProjectAdditionalPrivilegeV2ServiceFactory;
|
||||||
secretSharing: TSecretSharingServiceFactory;
|
secretSharing: TSecretSharingServiceFactory;
|
||||||
rateLimit: TRateLimitServiceFactory;
|
rateLimit: TRateLimitServiceFactory;
|
||||||
userEngagement: TUserEngagementServiceFactory;
|
userEngagement: TUserEngagementServiceFactory;
|
||||||
|
4
backend/src/@types/hdb.d.ts
vendored
Normal file
4
backend/src/@types/hdb.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
declare module "hdb" {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Untyped, the function returns `any`.
|
||||||
|
function createClient(options): any;
|
||||||
|
}
|
@ -9,7 +9,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.string("integration").notNullable();
|
t.string("integration").notNullable();
|
||||||
t.string("teamId"); // vercel-specific
|
t.string("teamId"); // vercel-specific
|
||||||
t.string("url"); // for self hosted
|
t.string("url"); // for self-hosted
|
||||||
t.string("namespace"); // hashicorp specific
|
t.string("namespace"); // hashicorp specific
|
||||||
t.string("accountId"); // netlify
|
t.string("accountId"); // netlify
|
||||||
t.text("refreshCiphertext");
|
t.text("refreshCiphertext");
|
||||||
@ -36,7 +36,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
await knex.schema.createTable(TableName.Integration, (t) => {
|
await knex.schema.createTable(TableName.Integration, (t) => {
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
t.boolean("isActive").notNullable();
|
t.boolean("isActive").notNullable();
|
||||||
t.string("url"); // self hosted
|
t.string("url"); // self-hosted
|
||||||
t.string("app"); // name of app in provider
|
t.string("app"); // name of app in provider
|
||||||
t.string("appId");
|
t.string("appId");
|
||||||
t.string("targetEnvironment");
|
t.string("targetEnvironment");
|
||||||
|
@ -4,27 +4,40 @@ import { TableName } from "../schemas";
|
|||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
const hasEncryptedSecret = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSecret");
|
||||||
|
const hasIdentifier = await knex.schema.hasColumn(TableName.SecretSharing, "identifier");
|
||||||
|
|
||||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
t.string("iv").nullable().alter();
|
t.string("iv").nullable().alter();
|
||||||
t.string("tag").nullable().alter();
|
t.string("tag").nullable().alter();
|
||||||
t.string("encryptedValue").nullable().alter();
|
t.string("encryptedValue").nullable().alter();
|
||||||
|
|
||||||
t.binary("encryptedSecret").nullable();
|
if (!hasEncryptedSecret) {
|
||||||
|
t.binary("encryptedSecret").nullable();
|
||||||
|
}
|
||||||
t.string("hashedHex").nullable().alter();
|
t.string("hashedHex").nullable().alter();
|
||||||
|
|
||||||
t.string("identifier", 64).nullable();
|
if (!hasIdentifier) {
|
||||||
t.unique("identifier");
|
t.string("identifier", 64).nullable();
|
||||||
t.index("identifier");
|
t.unique("identifier");
|
||||||
|
t.index("identifier");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasEncryptedSecret = await knex.schema.hasColumn(TableName.SecretSharing, "encryptedSecret");
|
||||||
|
const hasIdentifier = await knex.schema.hasColumn(TableName.SecretSharing, "identifier");
|
||||||
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
t.dropColumn("encryptedSecret");
|
if (hasEncryptedSecret) {
|
||||||
|
t.dropColumn("encryptedSecret");
|
||||||
|
}
|
||||||
|
|
||||||
t.dropColumn("identifier");
|
if (hasIdentifier) {
|
||||||
|
t.dropColumn("identifier");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,15 +7,18 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
||||||
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
const hasProjectId = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
|
||||||
|
|
||||||
// drop constraint if exists (won't exist if rolled back, see below)
|
// drop constraint if exists (won't exist if rolled back, see below)
|
||||||
await dropConstraintIfExists(TableName.KmsKey, "kms_keys_orgid_slug_unique", knex);
|
await dropConstraintIfExists(TableName.KmsKey, "kms_keys_orgid_slug_unique", knex);
|
||||||
|
|
||||||
// projectId for CMEK functionality
|
// projectId for CMEK functionality
|
||||||
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
table.string("projectId").nullable().references("id").inTable(TableName.Project).onDelete("CASCADE");
|
if (!hasProjectId) {
|
||||||
|
table.string("projectId").nullable().references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
if (hasOrgId) {
|
if (hasOrgId && hasSlug) {
|
||||||
table.unique(["orgId", "projectId", "slug"]);
|
table.unique(["orgId", "projectId", "slug"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +33,7 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
||||||
const hasName = await knex.schema.hasColumn(TableName.KmsKey, "name");
|
const hasName = await knex.schema.hasColumn(TableName.KmsKey, "name");
|
||||||
|
const hasProjectId = await knex.schema.hasColumn(TableName.KmsKey, "projectId");
|
||||||
|
|
||||||
// remove projectId for CMEK functionality
|
// remove projectId for CMEK functionality
|
||||||
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
@ -40,7 +44,9 @@ export async function down(knex: Knex): Promise<void> {
|
|||||||
if (hasOrgId) {
|
if (hasOrgId) {
|
||||||
table.dropUnique(["orgId", "projectId", "slug"]);
|
table.dropUnique(["orgId", "projectId", "slug"]);
|
||||||
}
|
}
|
||||||
table.dropColumn("projectId");
|
if (hasProjectId) {
|
||||||
|
table.dropColumn("projectId");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
import { packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import {
|
||||||
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const CHUNK_SIZE = 1000;
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasVersion = await knex.schema.hasColumn(TableName.ProjectRoles, "version");
|
||||||
|
if (!hasVersion) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectRoles, (t) => {
|
||||||
|
t.integer("version").defaultTo(1).notNullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
const docs = await knex(TableName.ProjectRoles).select("*");
|
||||||
|
const updatedDocs = docs
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions), true)))
|
||||||
|
}));
|
||||||
|
if (updatedDocs.length) {
|
||||||
|
for (let i = 0; i < updatedDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.ProjectRoles).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// secret permission is split into multiple ones like secrets, folders, imports and dynamic-secrets
|
||||||
|
// so we just find all the privileges with respective mapping and map it as needed
|
||||||
|
const identityPrivileges = await knex(TableName.IdentityProjectAdditionalPrivilege).select("*");
|
||||||
|
const updatedIdentityPrivilegesDocs = identityPrivileges
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretFolders)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions))))
|
||||||
|
}));
|
||||||
|
if (updatedIdentityPrivilegesDocs.length) {
|
||||||
|
for (let i = 0; i < updatedIdentityPrivilegesDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedIdentityPrivilegesDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.IdentityProjectAdditionalPrivilege).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPrivileges = await knex(TableName.ProjectUserAdditionalPrivilege).select("*");
|
||||||
|
const updatedUserPrivilegeDocs = userPrivileges
|
||||||
|
.filter((i) => {
|
||||||
|
const permissionString = JSON.stringify(i.permissions || []);
|
||||||
|
return (
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretImports) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.DynamicSecrets) &&
|
||||||
|
!permissionString.includes(ProjectPermissionSub.SecretFolders)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.map((el) => ({
|
||||||
|
...el,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(unpackRules(el.permissions))))
|
||||||
|
}));
|
||||||
|
if (docs.length) {
|
||||||
|
for (let i = 0; i < updatedUserPrivilegeDocs.length; i += CHUNK_SIZE) {
|
||||||
|
const chunk = updatedUserPrivilegeDocs.slice(i, i + CHUNK_SIZE);
|
||||||
|
await knex(TableName.ProjectUserAdditionalPrivilege).insert(chunk).onConflict("id").merge();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasVersion = await knex.schema.hasColumn(TableName.ProjectRoles, "version");
|
||||||
|
if (hasVersion) {
|
||||||
|
await knex.schema.alterTable(TableName.ProjectRoles, (t) => {
|
||||||
|
t.dropColumn("version");
|
||||||
|
});
|
||||||
|
|
||||||
|
// permission change can be ignored
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
const BATCH_SIZE = 30_000;
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasAuthMethodColumnAccessToken = await knex.schema.hasColumn(TableName.IdentityAccessToken, "authMethod");
|
||||||
|
|
||||||
|
if (!hasAuthMethodColumnAccessToken) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.string("authMethod").nullable();
|
||||||
|
});
|
||||||
|
|
||||||
|
let nullableAccessTokens = await knex(TableName.IdentityAccessToken).whereNull("authMethod").limit(BATCH_SIZE);
|
||||||
|
let totalUpdated = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const batchIds = nullableAccessTokens.map((token) => token.id);
|
||||||
|
|
||||||
|
// ! Update the auth method column in batches for the current batch
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await knex(TableName.IdentityAccessToken)
|
||||||
|
.whereIn("id", batchIds)
|
||||||
|
.update({
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore because generate schema happens after this
|
||||||
|
authMethod: knex(TableName.Identity)
|
||||||
|
.select("authMethod")
|
||||||
|
.whereRaw(`${TableName.IdentityAccessToken}."identityId" = ${TableName.Identity}.id`)
|
||||||
|
.whereNotNull("authMethod")
|
||||||
|
.first()
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
nullableAccessTokens = await knex(TableName.IdentityAccessToken).whereNull("authMethod").limit(BATCH_SIZE);
|
||||||
|
|
||||||
|
totalUpdated += batchIds.length;
|
||||||
|
console.log(`Updated ${batchIds.length} access tokens in batch <> Total updated: ${totalUpdated}`);
|
||||||
|
} while (nullableAccessTokens.length > 0);
|
||||||
|
|
||||||
|
// ! We delete all access tokens where the identity has no auth method set!
|
||||||
|
// ! Which means un-configured identities that for some reason have access tokens, will have their access tokens deleted.
|
||||||
|
await knex(TableName.IdentityAccessToken)
|
||||||
|
.whereNotExists((queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.select("id")
|
||||||
|
.from(TableName.Identity)
|
||||||
|
.whereRaw(`${TableName.IdentityAccessToken}."identityId" = ${TableName.Identity}.id`)
|
||||||
|
.whereNotNull("authMethod");
|
||||||
|
})
|
||||||
|
.delete();
|
||||||
|
|
||||||
|
// Finally we set the authMethod to notNullable after populating the column.
|
||||||
|
// This will fail if the data is not populated correctly, so it's safe.
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.string("authMethod").notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ! We aren't dropping the authMethod column from the Identity itself, because we wan't to be able to easily rollback for the time being.
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasAuthMethodColumnAccessToken = await knex.schema.hasColumn(TableName.IdentityAccessToken, "authMethod");
|
||||||
|
|
||||||
|
if (hasAuthMethodColumnAccessToken) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityAccessToken, (t) => {
|
||||||
|
t.dropColumn("authMethod");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = { transaction: false };
|
||||||
|
export { config };
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.Organization, "enforceMfa"))) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (tb) => {
|
||||||
|
tb.boolean("enforceMfa").defaultTo(false).notNullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Organization, "enforceMfa")) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
t.dropColumn("enforceMfa");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.OidcConfig, "orgId")) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
|
||||||
|
t.dropForeign("orgId");
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.OidcConfig, "orgId")) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (t) => {
|
||||||
|
t.dropForeign("orgId");
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,8 @@ export const IdentityAccessTokensSchema = z.object({
|
|||||||
identityId: z.string().uuid(),
|
identityId: z.string().uuid(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
name: z.string().nullable().optional()
|
name: z.string().nullable().optional(),
|
||||||
|
authMethod: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
|
export type TIdentityAccessTokens = z.infer<typeof IdentityAccessTokensSchema>;
|
||||||
|
@ -189,7 +189,7 @@ export enum ProjectUpgradeStatus {
|
|||||||
|
|
||||||
export enum IdentityAuthMethod {
|
export enum IdentityAuthMethod {
|
||||||
TOKEN_AUTH = "token-auth",
|
TOKEN_AUTH = "token-auth",
|
||||||
Univeral = "universal-auth",
|
UNIVERSAL_AUTH = "universal-auth",
|
||||||
KUBERNETES_AUTH = "kubernetes-auth",
|
KUBERNETES_AUTH = "kubernetes-auth",
|
||||||
GCP_AUTH = "gcp-auth",
|
GCP_AUTH = "gcp-auth",
|
||||||
AWS_AUTH = "aws-auth",
|
AWS_AUTH = "aws-auth",
|
||||||
|
@ -20,7 +20,8 @@ export const OrganizationsSchema = z.object({
|
|||||||
scimEnabled: z.boolean().default(false).nullable().optional(),
|
scimEnabled: z.boolean().default(false).nullable().optional(),
|
||||||
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
|
kmsDefaultKeyId: z.string().uuid().nullable().optional(),
|
||||||
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
defaultMembershipRole: z.string().default("member")
|
defaultMembershipRole: z.string().default("member"),
|
||||||
|
enforceMfa: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
@ -16,7 +16,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
id: seedData1.machineIdentity.id,
|
id: seedData1.machineIdentity.id,
|
||||||
name: seedData1.machineIdentity.name,
|
name: seedData1.machineIdentity.name,
|
||||||
authMethod: IdentityAuthMethod.Univeral
|
authMethod: IdentityAuthMethod.UNIVERSAL_AUTH
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const identityUa = await knex(TableName.IdentityUniversalAuth)
|
const identityUa = await knex(TableName.IdentityUniversalAuth)
|
||||||
|
@ -165,7 +165,8 @@ export const registerGroupRouter = async (server: FastifyZodProvider) => {
|
|||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
|
offset: z.coerce.number().min(0).max(100).default(0).describe(GROUPS.LIST_USERS.offset),
|
||||||
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
|
limit: z.coerce.number().min(1).max(100).default(10).describe(GROUPS.LIST_USERS.limit),
|
||||||
username: z.string().optional().describe(GROUPS.LIST_USERS.username)
|
username: z.string().trim().optional().describe(GROUPS.LIST_USERS.username),
|
||||||
|
search: z.string().trim().optional().describe(GROUPS.LIST_USERS.search)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
||||||
|
import { backfillPermissionV1SchemaToV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@ -79,7 +79,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
permissions: JSON.stringify(packRules(permission))
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@ -159,7 +161,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
...req.body,
|
...req.body,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
permissions: JSON.stringify(packRules(permission))
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@ -244,7 +248,13 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
projectSlug: req.body.projectSlug,
|
projectSlug: req.body.projectSlug,
|
||||||
data: {
|
data: {
|
||||||
...updatedInfo,
|
...updatedInfo,
|
||||||
permissions: permission ? JSON.stringify(packRules(permission)) : undefined
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
permissions: permission
|
||||||
|
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
backfillPermissionV1SchemaToV2Schema(permission)
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
|
@ -81,9 +81,9 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
await server.register(registerSecretVersionRouter, { prefix: "/secret" });
|
||||||
await server.register(registerGroupRouter, { prefix: "/groups" });
|
await server.register(registerGroupRouter, { prefix: "/groups" });
|
||||||
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
|
await server.register(registerAuditLogStreamRouter, { prefix: "/audit-log-streams" });
|
||||||
|
await server.register(registerUserAdditionalPrivilegeRouter, { prefix: "/user-project-additional-privilege" });
|
||||||
await server.register(
|
await server.register(
|
||||||
async (privilegeRouter) => {
|
async (privilegeRouter) => {
|
||||||
await privilegeRouter.register(registerUserAdditionalPrivilegeRouter, { prefix: "/users" });
|
|
||||||
await privilegeRouter.register(registerIdentityProjectAdditionalPrivilegeRouter, { prefix: "/identity" });
|
await privilegeRouter.register(registerIdentityProjectAdditionalPrivilegeRouter, { prefix: "/identity" });
|
||||||
},
|
},
|
||||||
{ prefix: "/additional-privilege" }
|
{ prefix: "/additional-privilege" }
|
||||||
|
@ -3,12 +3,16 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
import { ProjectPermissionSchema } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
|
ProjectPermissionV1Schema
|
||||||
|
} 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";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedRoleSchemaV1 } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types";
|
||||||
|
|
||||||
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@ -43,11 +47,11 @@ 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().optional().describe(PROJECT_ROLE.CREATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
||||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -58,12 +62,16 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.SLUG,
|
||||||
|
projectSlug: req.params.projectSlug
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: JSON.stringify(packRules(req.body.permissions))
|
permissions: JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { role };
|
return { role };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -103,11 +111,11 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionV1Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -118,11 +126,12 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
|
||||||
roleId: req.params.roleId,
|
roleId: req.params.roleId,
|
||||||
data: {
|
data: {
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined
|
permissions: req.body.permissions
|
||||||
|
? JSON.stringify(packRules(backfillPermissionV1SchemaToV2Schema(req.body.permissions, true)))
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return { role };
|
return { role };
|
||||||
@ -148,7 +157,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -159,7 +168,6 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
|
||||||
roleId: req.params.roleId
|
roleId: req.params.roleId
|
||||||
});
|
});
|
||||||
return { role };
|
return { role };
|
||||||
@ -195,7 +203,10 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.SLUG,
|
||||||
|
projectSlug: req.params.projectSlug
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { roles };
|
return { roles };
|
||||||
}
|
}
|
||||||
@ -214,7 +225,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
role: SanitizedRoleSchema
|
role: SanitizedRoleSchemaV1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -225,9 +236,13 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
projectSlug: req.params.projectSlug,
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.SLUG,
|
||||||
|
projectSlug: req.params.projectSlug
|
||||||
|
},
|
||||||
roleSlug: req.params.slug
|
roleSlug: req.params.slug
|
||||||
});
|
});
|
||||||
|
|
||||||
return { role };
|
return { role };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2,17 +2,18 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
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";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
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 { SanitizedUserProjectAdditionalPrivilegeSchema } from "@app/server/routes/santizedSchemas/user-additional-privilege";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
url: "/permanent",
|
url: "/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: writeLimit
|
rateLimit: writeLimit
|
||||||
@ -31,66 +32,30 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
||||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions)
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
||||||
}),
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
response: {
|
z.object({
|
||||||
200: z.object({
|
isTemporary: z.literal(false)
|
||||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
}),
|
||||||
})
|
z.object({
|
||||||
}
|
isTemporary: z.literal(true),
|
||||||
},
|
temporaryMode: z
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
handler: async (req) => {
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
||||||
const privilege = await server.services.projectUserAdditionalPrivilege.create({
|
temporaryRange: z
|
||||||
actorId: req.permission.id,
|
.string()
|
||||||
actor: req.permission.type,
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
actorOrgId: req.permission.orgId,
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
||||||
actorAuthMethod: req.permission.authMethod,
|
temporaryAccessStartTime: z
|
||||||
...req.body,
|
.string()
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : slugify(alphaNumericNanoId(12)),
|
.datetime()
|
||||||
isTemporary: false,
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
||||||
permissions: JSON.stringify(req.body.permissions)
|
|
||||||
});
|
|
||||||
return { privilege };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/temporary",
|
|
||||||
config: {
|
|
||||||
rateLimit: writeLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
projectMembershipId: z.string().min(1).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.projectMembershipId),
|
|
||||||
slug: z
|
|
||||||
.string()
|
|
||||||
.min(1)
|
|
||||||
.max(60)
|
|
||||||
.trim()
|
|
||||||
.refine((v) => v.toLowerCase() === v, "Slug must be lowercase")
|
|
||||||
.refine((v) => slugify(v) === v, {
|
|
||||||
message: "Slug must be a valid slug"
|
|
||||||
})
|
})
|
||||||
.optional()
|
])
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.slug),
|
|
||||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.permissions),
|
|
||||||
temporaryMode: z
|
|
||||||
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryMode),
|
|
||||||
temporaryRange: z
|
|
||||||
.string()
|
|
||||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryRange),
|
|
||||||
temporaryAccessStartTime: z
|
|
||||||
.string()
|
|
||||||
.datetime()
|
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.CREATE.temporaryAccessStartTime)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
privilege: SanitizedUserProjectAdditionalPrivilegeSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -101,10 +66,10 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.body,
|
projectMembershipId: req.body.projectMembershipId,
|
||||||
slug: req.body.slug ? slugify(req.body.slug) : `privilege-${slugify(alphaNumericNanoId(12))}`,
|
...req.body.type,
|
||||||
isTemporary: true,
|
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
|
||||||
permissions: JSON.stringify(req.body.permissions)
|
permissions: req.body.permissions
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@ -131,24 +96,31 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
message: "Slug must be a valid slug"
|
message: "Slug must be a valid slug"
|
||||||
})
|
})
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.slug),
|
||||||
permissions: z.any().array().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
isTemporary: z.boolean().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
.optional()
|
||||||
temporaryMode: z
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.permissions),
|
||||||
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
z.object({ isTemporary: z.literal(false).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary) }),
|
||||||
temporaryRange: z
|
z.object({
|
||||||
.string()
|
isTemporary: z.literal(true).describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.isTemporary),
|
||||||
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
temporaryMode: z
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
.nativeEnum(ProjectUserAdditionalPrivilegeTemporaryMode)
|
||||||
temporaryAccessStartTime: z
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryMode),
|
||||||
.string()
|
temporaryRange: z
|
||||||
.datetime()
|
.string()
|
||||||
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
.refine((val) => typeof val === "undefined" || ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.UPDATE.temporaryAccessStartTime)
|
||||||
|
})
|
||||||
|
])
|
||||||
})
|
})
|
||||||
.partial(),
|
.partial(),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
privilege: SanitizedUserProjectAdditionalPrivilegeSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -160,7 +132,12 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined,
|
...req.body.type,
|
||||||
|
permissions: req.body.permissions
|
||||||
|
? // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
req.body.permissions
|
||||||
|
: undefined,
|
||||||
privilegeId: req.params.privilegeId
|
privilegeId: req.params.privilegeId
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
@ -179,7 +156,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
privilege: SanitizedUserProjectAdditionalPrivilegeSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -208,7 +185,7 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
privileges: ProjectUserAdditionalPrivilegeSchema.array()
|
privileges: SanitizedUserProjectAdditionalPrivilegeSchema.omit({ permissions: true }).array()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -233,11 +210,11 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.GET_BY_PRIVILEGEID.privilegeId)
|
privilegeId: z.string().describe(PROJECT_USER_ADDITIONAL_PRIVILEGE.GET_BY_PRIVILEGE_ID.privilegeId)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
privilege: ProjectUserAdditionalPrivilegeSchema
|
privilege: SanitizedUserProjectAdditionalPrivilegeSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,305 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import ms from "ms";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-types";
|
||||||
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { IDENTITY_ADDITIONAL_PRIVILEGE_V2 } from "@app/lib/api-docs";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { SanitizedIdentityPrivilegeSchema } from "@app/server/routes/santizedSchemas/identitiy-additional-privilege";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Add an additional privilege for identity.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.identityId),
|
||||||
|
projectId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.projectId),
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.slug),
|
||||||
|
permissions: ProjectPermissionV2Schema.array().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.permission),
|
||||||
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
|
z.object({
|
||||||
|
isTemporary: z.literal(false)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
isTemporary: z.literal(true),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.CREATE.temporaryAccessStartTime)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: SanitizedIdentityPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.create({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
projectId: req.body.projectId,
|
||||||
|
identityId: req.body.identityId,
|
||||||
|
...req.body.type,
|
||||||
|
slug: req.body.slug || slugify(alphaNumericNanoId(8)),
|
||||||
|
permissions: req.body.permissions
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Update a specific identity privilege.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.id)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.slug),
|
||||||
|
permissions: ProjectPermissionV2Schema.array()
|
||||||
|
.optional()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.privilegePermission),
|
||||||
|
type: z.discriminatedUnion("isTemporary", [
|
||||||
|
z.object({ isTemporary: z.literal(false).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary) }),
|
||||||
|
z.object({
|
||||||
|
isTemporary: z.literal(true).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.isTemporary),
|
||||||
|
temporaryMode: z
|
||||||
|
.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode)
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.temporaryMode),
|
||||||
|
temporaryRange: z
|
||||||
|
.string()
|
||||||
|
.refine((val) => typeof val === "undefined" || ms(val) > 0, "Temporary range must be a positive number")
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.temporaryRange),
|
||||||
|
temporaryAccessStartTime: z
|
||||||
|
.string()
|
||||||
|
.datetime()
|
||||||
|
.describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.UPDATE.temporaryAccessStartTime)
|
||||||
|
})
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: SanitizedIdentityPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.updateById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
id: req.params.id,
|
||||||
|
data: {
|
||||||
|
...req.body,
|
||||||
|
...req.body.type,
|
||||||
|
permissions: req.body.permissions || undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Delete the specified identity privilege.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().trim().describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.DELETE.id)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: SanitizedIdentityPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.deleteById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Retrieve details of a specific privilege by id.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.GET_BY_ID.id)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: SanitizedIdentityPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsById({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/slug/:privilegeSlug",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Retrieve details of a specific privilege by slug.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
privilegeSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.GET_BY_SLUG.slug)
|
||||||
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.GET_BY_SLUG.identityId),
|
||||||
|
projectSlug: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.GET_BY_SLUG.projectSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privilege: SanitizedIdentityPrivilegeSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privilege = await server.services.identityProjectAdditionalPrivilegeV2.getPrivilegeDetailsBySlug({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
slug: req.params.privilegeSlug,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return { privilege };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "List privileges for the specified identity by project.",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
identityId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.LIST.identityId),
|
||||||
|
projectId: z.string().min(1).describe(IDENTITY_ADDITIONAL_PRIVILEGE_V2.LIST.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
privileges: SanitizedIdentityPrivilegeSchema.omit({ permissions: true }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const privileges = await server.services.identityProjectAdditionalPrivilegeV2.listIdentityProjectPrivileges({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
privileges
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
16
backend/src/ee/routes/v2/index.ts
Normal file
16
backend/src/ee/routes/v2/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||||
|
import { registerProjectRoleRouter } from "./project-role-router";
|
||||||
|
|
||||||
|
export const registerV2EERoutes = async (server: FastifyZodProvider) => {
|
||||||
|
// org role starts with organization
|
||||||
|
await server.register(
|
||||||
|
async (projectRouter) => {
|
||||||
|
await projectRouter.register(registerProjectRoleRouter);
|
||||||
|
},
|
||||||
|
{ prefix: "/workspace" }
|
||||||
|
);
|
||||||
|
|
||||||
|
await server.register(registerIdentityProjectAdditionalPrivilegeRouter, {
|
||||||
|
prefix: "/identity-project-additional-privilege"
|
||||||
|
});
|
||||||
|
};
|
242
backend/src/ee/routes/v2/project-role-router.ts
Normal file
242
backend/src/ee/routes/v2/project-role-router.ts
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
import { packRules } from "@casl/ability/extra";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
|
import { ProjectPermissionV2Schema } from "@app/ee/services/permission/project-permission";
|
||||||
|
import { PROJECT_ROLE } from "@app/lib/api-docs";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { ProjectRoleServiceIdentifierType } from "@app/services/project-role/project-role-types";
|
||||||
|
|
||||||
|
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:projectId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Create a project role",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim().describe(PROJECT_ROLE.CREATE.projectId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.refine(
|
||||||
|
(val) => !Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
||||||
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
|
)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid"
|
||||||
|
})
|
||||||
|
.describe(PROJECT_ROLE.CREATE.slug),
|
||||||
|
name: z.string().min(1).trim().describe(PROJECT_ROLE.CREATE.name),
|
||||||
|
description: z.string().trim().optional().describe(PROJECT_ROLE.CREATE.description),
|
||||||
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.CREATE.permissions)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
role: SanitizedRoleSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const role = await server.services.projectRole.createRole({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.ID,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...req.body,
|
||||||
|
permissions: JSON.stringify(packRules(req.body.permissions))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { role };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:projectId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Update a project role",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim().describe(PROJECT_ROLE.UPDATE.projectId),
|
||||||
|
roleId: z.string().trim().describe(PROJECT_ROLE.UPDATE.roleId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.describe(PROJECT_ROLE.UPDATE.slug)
|
||||||
|
.refine(
|
||||||
|
(val) =>
|
||||||
|
typeof val === "undefined" ||
|
||||||
|
!Object.values(ProjectMembershipRole).includes(val as ProjectMembershipRole),
|
||||||
|
"Please choose a different slug, the slug you have entered is reserved"
|
||||||
|
)
|
||||||
|
.refine((val) => typeof val === "undefined" || slugify(val) === val, {
|
||||||
|
message: "Slug must be a valid"
|
||||||
|
}),
|
||||||
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
|
permissions: ProjectPermissionV2Schema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
role: SanitizedRoleSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const role = await server.services.projectRole.updateRole({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
roleId: req.params.roleId,
|
||||||
|
data: {
|
||||||
|
...req.body,
|
||||||
|
permissions: req.body.permissions ? JSON.stringify(packRules(req.body.permissions)) : undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { role };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:projectId/roles/:roleId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Delete a project role",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim().describe(PROJECT_ROLE.DELETE.projectId),
|
||||||
|
roleId: z.string().trim().describe(PROJECT_ROLE.DELETE.roleId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
role: SanitizedRoleSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const role = await server.services.projectRole.deleteRole({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
roleId: req.params.roleId
|
||||||
|
});
|
||||||
|
return { role };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:projectId/roles",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "List project role",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim().describe(PROJECT_ROLE.LIST.projectId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
roles: ProjectRolesSchema.omit({ permissions: true }).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const roles = await server.services.projectRole.listRoles({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.ID,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { roles };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:projectId/roles/slug/:roleSlug",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.projectId),
|
||||||
|
roleSlug: z.string().trim().describe(PROJECT_ROLE.GET_ROLE_BY_SLUG.roleSlug)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
role: SanitizedRoleSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const role = await server.services.projectRole.getRoleBySlug({
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
filter: {
|
||||||
|
type: ProjectRoleServiceIdentifierType.ID,
|
||||||
|
projectId: req.params.projectId
|
||||||
|
},
|
||||||
|
roleSlug: req.params.roleSlug
|
||||||
|
});
|
||||||
|
return { role };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@ -14,7 +14,7 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const accessApprovalPolicyFindQuery = async (
|
const accessApprovalPolicyFindQuery = async (
|
||||||
tx: Knex,
|
tx: Knex,
|
||||||
filter: TFindFilter<TAccessApprovalPolicies>,
|
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
policyId?: string;
|
policyId?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
|
||||||
|
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
|
||||||
|
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
|
||||||
import { TIsApproversValid } from "./access-approval-policy-types";
|
|
||||||
|
|
||||||
export const isApproversValid = async ({
|
|
||||||
userIds,
|
|
||||||
projectId,
|
|
||||||
orgId,
|
|
||||||
envSlug,
|
|
||||||
actorAuthMethod,
|
|
||||||
secretPath,
|
|
||||||
permissionService
|
|
||||||
}: TIsApproversValid) => {
|
|
||||||
try {
|
|
||||||
for await (const userId of userIds) {
|
|
||||||
const { permission: approverPermission } = await permissionService.getProjectPermission(
|
|
||||||
ActorType.USER,
|
|
||||||
userId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
orgId
|
|
||||||
);
|
|
||||||
|
|
||||||
ForbiddenError.from(approverPermission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Create,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: envSlug, secretPath })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
@ -11,7 +11,6 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
import { TGroupDALFactory } from "../group/group-dal";
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
|
import { TAccessApprovalPolicyApproverDALFactory } from "./access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "./access-approval-policy-dal";
|
||||||
import { isApproversValid } from "./access-approval-policy-fns";
|
|
||||||
import {
|
import {
|
||||||
ApproverType,
|
ApproverType,
|
||||||
TCreateAccessApprovalPolicy,
|
TCreateAccessApprovalPolicy,
|
||||||
@ -58,7 +57,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
enforcementLevel
|
enforcementLevel
|
||||||
}: TCreateAccessApprovalPolicy) => {
|
}: TCreateAccessApprovalPolicy) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
// If there is a group approver people might be added to the group later to meet the approvers quota
|
// If there is a group approver people might be added to the group later to meet the approvers quota
|
||||||
const groupApprovers = approvers
|
const groupApprovers = approvers
|
||||||
@ -89,7 +88,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
ProjectPermissionSub.SecretApproval
|
ProjectPermissionSub.SecretApproval
|
||||||
);
|
);
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
|
||||||
if (!env) throw new NotFoundError({ message: "Environment not found" });
|
if (!env) throw new NotFoundError({ message: `Environment with slug '${environment}' not found` });
|
||||||
|
|
||||||
let approverUserIds = userApprovers;
|
let approverUserIds = userApprovers;
|
||||||
if (userApproverNames.length) {
|
if (userApproverNames.length) {
|
||||||
@ -124,7 +123,9 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
const verifyAllApprovers = [...approverUserIds];
|
const verifyAllApprovers = [...approverUserIds];
|
||||||
|
|
||||||
for (const groupId of groupApprovers) {
|
for (const groupId of groupApprovers) {
|
||||||
usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }));
|
usersPromises.push(
|
||||||
|
groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }).then((group) => group.members)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const verifyGroupApprovers = (await Promise.all(usersPromises))
|
const verifyGroupApprovers = (await Promise.all(usersPromises))
|
||||||
.flat()
|
.flat()
|
||||||
@ -132,22 +133,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
.map((user) => user.id);
|
.map((user) => user.id);
|
||||||
verifyAllApprovers.push(...verifyGroupApprovers);
|
verifyAllApprovers.push(...verifyGroupApprovers);
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: project.id,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: environment,
|
|
||||||
secretPath,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: verifyAllApprovers
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "One or more approvers doesn't have access to be specified secret path"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await accessApprovalPolicyDAL.create(
|
const doc = await accessApprovalPolicyDAL.create(
|
||||||
{
|
{
|
||||||
@ -192,7 +177,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
projectSlug
|
projectSlug
|
||||||
}: TListAccessApprovalPoliciesDTO) => {
|
}: TListAccessApprovalPoliciesDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
// Anyone in the project should be able to get the policies.
|
// Anyone in the project should be able to get the policies.
|
||||||
/* const { permission } = */ await permissionService.getProjectPermission(
|
/* const { permission } = */ await permissionService.getProjectPermission(
|
||||||
@ -243,7 +228,9 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessApprovalPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
if (!accessApprovalPolicy) {
|
||||||
|
throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||||
|
}
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@ -289,22 +276,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: accessApprovalPolicy.projectId,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: accessApprovalPolicy.environment.slug,
|
|
||||||
secretPath: doc.secretPath!,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: userApproverIds
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "One or more approvers doesn't have access to be specified secret path"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
userApproverIds.map((userId) => ({
|
userApproverIds.map((userId) => ({
|
||||||
approverUserId: userId,
|
approverUserId: userId,
|
||||||
@ -315,41 +286,6 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (groupApprovers) {
|
if (groupApprovers) {
|
||||||
const usersPromises: Promise<
|
|
||||||
{
|
|
||||||
id: string;
|
|
||||||
email: string | null | undefined;
|
|
||||||
username: string;
|
|
||||||
firstName: string | null | undefined;
|
|
||||||
lastName: string | null | undefined;
|
|
||||||
isPartOfGroup: boolean;
|
|
||||||
}[]
|
|
||||||
>[] = [];
|
|
||||||
|
|
||||||
for (const groupId of groupApprovers) {
|
|
||||||
usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }));
|
|
||||||
}
|
|
||||||
const verifyGroupApprovers = (await Promise.all(usersPromises))
|
|
||||||
.flat()
|
|
||||||
.filter((user) => user.isPartOfGroup)
|
|
||||||
.map((user) => user.id);
|
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: accessApprovalPolicy.projectId,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: accessApprovalPolicy.environment.slug,
|
|
||||||
secretPath: doc.secretPath!,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: verifyGroupApprovers
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "One or more approvers doesn't have access to be specified secret path"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
groupApprovers.map((groupId) => ({
|
groupApprovers.map((groupId) => ({
|
||||||
approverGroupId: groupId,
|
approverGroupId: groupId,
|
||||||
@ -376,7 +312,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TDeleteAccessApprovalPolicy) => {
|
}: TDeleteAccessApprovalPolicy) => {
|
||||||
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
||||||
if (!policy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
if (!policy) throw new NotFoundError({ message: `Secret approval policy with ID '${policyId}' not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -404,7 +340,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}: TGetAccessPolicyCountByEnvironmentDTO) => {
|
}: TGetAccessPolicyCountByEnvironmentDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -418,10 +354,10 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
||||||
if (!environment) throw new NotFoundError({ message: "Environment not found" });
|
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
||||||
|
|
||||||
const policies = await accessApprovalPolicyDAL.find({ envId: environment.id, projectId: project.id });
|
const policies = await accessApprovalPolicyDAL.find({ envId: environment.id, projectId: project.id });
|
||||||
if (!policies) throw new NotFoundError({ message: "No policies found" });
|
if (!policies) throw new NotFoundError({ message: `No policies found in environment with slug '${envSlug}'` });
|
||||||
|
|
||||||
return { count: policies.length };
|
return { count: policies.length };
|
||||||
};
|
};
|
||||||
@ -437,7 +373,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
if (!policy) {
|
if (!policy) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Cannot find access approval policy"
|
message: `Cannot find access approval policy with ID ${policyId}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ import { TUserDALFactory } from "@app/services/user/user-dal";
|
|||||||
|
|
||||||
import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal";
|
import { TAccessApprovalPolicyApproverDALFactory } from "../access-approval-policy/access-approval-policy-approver-dal";
|
||||||
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
|
import { TAccessApprovalPolicyDALFactory } from "../access-approval-policy/access-approval-policy-dal";
|
||||||
import { isApproversValid } from "../access-approval-policy/access-approval-policy-fns";
|
|
||||||
import { TGroupDALFactory } from "../group/group-dal";
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "../project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
@ -78,7 +77,6 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
accessApprovalRequestDAL,
|
accessApprovalRequestDAL,
|
||||||
accessApprovalRequestReviewerDAL,
|
accessApprovalRequestReviewerDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
accessApprovalPolicyDAL,
|
accessApprovalPolicyDAL,
|
||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
additionalPrivilegeDAL,
|
additionalPrivilegeDAL,
|
||||||
@ -99,7 +97,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
}: TCreateAccessApprovalRequestDTO) => {
|
}: TCreateAccessApprovalRequestDTO) => {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
// Anyone can create an access approval request.
|
// Anyone can create an access approval request.
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission(
|
||||||
@ -121,13 +119,17 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const { envSlug, secretPath, accessTypes } = verifyRequestedPermissions({ permissions: requestedPermissions });
|
const { envSlug, secretPath, accessTypes } = verifyRequestedPermissions({ permissions: requestedPermissions });
|
||||||
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
||||||
|
|
||||||
if (!environment) throw new NotFoundError({ message: "Environment not found" });
|
if (!environment) throw new NotFoundError({ message: `Environment with slug '${envSlug}' not found` });
|
||||||
|
|
||||||
const policy = await accessApprovalPolicyDAL.findOne({
|
const policy = await accessApprovalPolicyDAL.findOne({
|
||||||
envId: environment.id,
|
envId: environment.id,
|
||||||
secretPath
|
secretPath
|
||||||
});
|
});
|
||||||
if (!policy) throw new NotFoundError({ message: "No policy matching criteria was found." });
|
if (!policy) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `No policy in environment with slug '${environment.slug}' and with secret path '${secretPath}' was found.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const approverIds: string[] = [];
|
const approverIds: string[] = [];
|
||||||
const approverGroupIds: string[] = [];
|
const approverGroupIds: string[] = [];
|
||||||
@ -147,10 +149,12 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const groupUsers = (
|
const groupUsers = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
approverGroupIds.map((groupApproverId) =>
|
approverGroupIds.map((groupApproverId) =>
|
||||||
groupDAL.findAllGroupPossibleMembers({
|
groupDAL
|
||||||
orgId: actorOrgId,
|
.findAllGroupPossibleMembers({
|
||||||
groupId: groupApproverId
|
orgId: actorOrgId,
|
||||||
})
|
groupId: groupApproverId
|
||||||
|
})
|
||||||
|
.then((group) => group.members)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).flat();
|
).flat();
|
||||||
@ -264,7 +268,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TListApprovalRequestsDTO) => {
|
}: TListApprovalRequestsDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -300,7 +304,9 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TReviewAccessRequestDTO) => {
|
}: TReviewAccessRequestDTO) => {
|
||||||
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
|
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
|
||||||
if (!accessApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
if (!accessApprovalRequest) {
|
||||||
|
throw new NotFoundError({ message: `Secret approval request with ID '${requestId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
const { policy } = accessApprovalRequest;
|
const { policy } = accessApprovalRequest;
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||||
@ -323,22 +329,6 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
|
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewerProjectMembership = await projectMembershipDAL.findById(membership.id);
|
|
||||||
|
|
||||||
const approversValid = await isApproversValid({
|
|
||||||
projectId: accessApprovalRequest.projectId,
|
|
||||||
orgId: actorOrgId,
|
|
||||||
envSlug: accessApprovalRequest.environment,
|
|
||||||
secretPath: accessApprovalRequest.policy.secretPath!,
|
|
||||||
actorAuthMethod,
|
|
||||||
permissionService,
|
|
||||||
userIds: [reviewerProjectMembership.userId]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!approversValid) {
|
|
||||||
throw new ForbiddenRequestError({ message: "You don't have access to approve this request" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingReviews = await accessApprovalRequestReviewerDAL.find({ requestId: accessApprovalRequest.id });
|
const existingReviews = await accessApprovalRequestReviewerDAL.find({ requestId: accessApprovalRequest.id });
|
||||||
if (existingReviews.some((review) => review.status === ApprovalStatus.REJECTED)) {
|
if (existingReviews.some((review) => review.status === ApprovalStatus.REJECTED)) {
|
||||||
throw new BadRequestError({ message: "The request has already been rejected by another reviewer" });
|
throw new BadRequestError({ message: "The request has already been rejected by another reviewer" });
|
||||||
@ -421,7 +411,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const getCount = async ({ projectSlug, actor, actorAuthMethod, actorId, actorOrgId }: TGetAccessRequestCountDTO) => {
|
const getCount = async ({ projectSlug, actor, actorAuthMethod, actorId, actorOrgId }: TGetAccessRequestCountDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
|
@ -130,7 +130,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const logStream = await auditLogStreamDAL.findById(id);
|
const logStream = await auditLogStreamDAL.findById(id);
|
||||||
if (!logStream) throw new NotFoundError({ message: "Audit log stream not found" });
|
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
|
||||||
|
|
||||||
const { orgId } = logStream;
|
const { orgId } = logStream;
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
@ -182,7 +182,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
|
||||||
|
|
||||||
const logStream = await auditLogStreamDAL.findById(id);
|
const logStream = await auditLogStreamDAL.findById(id);
|
||||||
if (!logStream) throw new NotFoundError({ message: "Audit log stream not found" });
|
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
|
||||||
|
|
||||||
const { orgId } = logStream;
|
const { orgId } = logStream;
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
@ -194,7 +194,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
|
|
||||||
const getById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetDetailsAuditLogStreamDTO) => {
|
const getById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TGetDetailsAuditLogStreamDTO) => {
|
||||||
const logStream = await auditLogStreamDAL.findById(id);
|
const logStream = await auditLogStreamDAL.findById(id);
|
||||||
if (!logStream) throw new NotFoundError({ message: "Audit log stream not found" });
|
if (!logStream) throw new NotFoundError({ message: `Audit log stream with ID '${id}' not found` });
|
||||||
|
|
||||||
const { orgId } = logStream;
|
const { orgId } = logStream;
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
@ -34,7 +34,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
*/
|
*/
|
||||||
const getCrlById = async (crlId: TGetCrlById) => {
|
const getCrlById = async (crlId: TGetCrlById) => {
|
||||||
const caCrl = await certificateAuthorityCrlDAL.findById(crlId);
|
const caCrl = await certificateAuthorityCrlDAL.findById(crlId);
|
||||||
if (!caCrl) throw new NotFoundError({ message: "CRL not found" });
|
if (!caCrl) throw new NotFoundError({ message: `CRL with ID '${crlId}' not found` });
|
||||||
|
|
||||||
const ca = await certificateAuthorityDAL.findById(caCrl.caId);
|
const ca = await certificateAuthorityDAL.findById(caCrl.caId);
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
*/
|
*/
|
||||||
const getCaCrls = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => {
|
const getCaCrls = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCrlsDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) throw new NotFoundError({ message: "CA not found" });
|
if (!ca) throw new NotFoundError({ message: `CA with ID '${caId}' not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
|
@ -211,7 +211,7 @@ export const certificateEstServiceFactory = ({
|
|||||||
const certTemplate = await certificateTemplateDAL.findById(certificateTemplateId);
|
const certTemplate = await certificateTemplateDAL.findById(certificateTemplateId);
|
||||||
if (!certTemplate) {
|
if (!certTemplate) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Certificate template not found"
|
message: `Certificate template with ID '${certificateTemplateId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ export const certificateEstServiceFactory = ({
|
|||||||
const ca = await certificateAuthorityDAL.findById(certTemplate.caId);
|
const ca = await certificateAuthorityDAL.findById(certTemplate.caId);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Certificate Authority not found"
|
message: `Certificate Authority with ID '${certTemplate.caId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,10 @@ import ms from "ms";
|
|||||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionDynamicSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
@ -61,7 +64,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
}: TCreateDynamicSecretLeaseDTO) => {
|
}: TCreateDynamicSecretLeaseDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -72,8 +75,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -84,10 +87,16 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
|
if (!dynamicSecretCfg)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
|
||||||
|
});
|
||||||
|
|
||||||
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
|
const totalLeasesTaken = await dynamicSecretLeaseDAL.countLeasesForDynamicSecret(dynamicSecretCfg.id);
|
||||||
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
|
if (totalLeasesTaken >= appCfg.MAX_LEASE_LIMIT)
|
||||||
@ -134,7 +143,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
leaseId
|
leaseId
|
||||||
}: TRenewDynamicSecretLeaseDTO) => {
|
}: TRenewDynamicSecretLeaseDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -145,8 +154,8 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -157,10 +166,15 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease) {
|
||||||
|
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
@ -208,7 +222,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
isForced
|
isForced
|
||||||
}: TDeleteDynamicSecretLeaseDTO) => {
|
}: TDeleteDynamicSecretLeaseDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -219,15 +233,19 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Delete,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease)
|
||||||
|
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
const dynamicSecretCfg = dynamicSecretLease.dynamicSecret;
|
||||||
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
const selectedProvider = dynamicSecretProviders[dynamicSecretCfg.type as DynamicSecretProviders];
|
||||||
@ -273,7 +291,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TListDynamicSecretLeasesDTO) => {
|
}: TListDynamicSecretLeasesDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -284,15 +302,21 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${path}' in environment with slug '${environmentSlug}' not found`
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
|
if (!dynamicSecretCfg)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Dynamic secret with name '${name}' in folder with path '${path}' not found`
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
return dynamicSecretLeases;
|
return dynamicSecretLeases;
|
||||||
@ -309,7 +333,7 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TDetailsDynamicSecretLeaseDTO) => {
|
}: TDetailsDynamicSecretLeaseDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -320,15 +344,16 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionDynamicSecretActions.Lease,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: `Folder with path '${path}' not found` });
|
||||||
|
|
||||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease)
|
||||||
|
throw new NotFoundError({ message: `Dynamic secret lease with ID '${leaseId}' not found` });
|
||||||
|
|
||||||
return dynamicSecretLease;
|
return dynamicSecretLease;
|
||||||
};
|
};
|
||||||
|
20
backend/src/ee/services/dynamic-secret/dynamic-secret-fns.ts
Normal file
20
backend/src/ee/services/dynamic-secret/dynamic-secret-fns.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { getDbConnectionHost } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export const verifyHostInputValidity = (host: string) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
||||||
|
|
||||||
|
if (
|
||||||
|
appCfg.isCloud &&
|
||||||
|
// localhost
|
||||||
|
// internal ips
|
||||||
|
(host === "host.docker.internal" || host.match(/^10\.\d+\.\d+\.\d+/) || host.match(/^192\.168\.\d+\.\d+/))
|
||||||
|
)
|
||||||
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
|
|
||||||
|
if (host === "localhost" || host === "127.0.0.1" || dbHost === host) {
|
||||||
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
|
}
|
||||||
|
};
|
@ -3,10 +3,13 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import {
|
||||||
|
ProjectPermissionDynamicSecretActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection, ProjectServiceActor } from "@app/lib/types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal";
|
||||||
|
|
||||||
@ -19,6 +22,7 @@ import {
|
|||||||
TDeleteDynamicSecretDTO,
|
TDeleteDynamicSecretDTO,
|
||||||
TDetailsDynamicSecretDTO,
|
TDetailsDynamicSecretDTO,
|
||||||
TGetDynamicSecretsCountDTO,
|
TGetDynamicSecretsCountDTO,
|
||||||
|
TListDynamicSecretsByFolderMappingsDTO,
|
||||||
TListDynamicSecretsDTO,
|
TListDynamicSecretsDTO,
|
||||||
TListDynamicSecretsMultiEnvDTO,
|
TListDynamicSecretsMultiEnvDTO,
|
||||||
TUpdateDynamicSecretDTO
|
TUpdateDynamicSecretDTO
|
||||||
@ -66,7 +70,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TCreateDynamicSecretDTO) => {
|
}: TCreateDynamicSecretDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -77,8 +81,8 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Create,
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -89,7 +93,9 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder) {
|
||||||
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
if (existingDynamicSecret)
|
if (existingDynamicSecret)
|
||||||
@ -134,7 +140,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TUpdateDynamicSecretDTO) => {
|
}: TUpdateDynamicSecretDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
@ -146,8 +152,8 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -158,11 +164,15 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
|
if (!dynamicSecretCfg) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
if (newName) {
|
if (newName) {
|
||||||
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
|
const existingDynamicSecret = await dynamicSecretDAL.findOne({ name: newName, folderId: folder.id });
|
||||||
if (existingDynamicSecret)
|
if (existingDynamicSecret)
|
||||||
@ -213,7 +223,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
isForced
|
isForced
|
||||||
}: TDeleteDynamicSecretDTO) => {
|
}: TDeleteDynamicSecretDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
@ -225,15 +235,18 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) throw new BadRequestError({ message: "Dynamic secret not found" });
|
if (!dynamicSecretCfg) {
|
||||||
|
throw new NotFoundError({ message: `Dynamic secret with name '${name}' in folder '${folder.path}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
const leases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
// when not forced we check with the external system to first remove the things
|
// when not forced we check with the external system to first remove the things
|
||||||
@ -271,7 +284,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actor
|
actor
|
||||||
}: TDetailsDynamicSecretDTO) => {
|
}: TDetailsDynamicSecretDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@ -282,15 +295,22 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
const dynamicSecretCfg = await dynamicSecretDAL.findOne({ name, folderId: folder.id });
|
||||||
if (!dynamicSecretCfg) throw new NotFoundError({ message: "Dynamic secret not found" });
|
if (!dynamicSecretCfg) {
|
||||||
|
throw new NotFoundError({ message: `Dynamic secret with name '${name} in folder '${path}' not found` });
|
||||||
|
}
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
infisicalSymmetricDecrypt({
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
@ -328,14 +348,18 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
// verify user has access to each env in request
|
// verify user has access to each env in request
|
||||||
environmentSlugs.forEach((environmentSlug) =>
|
environmentSlugs.forEach((environmentSlug) =>
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
||||||
if (!folders.length) throw new NotFoundError({ message: "Folders not found" });
|
if (!folders.length) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folders with path '${path}' in environments with slugs '${environmentSlugs.join(", ")}' not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.find(
|
const dynamicSecretCfg = await dynamicSecretDAL.find(
|
||||||
{ $in: { folderId: folders.map((folder) => folder.id) }, $search: search ? { name: `%${search}%` } : undefined },
|
{ $in: { folderId: folders.map((folder) => folder.id) }, $search: search ? { name: `%${search}%` } : undefined },
|
||||||
@ -364,12 +388,14 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder) {
|
||||||
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.find(
|
const dynamicSecretCfg = await dynamicSecretDAL.find(
|
||||||
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
|
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
|
||||||
@ -398,7 +424,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
if (!projectSlug) throw new BadRequestError({ message: "Project ID or slug required" });
|
if (!projectSlug) throw new BadRequestError({ message: "Project ID or slug required" });
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,12 +436,13 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({ message: `Folder with path '${path}' in environment '${environmentSlug}' not found` });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.find(
|
const dynamicSecretCfg = await dynamicSecretDAL.find(
|
||||||
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
|
{ folderId: folder.id, $search: search ? { name: `%${search}%` } : undefined },
|
||||||
@ -428,8 +455,44 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
return dynamicSecretCfg;
|
return dynamicSecretCfg;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const listDynamicSecretsByFolderIds = async (
|
||||||
|
{ folderMappings, filters, projectId }: TListDynamicSecretsByFolderMappingsDTO,
|
||||||
|
actor: ProjectServiceActor
|
||||||
|
) => {
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor.type,
|
||||||
|
actor.id,
|
||||||
|
projectId,
|
||||||
|
actor.authMethod,
|
||||||
|
actor.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const userAccessibleFolderMappings = folderMappings.filter(({ path, environment }) =>
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
subject(ProjectPermissionSub.DynamicSecrets, { environment, secretPath: path })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupedFolderMappings = new Map(userAccessibleFolderMappings.map((path) => [path.folderId, path]));
|
||||||
|
|
||||||
|
const dynamicSecrets = await dynamicSecretDAL.listDynamicSecretsByFolderIds({
|
||||||
|
folderIds: userAccessibleFolderMappings.map(({ folderId }) => folderId),
|
||||||
|
...filters
|
||||||
|
});
|
||||||
|
|
||||||
|
return dynamicSecrets.map((dynamicSecret) => {
|
||||||
|
const { environment, path } = groupedFolderMappings.get(dynamicSecret.folderId)!;
|
||||||
|
return {
|
||||||
|
...dynamicSecret,
|
||||||
|
environment,
|
||||||
|
path
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// get dynamic secrets for multiple envs
|
// get dynamic secrets for multiple envs
|
||||||
const listDynamicSecretsByFolderIds = async ({
|
const listDynamicSecretsByEnvs = async ({
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorId,
|
actorId,
|
||||||
@ -452,14 +515,17 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
// verify user has access to each env in request
|
// verify user has access to each env in request
|
||||||
environmentSlugs.forEach((environmentSlug) =>
|
environmentSlugs.forEach((environmentSlug) =>
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Read,
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.DynamicSecrets, { environment: environmentSlug, secretPath: path })
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
const folders = await folderDAL.findBySecretPathMultiEnv(projectId, environmentSlugs, path);
|
||||||
if (!folders.length) throw new NotFoundError({ message: "Folders not found" });
|
if (!folders.length)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folders with path '${path} in environments with slugs '${environmentSlugs.join(", ")}' not found`
|
||||||
|
});
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.listDynamicSecretsByFolderIds({
|
const dynamicSecretCfg = await dynamicSecretDAL.listDynamicSecretsByFolderIds({
|
||||||
folderIds: folders.map((folder) => folder.id),
|
folderIds: folders.map((folder) => folder.id),
|
||||||
@ -492,9 +558,10 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
deleteByName,
|
deleteByName,
|
||||||
getDetails,
|
getDetails,
|
||||||
listDynamicSecretsByEnv,
|
listDynamicSecretsByEnv,
|
||||||
listDynamicSecretsByFolderIds,
|
listDynamicSecretsByEnvs,
|
||||||
getDynamicSecretCount,
|
getDynamicSecretCount,
|
||||||
getCountMultiEnv,
|
getCountMultiEnv,
|
||||||
fetchAzureEntraIdUsers
|
fetchAzureEntraIdUsers,
|
||||||
|
listDynamicSecretsByFolderIds
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -48,17 +48,27 @@ export type TDetailsDynamicSecretDTO = {
|
|||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TListDynamicSecretsDTO = {
|
export type ListDynamicSecretsFilters = {
|
||||||
path: string;
|
|
||||||
environmentSlug: string;
|
|
||||||
projectSlug?: string;
|
|
||||||
projectId?: string;
|
|
||||||
offset?: number;
|
offset?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
orderBy?: SecretsOrderBy;
|
orderBy?: SecretsOrderBy;
|
||||||
orderDirection?: OrderByDirection;
|
orderDirection?: OrderByDirection;
|
||||||
search?: string;
|
search?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
};
|
||||||
|
|
||||||
|
export type TListDynamicSecretsDTO = {
|
||||||
|
path: string;
|
||||||
|
environmentSlug: string;
|
||||||
|
projectSlug?: string;
|
||||||
|
projectId?: string;
|
||||||
|
} & ListDynamicSecretsFilters &
|
||||||
|
Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TListDynamicSecretsByFolderMappingsDTO = {
|
||||||
|
projectId: string;
|
||||||
|
folderMappings: { folderId: string; path: string; environment: string }[];
|
||||||
|
filters: ListDynamicSecretsFilters;
|
||||||
|
};
|
||||||
|
|
||||||
export type TListDynamicSecretsMultiEnvDTO = Omit<
|
export type TListDynamicSecretsMultiEnvDTO = Omit<
|
||||||
TListDynamicSecretsDTO,
|
TListDynamicSecretsDTO,
|
||||||
|
@ -2,10 +2,9 @@ import { Client as ElasticSearchClient } from "@elastic/elasticsearch";
|
|||||||
import { customAlphabet } from "nanoid";
|
import { customAlphabet } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||||
import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models";
|
import { DynamicSecretElasticSearchSchema, ElasticSearchAuthTypes, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
@ -19,23 +18,8 @@ const generateUsername = () => {
|
|||||||
|
|
||||||
export const ElasticSearchProvider = (): TDynamicProviderFns => {
|
export const ElasticSearchProvider = (): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
|
|
||||||
|
|
||||||
const providerInputs = await DynamicSecretElasticSearchSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretElasticSearchSchema.parseAsync(inputs);
|
||||||
if (
|
verifyHostInputValidity(providerInputs.host);
|
||||||
isCloud &&
|
|
||||||
// localhost
|
|
||||||
// internal ips
|
|
||||||
(providerInputs.host === "host.docker.internal" ||
|
|
||||||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
|
||||||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
}
|
|
||||||
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { SnowflakeProvider } from "@app/ee/services/dynamic-secret/providers/snowflake";
|
||||||
|
|
||||||
import { AwsElastiCacheDatabaseProvider } from "./aws-elasticache";
|
import { AwsElastiCacheDatabaseProvider } from "./aws-elasticache";
|
||||||
import { AwsIamProvider } from "./aws-iam";
|
import { AwsIamProvider } from "./aws-iam";
|
||||||
import { AzureEntraIDProvider } from "./azure-entra-id";
|
import { AzureEntraIDProvider } from "./azure-entra-id";
|
||||||
@ -9,6 +11,7 @@ import { MongoAtlasProvider } from "./mongo-atlas";
|
|||||||
import { MongoDBProvider } from "./mongo-db";
|
import { MongoDBProvider } from "./mongo-db";
|
||||||
import { RabbitMqProvider } from "./rabbit-mq";
|
import { RabbitMqProvider } from "./rabbit-mq";
|
||||||
import { RedisDatabaseProvider } from "./redis";
|
import { RedisDatabaseProvider } from "./redis";
|
||||||
|
import { SapHanaProvider } from "./sap-hana";
|
||||||
import { SqlDatabaseProvider } from "./sql-database";
|
import { SqlDatabaseProvider } from "./sql-database";
|
||||||
|
|
||||||
export const buildDynamicSecretProviders = () => ({
|
export const buildDynamicSecretProviders = () => ({
|
||||||
@ -22,5 +25,7 @@ export const buildDynamicSecretProviders = () => ({
|
|||||||
[DynamicSecretProviders.ElasticSearch]: ElasticSearchProvider(),
|
[DynamicSecretProviders.ElasticSearch]: ElasticSearchProvider(),
|
||||||
[DynamicSecretProviders.RabbitMq]: RabbitMqProvider(),
|
[DynamicSecretProviders.RabbitMq]: RabbitMqProvider(),
|
||||||
[DynamicSecretProviders.AzureEntraID]: AzureEntraIDProvider(),
|
[DynamicSecretProviders.AzureEntraID]: AzureEntraIDProvider(),
|
||||||
[DynamicSecretProviders.Ldap]: LdapProvider()
|
[DynamicSecretProviders.Ldap]: LdapProvider(),
|
||||||
|
[DynamicSecretProviders.SapHana]: SapHanaProvider(),
|
||||||
|
[DynamicSecretProviders.Snowflake]: SnowflakeProvider()
|
||||||
});
|
});
|
||||||
|
@ -166,6 +166,27 @@ export const DynamicSecretMongoDBSchema = z.object({
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DynamicSecretSapHanaSchema = z.object({
|
||||||
|
host: z.string().trim().toLowerCase(),
|
||||||
|
port: z.number(),
|
||||||
|
username: z.string().trim(),
|
||||||
|
password: z.string().trim(),
|
||||||
|
creationStatement: z.string().trim(),
|
||||||
|
revocationStatement: z.string().trim(),
|
||||||
|
renewStatement: z.string().trim().optional(),
|
||||||
|
ca: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DynamicSecretSnowflakeSchema = z.object({
|
||||||
|
accountId: z.string().trim().min(1),
|
||||||
|
orgId: z.string().trim().min(1),
|
||||||
|
username: z.string().trim().min(1),
|
||||||
|
password: z.string().trim().min(1),
|
||||||
|
creationStatement: z.string().trim().min(1),
|
||||||
|
revocationStatement: z.string().trim().min(1),
|
||||||
|
renewStatement: z.string().trim().optional()
|
||||||
|
});
|
||||||
|
|
||||||
export const AzureEntraIDSchema = z.object({
|
export const AzureEntraIDSchema = z.object({
|
||||||
tenantId: z.string().trim().min(1),
|
tenantId: z.string().trim().min(1),
|
||||||
userId: z.string().trim().min(1),
|
userId: z.string().trim().min(1),
|
||||||
@ -196,7 +217,9 @@ export enum DynamicSecretProviders {
|
|||||||
MongoDB = "mongo-db",
|
MongoDB = "mongo-db",
|
||||||
RabbitMq = "rabbit-mq",
|
RabbitMq = "rabbit-mq",
|
||||||
AzureEntraID = "azure-entra-id",
|
AzureEntraID = "azure-entra-id",
|
||||||
Ldap = "ldap"
|
Ldap = "ldap",
|
||||||
|
SapHana = "sap-hana",
|
||||||
|
Snowflake = "snowflake"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||||
@ -204,13 +227,15 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
|||||||
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.Cassandra), inputs: DynamicSecretCassandraSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.AwsIam), inputs: DynamicSecretAwsIamSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.Redis), inputs: DynamicSecretRedisDBSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.Redis), inputs: DynamicSecretRedisDBSchema }),
|
||||||
|
z.object({ type: z.literal(DynamicSecretProviders.SapHana), inputs: DynamicSecretSapHanaSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.RabbitMq), inputs: DynamicSecretRabbitMqSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.RabbitMq), inputs: DynamicSecretRabbitMqSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.AzureEntraID), inputs: AzureEntraIDSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.AzureEntraID), inputs: AzureEntraIDSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.Ldap), inputs: LdapSchema })
|
z.object({ type: z.literal(DynamicSecretProviders.Ldap), inputs: LdapSchema }),
|
||||||
|
z.object({ type: z.literal(DynamicSecretProviders.Snowflake), inputs: DynamicSecretSnowflakeSchema })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type TDynamicProviderFns = {
|
export type TDynamicProviderFns = {
|
||||||
|
@ -2,10 +2,9 @@ import { MongoClient } from "mongodb";
|
|||||||
import { customAlphabet } from "nanoid";
|
import { customAlphabet } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||||
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = (size = 48) => {
|
const generatePassword = (size = 48) => {
|
||||||
@ -19,22 +18,8 @@ const generateUsername = () => {
|
|||||||
|
|
||||||
export const MongoDBProvider = (): TDynamicProviderFns => {
|
export const MongoDBProvider = (): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
const providerInputs = await DynamicSecretMongoDBSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretMongoDBSchema.parseAsync(inputs);
|
||||||
if (
|
verifyHostInputValidity(providerInputs.host);
|
||||||
appCfg.isCloud &&
|
|
||||||
// localhost
|
|
||||||
// internal ips
|
|
||||||
(providerInputs.host === "host.docker.internal" ||
|
|
||||||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
|
||||||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
|
|
||||||
)
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
|
|
||||||
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3,12 +3,11 @@ import https from "https";
|
|||||||
import { customAlphabet } from "nanoid";
|
import { customAlphabet } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
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 { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||||
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
@ -79,23 +78,8 @@ async function deleteRabbitMqUser({ axiosInstance, usernameToDelete }: TDeleteRa
|
|||||||
|
|
||||||
export const RabbitMqProvider = (): TDynamicProviderFns => {
|
export const RabbitMqProvider = (): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
|
|
||||||
|
|
||||||
const providerInputs = await DynamicSecretRabbitMqSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretRabbitMqSchema.parseAsync(inputs);
|
||||||
if (
|
verifyHostInputValidity(providerInputs.host);
|
||||||
isCloud &&
|
|
||||||
// localhost
|
|
||||||
// internal ips
|
|
||||||
(providerInputs.host === "host.docker.internal" ||
|
|
||||||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
|
||||||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
}
|
|
||||||
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
};
|
};
|
||||||
|
@ -3,11 +3,10 @@ import { Redis } from "ioredis";
|
|||||||
import { customAlphabet } from "nanoid";
|
import { customAlphabet } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { getDbConnectionHost } from "@app/lib/knex";
|
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||||
import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretRedisDBSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const generatePassword = () => {
|
const generatePassword = () => {
|
||||||
@ -51,22 +50,8 @@ const executeTransactions = async (connection: Redis, commands: string[]): Promi
|
|||||||
|
|
||||||
export const RedisDatabaseProvider = (): TDynamicProviderFns => {
|
export const RedisDatabaseProvider = (): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
|
|
||||||
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
|
||||||
|
|
||||||
const providerInputs = await DynamicSecretRedisDBSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretRedisDBSchema.parseAsync(inputs);
|
||||||
if (
|
verifyHostInputValidity(providerInputs.host);
|
||||||
isCloud &&
|
|
||||||
// localhost
|
|
||||||
// internal ips
|
|
||||||
(providerInputs.host === "host.docker.internal" ||
|
|
||||||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
|
||||||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
|
|
||||||
)
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1" || dbHost === providerInputs.host)
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
174
backend/src/ee/services/dynamic-secret/providers/sap-hana.ts
Normal file
174
backend/src/ee/services/dynamic-secret/providers/sap-hana.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
|
|
||||||
|
import handlebars from "handlebars";
|
||||||
|
import hdb from "hdb";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||||
|
import { DynamicSecretSapHanaSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
const generatePassword = (size = 48) => {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
return customAlphabet(charset, 48)(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUsername = () => {
|
||||||
|
return alphaNumericNanoId(32);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SapHanaProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await DynamicSecretSapHanaSchema.parseAsync(inputs);
|
||||||
|
|
||||||
|
verifyHostInputValidity(providerInputs.host);
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = async (providerInputs: z.infer<typeof DynamicSecretSapHanaSchema>) => {
|
||||||
|
const client = hdb.createClient({
|
||||||
|
host: providerInputs.host,
|
||||||
|
port: providerInputs.port,
|
||||||
|
user: providerInputs.username,
|
||||||
|
password: providerInputs.password,
|
||||||
|
...(providerInputs.ca
|
||||||
|
? {
|
||||||
|
ca: providerInputs.ca
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.connect((err: any) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.readyState) {
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(new Error("SAP HANA client not ready"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const testResult: boolean = await new Promise((resolve, reject) => {
|
||||||
|
client.exec("SELECT 1 FROM DUMMY;", (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return testResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (inputs: unknown, expireAt: number) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
|
const username = generateUsername();
|
||||||
|
const password = generatePassword();
|
||||||
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
expiration
|
||||||
|
});
|
||||||
|
|
||||||
|
const queries = creationStatement.toString().split(";").filter(Boolean);
|
||||||
|
for await (const query of queries) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.exec(query, (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
reject(
|
||||||
|
new BadRequestError({
|
||||||
|
message: err.message
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (inputs: unknown, username: string) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
|
||||||
|
const queries = revokeStatement.toString().split(";").filter(Boolean);
|
||||||
|
for await (const query of queries) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.exec(query, (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
reject(
|
||||||
|
new BadRequestError({
|
||||||
|
message: err.message
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (inputs: unknown, username: string, expireAt: number) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
try {
|
||||||
|
const expiration = new Date(expireAt).toISOString();
|
||||||
|
|
||||||
|
const renewStatement = handlebars.compile(providerInputs.renewStatement)({ username, expiration });
|
||||||
|
const queries = renewStatement.toString().split(";").filter(Boolean);
|
||||||
|
for await (const query of queries) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.exec(query, (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
reject(
|
||||||
|
new BadRequestError({
|
||||||
|
message: err.message
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
client.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
174
backend/src/ee/services/dynamic-secret/providers/snowflake.ts
Normal file
174
backend/src/ee/services/dynamic-secret/providers/snowflake.ts
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import handlebars from "handlebars";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import snowflake from "snowflake-sdk";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { DynamicSecretSnowflakeSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
// destroy client requires callback...
|
||||||
|
const noop = () => {};
|
||||||
|
|
||||||
|
const generatePassword = (size = 48) => {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||||
|
return customAlphabet(charset, 48)(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUsername = () => {
|
||||||
|
return `infisical_${alphaNumericNanoId(32)}`; // username must start with alpha character, hence prefix
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDaysToExpiry = (expiryDate: Date) => {
|
||||||
|
const start = new Date().getTime();
|
||||||
|
const end = new Date(expiryDate).getTime();
|
||||||
|
const diffTime = Math.abs(end - start);
|
||||||
|
|
||||||
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SnowflakeProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await DynamicSecretSnowflakeSchema.parseAsync(inputs);
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = async (providerInputs: z.infer<typeof DynamicSecretSnowflakeSchema>) => {
|
||||||
|
const client = snowflake.createConnection({
|
||||||
|
account: `${providerInputs.orgId}-${providerInputs.accountId}`,
|
||||||
|
username: providerInputs.username,
|
||||||
|
password: providerInputs.password,
|
||||||
|
application: "Infisical"
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.connectAsync(noop);
|
||||||
|
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
let isValidConnection: boolean;
|
||||||
|
|
||||||
|
try {
|
||||||
|
isValidConnection = await Promise.race([
|
||||||
|
client.isValidAsync(),
|
||||||
|
new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, 10000);
|
||||||
|
}).then(() => {
|
||||||
|
throw new BadRequestError({ message: "Unable to establish connection - verify credentials" });
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
} finally {
|
||||||
|
client.destroy(noop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValidConnection;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (inputs: unknown, expireAt: number) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = generateUsername();
|
||||||
|
const password = generatePassword();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const expiration = getDaysToExpiry(new Date(expireAt));
|
||||||
|
const creationStatement = handlebars.compile(providerInputs.creationStatement, { noEscape: true })({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
expiration
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.execute({
|
||||||
|
sqlText: creationStatement,
|
||||||
|
complete(err) {
|
||||||
|
if (err) {
|
||||||
|
return reject(new BadRequestError({ name: "CreateLease", message: err.message }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
client.destroy(noop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (inputs: unknown, username: string) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const revokeStatement = handlebars.compile(providerInputs.revocationStatement)({ username });
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.execute({
|
||||||
|
sqlText: revokeStatement,
|
||||||
|
complete(err) {
|
||||||
|
if (err) {
|
||||||
|
return reject(new BadRequestError({ name: "RevokeLease", message: err.message }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
client.destroy(noop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (inputs: unknown, username: string, expireAt: number) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
|
||||||
|
if (!providerInputs.renewStatement) return { entityId: username };
|
||||||
|
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const expiration = getDaysToExpiry(new Date(expireAt));
|
||||||
|
const renewStatement = handlebars.compile(providerInputs.renewStatement)({
|
||||||
|
username,
|
||||||
|
expiration
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
client.execute({
|
||||||
|
sqlText: renewStatement,
|
||||||
|
complete(err) {
|
||||||
|
if (err) {
|
||||||
|
return reject(new BadRequestError({ name: "RenewLease", message: err.message }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
client.destroy(noop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
@ -3,11 +3,9 @@ import knex from "knex";
|
|||||||
import { customAlphabet } from "nanoid";
|
import { customAlphabet } from "nanoid";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { getDbConnectionHost } from "@app/lib/knex";
|
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { verifyHostInputValidity } from "../dynamic-secret-fns";
|
||||||
import { DynamicSecretSqlDBSchema, SqlProviders, TDynamicProviderFns } from "./models";
|
import { DynamicSecretSqlDBSchema, SqlProviders, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
|
||||||
@ -29,27 +27,8 @@ const generateUsername = (provider: SqlProviders) => {
|
|||||||
|
|
||||||
export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
export const SqlDatabaseProvider = (): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
|
|
||||||
const dbHost = appCfg.DB_HOST || getDbConnectionHost(appCfg.DB_CONNECTION_URI);
|
|
||||||
|
|
||||||
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretSqlDBSchema.parseAsync(inputs);
|
||||||
if (
|
verifyHostInputValidity(providerInputs.host);
|
||||||
isCloud &&
|
|
||||||
// localhost
|
|
||||||
// internal ips
|
|
||||||
(providerInputs.host === "host.docker.internal" ||
|
|
||||||
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
|
||||||
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
|
|
||||||
)
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
if (
|
|
||||||
providerInputs.host === "localhost" ||
|
|
||||||
providerInputs.host === "127.0.0.1" ||
|
|
||||||
// database infisical uses
|
|
||||||
dbHost === providerInputs.host
|
|
||||||
)
|
|
||||||
throw new BadRequestError({ message: "Invalid db host" });
|
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
const kmsName = name ? slugify(name) : undefined;
|
const kmsName = name ? slugify(name) : undefined;
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
|
||||||
|
|
||||||
let sanitizedProviderInput = "";
|
let sanitizedProviderInput = "";
|
||||||
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
|
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
|
||||||
@ -220,7 +220,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
|
||||||
|
|
||||||
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
const externalKms = await externalKmsDAL.transaction(async (tx) => {
|
||||||
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
|
const kms = await kmsDAL.deleteById(kmsDoc.id, tx);
|
||||||
@ -258,7 +258,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsId}' not found` });
|
||||||
|
|
||||||
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
@ -298,7 +298,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Kms);
|
||||||
|
|
||||||
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
const externalKmsDoc = await externalKmsDAL.findOne({ kmsKeyId: kmsDoc.id });
|
||||||
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: `External KMS with ID '${kmsDoc.id}' not found` });
|
||||||
|
|
||||||
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
|
@ -65,16 +65,18 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
groupId,
|
groupId,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
limit,
|
limit,
|
||||||
username
|
username, // depreciated in favor of search
|
||||||
|
search
|
||||||
}: {
|
}: {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
groupId: string;
|
groupId: string;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
search?: string;
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
let query = db
|
const query = db
|
||||||
.replicaNode()(TableName.OrgMembership)
|
.replicaNode()(TableName.OrgMembership)
|
||||||
.where(`${TableName.OrgMembership}.orgId`, orgId)
|
.where(`${TableName.OrgMembership}.orgId`, orgId)
|
||||||
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
.join(TableName.Users, `${TableName.OrgMembership}.userId`, `${TableName.Users}.id`)
|
||||||
@ -92,31 +94,39 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("firstName").withSchema(TableName.Users),
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
db.ref("lastName").withSchema(TableName.Users),
|
db.ref("lastName").withSchema(TableName.Users),
|
||||||
db.ref("id").withSchema(TableName.Users).as("userId")
|
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||||
|
db.raw(`count(*) OVER() as total_count`)
|
||||||
)
|
)
|
||||||
.where({ isGhost: false })
|
.where({ isGhost: false })
|
||||||
.offset(offset);
|
.offset(offset)
|
||||||
|
.orderBy("firstName", "asc");
|
||||||
|
|
||||||
if (limit) {
|
if (limit) {
|
||||||
query = query.limit(limit);
|
void query.limit(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username) {
|
if (search) {
|
||||||
query = query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
|
void query.andWhereRaw(`CONCAT_WS(' ', "firstName", "lastName", "username") ilike '%${search}%'`);
|
||||||
|
} else if (username) {
|
||||||
|
void query.andWhere(`${TableName.Users}.username`, "ilike", `%${username}%`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = await query;
|
const members = await query;
|
||||||
|
|
||||||
return members.map(
|
return {
|
||||||
({ email, username: memberUsername, firstName, lastName, userId, groupId: memberGroupId }) => ({
|
members: members.map(
|
||||||
id: userId,
|
({ email, username: memberUsername, firstName, lastName, userId, groupId: memberGroupId }) => ({
|
||||||
email,
|
id: userId,
|
||||||
username: memberUsername,
|
email,
|
||||||
firstName,
|
username: memberUsername,
|
||||||
lastName,
|
firstName,
|
||||||
isPartOfGroup: !!memberGroupId
|
lastName,
|
||||||
})
|
isPartOfGroup: !!memberGroupId
|
||||||
);
|
})
|
||||||
|
),
|
||||||
|
// @ts-expect-error col select is raw and not strongly typed
|
||||||
|
totalCount: Number(members?.[0]?.total_count ?? 0)
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find all org members" });
|
throw new DatabaseError({ error, name: "Find all org members" });
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
|
|
||||||
if (!ghostUser) {
|
if (!ghostUser) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find project owner"
|
message: `Failed to find project owner of project with ID '${projectId}'`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
|
|
||||||
if (!ghostUserLatestKey) {
|
if (!ghostUserLatestKey) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find project owner's latest key"
|
message: `Failed to find project owner's latest key in project with ID '${projectId}'`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +90,7 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
|
|
||||||
if (!bot) {
|
if (!bot) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find project bot"
|
message: `Failed to find project bot in project with ID '${projectId}'`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,8 @@ export const groupServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
search
|
||||||
}: TListGroupUsersDTO) => {
|
}: TListGroupUsersDTO) => {
|
||||||
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
@ -244,17 +245,16 @@ export const groupServiceFactory = ({
|
|||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const users = await groupDAL.findAllGroupPossibleMembers({
|
const { members, totalCount } = await groupDAL.findAllGroupPossibleMembers({
|
||||||
orgId: group.orgId,
|
orgId: group.orgId,
|
||||||
groupId: group.id,
|
groupId: group.id,
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
username
|
username,
|
||||||
|
search
|
||||||
});
|
});
|
||||||
|
|
||||||
const count = await orgDAL.countAllOrgMembers(group.orgId);
|
return { users: members, totalCount };
|
||||||
|
|
||||||
return { users, totalCount: count };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
|
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
|
||||||
|
@ -38,6 +38,7 @@ export type TListGroupUsersDTO = {
|
|||||||
offset: number;
|
offset: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
search?: string;
|
||||||
} & TGenericPermission;
|
} & TGenericPermission;
|
||||||
|
|
||||||
export type TAddUserToGroupDTO = {
|
export type TAddUserToGroupDTO = {
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeV2DALFactory = ReturnType<
|
||||||
|
typeof identityProjectAdditionalPrivilegeV2DALFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const identityProjectAdditionalPrivilegeV2DALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.IdentityProjectAdditionalPrivilege);
|
||||||
|
return orm;
|
||||||
|
};
|
@ -0,0 +1,343 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { packRules } from "@casl/ability/extra";
|
||||||
|
import ms from "ms";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { unpackPermissions } from "@app/server/routes/santizedSchemas/permission";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
|
import { TIdentityProjectAdditionalPrivilegeV2DALFactory } from "./identity-project-additional-privilege-v2-dal";
|
||||||
|
import {
|
||||||
|
IdentityProjectAdditionalPrivilegeTemporaryMode,
|
||||||
|
TCreateIdentityPrivilegeDTO,
|
||||||
|
TDeleteIdentityPrivilegeByIdDTO,
|
||||||
|
TGetIdentityPrivilegeDetailsByIdDTO,
|
||||||
|
TGetIdentityPrivilegeDetailsBySlugDTO,
|
||||||
|
TListIdentityPrivilegesDTO,
|
||||||
|
TUpdateIdentityPrivilegeByIdDTO
|
||||||
|
} from "./identity-project-additional-privilege-v2-types";
|
||||||
|
|
||||||
|
type TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep = {
|
||||||
|
identityProjectAdditionalPrivilegeDAL: TIdentityProjectAdditionalPrivilegeV2DALFactory;
|
||||||
|
identityProjectDAL: Pick<TIdentityProjectDALFactory, "findOne" | "findById">;
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIdentityProjectAdditionalPrivilegeV2ServiceFactory = ReturnType<
|
||||||
|
typeof identityProjectAdditionalPrivilegeV2ServiceFactory
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const identityProjectAdditionalPrivilegeV2ServiceFactory = ({
|
||||||
|
identityProjectAdditionalPrivilegeDAL,
|
||||||
|
identityProjectDAL,
|
||||||
|
projectDAL,
|
||||||
|
permissionService
|
||||||
|
}: TIdentityProjectAdditionalPrivilegeV2ServiceFactoryDep) => {
|
||||||
|
const create = async ({
|
||||||
|
slug,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorOrgId,
|
||||||
|
identityId,
|
||||||
|
permissions: customPermission,
|
||||||
|
actorAuthMethod,
|
||||||
|
...dto
|
||||||
|
}: TCreateIdentityPrivilegeDTO) => {
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
||||||
|
|
||||||
|
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||||
|
if (!dto.isTemporary) {
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
slug,
|
||||||
|
permissions: packedPermission
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
|
projectMembershipId: identityProjectMembership.id,
|
||||||
|
slug,
|
||||||
|
permissions: packedPermission,
|
||||||
|
isTemporary: true,
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
|
temporaryRange: dto.temporaryRange,
|
||||||
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateById = async ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TUpdateIdentityPrivilegeByIdDTO) => {
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
||||||
|
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityProjectMembership.identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
|
if (data?.slug) {
|
||||||
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug: data.slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (existingSlug && existingSlug.id !== identityPrivilege.id)
|
||||||
|
throw new BadRequestError({ message: "Additional privilege with provided slug already exists" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||||
|
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
|
||||||
|
if (isTemporary) {
|
||||||
|
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
||||||
|
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
|
slug: data.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
|
isTemporary: data.isTemporary,
|
||||||
|
temporaryRange: data.temporaryRange,
|
||||||
|
temporaryMode: data.temporaryMode,
|
||||||
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
|
slug: data.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
|
isTemporary: false,
|
||||||
|
temporaryAccessStartTime: null,
|
||||||
|
temporaryAccessEndTime: null,
|
||||||
|
temporaryRange: null,
|
||||||
|
temporaryMode: null
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteById = async ({ actorId, id, actor, actorOrgId, actorAuthMethod }: TDeleteIdentityPrivilegeByIdDTO) => {
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
||||||
|
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
||||||
|
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.IDENTITY,
|
||||||
|
identityProjectMembership.identityId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
|
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||||
|
return {
|
||||||
|
...deletedPrivilege,
|
||||||
|
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrivilegeDetailsById = async ({
|
||||||
|
id,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TGetIdentityPrivilegeDetailsByIdDTO) => {
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findById(id);
|
||||||
|
if (!identityPrivilege) throw new NotFoundError({ message: `Identity privilege with ${id} not found` });
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ id: identityPrivilege.projectMembershipId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find identity with membership ${identityPrivilege.projectMembershipId}`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...identityPrivilege,
|
||||||
|
permissions: unpackPermissions(identityPrivilege.permissions)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrivilegeDetailsBySlug = async ({
|
||||||
|
identityId,
|
||||||
|
slug,
|
||||||
|
projectSlug,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod
|
||||||
|
}: TGetIdentityPrivilegeDetailsBySlugDTO) => {
|
||||||
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
if (!project) throw new NotFoundError({ message: `Project with slug ${slug} not found` });
|
||||||
|
const projectId = project.id;
|
||||||
|
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
|
slug,
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
});
|
||||||
|
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
||||||
|
|
||||||
|
return {
|
||||||
|
...identityPrivilege,
|
||||||
|
permissions: unpackPermissions(identityPrivilege.permissions)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const listIdentityProjectPrivileges = async ({
|
||||||
|
identityId,
|
||||||
|
actorOrgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectId
|
||||||
|
}: TListIdentityPrivilegesDTO) => {
|
||||||
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
|
if (!identityProjectMembership)
|
||||||
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
identityProjectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
|
const identityPrivileges = await identityProjectAdditionalPrivilegeDAL.find(
|
||||||
|
{
|
||||||
|
projectMembershipId: identityProjectMembership.id
|
||||||
|
},
|
||||||
|
{ sort: [[`${TableName.IdentityProjectAdditionalPrivilege}.slug` as "slug", "asc"]] }
|
||||||
|
);
|
||||||
|
return identityPrivileges;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getPrivilegeDetailsById,
|
||||||
|
getPrivilegeDetailsBySlug,
|
||||||
|
listIdentityProjectPrivileges,
|
||||||
|
create,
|
||||||
|
updateById,
|
||||||
|
deleteById
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,55 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||||
|
|
||||||
|
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||||
|
Relative = "relative"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TCreateIdentityPrivilegeDTO = {
|
||||||
|
permissions: TProjectPermissionV2Schema[];
|
||||||
|
identityId: string;
|
||||||
|
projectId: string;
|
||||||
|
slug: string;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
isTemporary: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isTemporary: true;
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}
|
||||||
|
) &
|
||||||
|
Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateIdentityPrivilegeByIdDTO = { id: string } & Omit<TProjectPermission, "projectId"> & {
|
||||||
|
data: Partial<{
|
||||||
|
permissions: TProjectPermissionV2Schema[];
|
||||||
|
slug: string;
|
||||||
|
isTemporary: boolean;
|
||||||
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
temporaryRange: string;
|
||||||
|
temporaryAccessStartTime: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TDeleteIdentityPrivilegeByIdDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetIdentityPrivilegeDetailsByIdDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TListIdentityPrivilegesDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
identityId: string;
|
||||||
|
projectId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGetIdentityPrivilegeDetailsBySlugDTO = Omit<TProjectPermission, "projectId"> & {
|
||||||
|
slug: string;
|
||||||
|
identityId: string;
|
||||||
|
projectSlug: string;
|
||||||
|
};
|
@ -1,10 +1,10 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@ -32,16 +32,6 @@ export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
|||||||
typeof identityProjectAdditionalPrivilegeServiceFactory
|
typeof identityProjectAdditionalPrivilegeServiceFactory
|
||||||
>;
|
>;
|
||||||
|
|
||||||
// TODO(akhilmhdh): move this to more centralized
|
|
||||||
export const UnpackedPermissionSchema = z.object({
|
|
||||||
subject: z
|
|
||||||
.union([z.string().min(1), z.string().array()])
|
|
||||||
.transform((el) => (typeof el !== "string" ? el[0] : el))
|
|
||||||
.optional(),
|
|
||||||
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
|
|
||||||
conditions: z.unknown().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
const unpackPermissions = (permissions: unknown) =>
|
const unpackPermissions = (permissions: unknown) =>
|
||||||
UnpackedPermissionSchema.array().parse(
|
UnpackedPermissionSchema.array().parse(
|
||||||
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||||
@ -65,7 +55,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
...dto
|
...dto
|
||||||
}: TCreateIdentityPrivilegeDTO) => {
|
}: TCreateIdentityPrivilegeDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
@ -80,14 +70,18 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityId,
|
identityId,
|
||||||
identityProjectMembership.projectId,
|
identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
|
||||||
|
// 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
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(customPermission));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@ -97,11 +91,12 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
||||||
|
|
||||||
|
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||||
if (!dto.isTemporary) {
|
if (!dto.isTemporary) {
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
projectMembershipId: identityProjectMembership.id,
|
projectMembershipId: identityProjectMembership.id,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission
|
permissions: packedPermission
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...additionalPrivilege,
|
...additionalPrivilege,
|
||||||
@ -113,7 +108,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.create({
|
||||||
projectMembershipId: identityProjectMembership.id,
|
projectMembershipId: identityProjectMembership.id,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission,
|
permissions: packedPermission,
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
temporaryRange: dto.temporaryRange,
|
temporaryRange: dto.temporaryRange,
|
||||||
@ -137,7 +132,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TUpdateIdentityPrivilegeDTO) => {
|
}: TUpdateIdentityPrivilegeDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
@ -152,14 +147,19 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
||||||
const { permission: identityRolePermission } = await permissionService.getProjectPermission(
|
|
||||||
|
const { permission: targetIdentityPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.IDENTITY,
|
ActorType.IDENTITY,
|
||||||
identityProjectMembership.identityId,
|
identityProjectMembership.identityId,
|
||||||
identityProjectMembership.projectId,
|
identityProjectMembership.projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, identityRolePermission);
|
|
||||||
|
// 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
|
||||||
|
targetIdentityPermission.update(targetIdentityPermission.rules.concat(data.permissions || []));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetIdentityPermission);
|
||||||
if (!hasRequiredPriviledges)
|
if (!hasRequiredPriviledges)
|
||||||
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
@ -167,7 +167,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
slug,
|
slug,
|
||||||
projectMembershipId: identityProjectMembership.id
|
projectMembershipId: identityProjectMembership.id
|
||||||
});
|
});
|
||||||
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
if (!identityPrivilege) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
if (data?.slug) {
|
if (data?.slug) {
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
@ -178,23 +182,29 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
const isTemporary = typeof data?.isTemporary !== "undefined" ? data.isTemporary : identityPrivilege.isTemporary;
|
||||||
|
|
||||||
|
const packedPermission = data.permissions ? JSON.stringify(packRules(data.permissions)) : undefined;
|
||||||
if (isTemporary) {
|
if (isTemporary) {
|
||||||
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
const temporaryAccessStartTime = data?.temporaryAccessStartTime || identityPrivilege?.temporaryAccessStartTime;
|
||||||
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
const temporaryRange = data?.temporaryRange || identityPrivilege?.temporaryRange;
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
...data,
|
slug: data.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
|
isTemporary: data.isTemporary,
|
||||||
|
temporaryRange: data.temporaryRange,
|
||||||
|
temporaryMode: data.temporaryMode,
|
||||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...additionalPrivilege,
|
...additionalPrivilege,
|
||||||
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
const additionalPrivilege = await identityProjectAdditionalPrivilegeDAL.updateById(identityPrivilege.id, {
|
||||||
...data,
|
slug: data.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
temporaryAccessStartTime: null,
|
temporaryAccessStartTime: null,
|
||||||
temporaryAccessEndTime: null,
|
temporaryAccessEndTime: null,
|
||||||
@ -203,7 +213,6 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
...additionalPrivilege,
|
...additionalPrivilege,
|
||||||
|
|
||||||
permissions: unpackPermissions(additionalPrivilege.permissions)
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -218,7 +227,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TDeleteIdentityPrivilegeDTO) => {
|
}: TDeleteIdentityPrivilegeDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
@ -248,7 +257,11 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
slug,
|
slug,
|
||||||
projectMembershipId: identityProjectMembership.id
|
projectMembershipId: identityProjectMembership.id
|
||||||
});
|
});
|
||||||
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
if (!identityPrivilege) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||||
return {
|
return {
|
||||||
@ -268,7 +281,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TGetIdentityPrivilegeDetailsDTO) => {
|
}: TGetIdentityPrivilegeDetailsDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
@ -281,14 +294,17 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const identityPrivilege = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
projectMembershipId: identityProjectMembership.id
|
projectMembershipId: identityProjectMembership.id
|
||||||
});
|
});
|
||||||
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
if (!identityPrivilege) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Identity additional privilege with slug '${slug}' not found for the specified identity with ID '${identityProjectMembership.identityId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...identityPrivilege,
|
...identityPrivilege,
|
||||||
permissions: unpackPermissions(identityPrivilege.permissions)
|
permissions: unpackPermissions(identityPrivilege.permissions)
|
||||||
@ -304,7 +320,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
projectSlug
|
projectSlug
|
||||||
}: TListIdentityPrivilegesDTO) => {
|
}: TListIdentityPrivilegesDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
if (!project) throw new NotFoundError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: `Project with slug '${projectSlug}' not found` });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
@ -324,7 +340,6 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
return identityPrivileges.map((el) => ({
|
return identityPrivileges.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
|
|
||||||
permissions: unpackPermissions(el.permissions)
|
permissions: unpackPermissions(el.permissions)
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||||
|
|
||||||
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
export enum IdentityProjectAdditionalPrivilegeTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateIdentityPrivilegeDTO = {
|
export type TCreateIdentityPrivilegeDTO = {
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
identityId: string;
|
identityId: string;
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
@ -27,7 +29,7 @@ export type TUpdateIdentityPrivilegeDTO = { slug: string; identityId: string; pr
|
|||||||
"projectId"
|
"projectId"
|
||||||
> & {
|
> & {
|
||||||
data: Partial<{
|
data: Partial<{
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: boolean;
|
isTemporary: boolean;
|
||||||
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
temporaryMode: IdentityProjectAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
@ -247,7 +247,11 @@ export const ldapConfigServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId });
|
||||||
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
if (!orgBot)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization bot in organization with ID '${orgId}' not found`,
|
||||||
|
name: "OrgBotNotFound"
|
||||||
|
});
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@ -283,10 +287,19 @@ export const ldapConfigServiceFactory = ({
|
|||||||
|
|
||||||
const getLdapCfg = async (filter: { orgId: string; isActive?: boolean; id?: string }) => {
|
const getLdapCfg = async (filter: { orgId: string; isActive?: boolean; id?: string }) => {
|
||||||
const ldapConfig = await ldapConfigDAL.findOne(filter);
|
const ldapConfig = await ldapConfigDAL.findOne(filter);
|
||||||
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
|
if (!ldapConfig) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find organization LDAP data in organization with ID '${filter.orgId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId });
|
||||||
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
if (!orgBot) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization bot not found in organization with ID ${ldapConfig.orgId}`,
|
||||||
|
name: "OrgBotNotFound"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
@ -369,7 +382,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
|
|
||||||
const bootLdap = async (organizationSlug: string) => {
|
const bootLdap = async (organizationSlug: string) => {
|
||||||
const organization = await orgDAL.findOne({ slug: organizationSlug });
|
const organization = await orgDAL.findOne({ slug: organizationSlug });
|
||||||
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
if (!organization) throw new NotFoundError({ message: `Organization with slug '${organizationSlug}' not found` });
|
||||||
|
|
||||||
const ldapConfig = await getLdapCfg({
|
const ldapConfig = await getLdapCfg({
|
||||||
orgId: organization.id,
|
orgId: organization.id,
|
||||||
@ -426,7 +439,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
if (!organization) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
|
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
@ -700,7 +713,11 @@ export const ldapConfigServiceFactory = ({
|
|||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
|
if (!ldapConfig) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find organization LDAP data with ID '${ldapConfigId}' in organization with ID ${orgId}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const groupMaps = await ldapGroupMapDAL.findLdapGroupMapsByLdapConfigId(ldapConfigId);
|
const groupMaps = await ldapGroupMapDAL.findLdapGroupMapsByLdapConfigId(ldapConfigId);
|
||||||
|
|
||||||
@ -747,7 +764,11 @@ export const ldapConfigServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const group = await groupDAL.findOne({ slug: groupSlug, orgId });
|
const group = await groupDAL.findOne({ slug: groupSlug, orgId });
|
||||||
if (!group) throw new NotFoundError({ message: "Failed to find group" });
|
if (!group) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find group with slug '${groupSlug}' in organization with ID '${orgId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const groupMap = await ldapGroupMapDAL.create({
|
const groupMap = await ldapGroupMapDAL.create({
|
||||||
ldapConfigId,
|
ldapConfigId,
|
||||||
@ -781,7 +802,11 @@ export const ldapConfigServiceFactory = ({
|
|||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
|
if (!ldapConfig) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find organization LDAP data with ID '${ldapConfigId}' in organization with ID ${orgId}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const [deletedGroupMap] = await ldapGroupMapDAL.delete({
|
const [deletedGroupMap] = await ldapGroupMapDAL.delete({
|
||||||
ldapConfigId: ldapConfig.id,
|
ldapConfigId: ldapConfig.id,
|
||||||
|
@ -46,7 +46,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
writeLimit: 200,
|
writeLimit: 200,
|
||||||
secretsLimit: 40
|
secretsLimit: 40
|
||||||
},
|
},
|
||||||
pkiEst: false
|
pkiEst: false,
|
||||||
|
enforceMfa: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
export const setupLicenseRequestWithStore = (baseURL: string, refreshUrl: string, licenseKey: string) => {
|
||||||
|
@ -129,7 +129,7 @@ export const licenseServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this means this is self hosted oss version
|
// this means this is the self-hosted oss version
|
||||||
// else it would reach catch statement
|
// else it would reach catch statement
|
||||||
isValidLicense = true;
|
isValidLicense = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -145,7 +145,7 @@ export const licenseServiceFactory = ({
|
|||||||
if (cachedPlan) return JSON.parse(cachedPlan) as TFeatureSet;
|
if (cachedPlan) return JSON.parse(cachedPlan) as TFeatureSet;
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
if (!org) throw new NotFoundError({ message: "Organization not found" });
|
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
const {
|
const {
|
||||||
data: { currentPlan }
|
data: { currentPlan }
|
||||||
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
|
} = await licenseServerCloudApi.request.get<{ currentPlan: TFeatureSet }>(
|
||||||
@ -204,7 +204,7 @@ export const licenseServiceFactory = ({
|
|||||||
const updateSubscriptionOrgMemberCount = async (orgId: string, tx?: Knex) => {
|
const updateSubscriptionOrgMemberCount = async (orgId: string, tx?: Knex) => {
|
||||||
if (instanceType === InstanceType.Cloud) {
|
if (instanceType === InstanceType.Cloud) {
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
if (!org) throw new NotFoundError({ message: "Organization not found" });
|
if (!org) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
|
|
||||||
const quantity = await licenseDAL.countOfOrgMembers(orgId, tx);
|
const quantity = await licenseDAL.countOfOrgMembers(orgId, tx);
|
||||||
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(orgId, tx);
|
const quantityIdentities = await licenseDAL.countOrgUsersAndIdentities(orgId, tx);
|
||||||
@ -267,7 +267,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +341,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.get(
|
const { data } = await licenseServerCloudApi.request.get(
|
||||||
@ -358,7 +358,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.get(
|
const { data } = await licenseServerCloudApi.request.get(
|
||||||
@ -374,7 +374,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +399,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.patch(
|
const { data } = await licenseServerCloudApi.request.patch(
|
||||||
@ -419,7 +419,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,7 +446,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
@ -475,7 +475,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,7 +492,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
@ -510,7 +510,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,7 +531,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,7 +548,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,7 +565,7 @@ export const licenseServiceFactory = ({
|
|||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with ID '${orgId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ export type TFeatureSet = {
|
|||||||
secretsLimit: number;
|
secretsLimit: number;
|
||||||
};
|
};
|
||||||
pkiEst: boolean;
|
pkiEst: boolean;
|
||||||
|
enforceMfa: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TOrgPlansTableDTO = {
|
export type TOrgPlansTableDTO = {
|
||||||
|
@ -79,7 +79,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found",
|
message: `Organization with slug '${dto.orgSlug}' not found`,
|
||||||
name: "OrgNotFound"
|
name: "OrgNotFound"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -100,14 +100,17 @@ export const oidcConfigServiceFactory = ({
|
|||||||
|
|
||||||
if (!oidcCfg) {
|
if (!oidcCfg) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization OIDC configuration"
|
message: `OIDC configuration for organization with slug '${dto.orgSlug}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// decrypt and return cfg
|
// decrypt and return cfg
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId: oidcCfg.orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId: oidcCfg.orgId });
|
||||||
if (!orgBot) {
|
if (!orgBot) {
|
||||||
throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
throw new NotFoundError({
|
||||||
|
message: `Organization bot for organization with ID '${oidcCfg.orgId}' not found`,
|
||||||
|
name: "OrgBotNotFound"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
@ -174,7 +177,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
if (!organization) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
|
|
||||||
let user: TUsers;
|
let user: TUsers;
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
@ -366,7 +369,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with slug '${orgSlug}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -387,7 +390,11 @@ export const oidcConfigServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId: org.id });
|
const orgBot = await orgBotDAL.findOne({ orgId: org.id });
|
||||||
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
if (!orgBot)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization bot for organization with ID '${org.id}' not found`,
|
||||||
|
name: "OrgBotNotFound"
|
||||||
|
});
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@ -455,7 +462,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: `Organization with slug '${orgSlug}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,7 +568,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found."
|
message: `Organization with slug '${orgSlug}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,10 +64,10 @@ export const permissionServiceFactory = ({
|
|||||||
permissions as PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[]
|
permissions as PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[]
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
throw new NotFoundError({ name: "OrgRoleInvalid", message: "Organization role not found" });
|
throw new NotFoundError({ name: "OrgRoleInvalid", message: `Organization role '${role}' not found` });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((curr, prev) => prev.concat(curr), []);
|
.reduce((prev, curr) => prev.concat(curr), []);
|
||||||
|
|
||||||
return createMongoAbility<OrgPermissionSet>(rules, {
|
return createMongoAbility<OrgPermissionSet>(rules, {
|
||||||
conditionsMatcher
|
conditionsMatcher
|
||||||
@ -94,11 +94,11 @@ export const permissionServiceFactory = ({
|
|||||||
default:
|
default:
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
name: "ProjectRoleInvalid",
|
name: "ProjectRoleInvalid",
|
||||||
message: "Project role not found"
|
message: `Project role '${role}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((curr, prev) => prev.concat(curr), []);
|
.reduce((prev, curr) => prev.concat(curr), []);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
@ -145,7 +145,7 @@ export const permissionServiceFactory = ({
|
|||||||
const membership = await permissionDAL.getOrgIdentityPermission(identityId, orgId);
|
const membership = await permissionDAL.getOrgIdentityPermission(identityId, orgId);
|
||||||
if (!membership) throw new ForbiddenRequestError({ name: "Identity is not apart of this organization" });
|
if (!membership) throw new ForbiddenRequestError({ name: "Identity is not apart of this organization" });
|
||||||
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
|
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
|
||||||
throw new NotFoundError({ name: "Custom organization permission not found" });
|
throw new NotFoundError({ name: `Custom organization permission not found for identity ${identityId}` });
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
permission: buildOrgPermission([{ role: membership.role, permissions: membership.permissions }]),
|
permission: buildOrgPermission([{ role: membership.role, permissions: membership.permissions }]),
|
||||||
@ -179,7 +179,10 @@ export const permissionServiceFactory = ({
|
|||||||
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
|
const isCustomRole = !Object.values(OrgMembershipRole).includes(role as OrgMembershipRole);
|
||||||
if (isCustomRole) {
|
if (isCustomRole) {
|
||||||
const orgRole = await orgRoleDAL.findOne({ slug: role, orgId });
|
const orgRole = await orgRoleDAL.findOne({ slug: role, orgId });
|
||||||
if (!orgRole) throw new NotFoundError({ message: "Specified role was not found" });
|
if (!orgRole)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Specified role '${role}' was not found in the organization with ID '${orgId}'`
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
permission: buildOrgPermission([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]),
|
permission: buildOrgPermission([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]),
|
||||||
role: orgRole
|
role: orgRole
|
||||||
@ -264,7 +267,9 @@ export const permissionServiceFactory = ({
|
|||||||
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
if (!identityProjectPermission)
|
if (!identityProjectPermission)
|
||||||
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified project" });
|
throw new ForbiddenRequestError({
|
||||||
|
name: `Identity is not a member of the specified project with ID '${projectId}'`
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
identityProjectPermission.roles.some(
|
identityProjectPermission.roles.some(
|
||||||
@ -326,7 +331,7 @@ export const permissionServiceFactory = ({
|
|||||||
actorOrgId: string | undefined
|
actorOrgId: string | undefined
|
||||||
) => {
|
) => {
|
||||||
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
const serviceToken = await serviceTokenDAL.findById(serviceTokenId);
|
||||||
if (!serviceToken) throw new NotFoundError({ message: "Service token not found" });
|
if (!serviceToken) throw new NotFoundError({ message: `Service token with ID '${serviceTokenId}' not found` });
|
||||||
|
|
||||||
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
|
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
|
||||||
|
|
||||||
@ -337,11 +342,15 @@ export const permissionServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (serviceToken.projectId !== projectId) {
|
if (serviceToken.projectId !== projectId) {
|
||||||
throw new ForbiddenRequestError({ name: "Service token not a part of the specified project" });
|
throw new ForbiddenRequestError({
|
||||||
|
name: `Service token not a part of the specified project with ID ${projectId}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceTokenProject.orgId !== actorOrgId) {
|
if (serviceTokenProject.orgId !== actorOrgId) {
|
||||||
throw new ForbiddenRequestError({ message: "Service token not a part of the specified organization" });
|
throw new ForbiddenRequestError({
|
||||||
|
message: `Service token not a part of the specified organization with ID ${actorOrgId}`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||||
|
@ -11,8 +11,8 @@ export enum PermissionConditionOperators {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const PermissionConditionSchema = {
|
export const PermissionConditionSchema = {
|
||||||
[PermissionConditionOperators.$IN]: z.string().min(1).array(),
|
[PermissionConditionOperators.$IN]: z.string().trim().min(1).array(),
|
||||||
[PermissionConditionOperators.$ALL]: z.string().min(1).array(),
|
[PermissionConditionOperators.$ALL]: z.string().trim().min(1).array(),
|
||||||
[PermissionConditionOperators.$REGEX]: z
|
[PermissionConditionOperators.$REGEX]: z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
|
|
||||||
import { PermissionConditionOperators, PermissionConditionSchema } from "./permission-types";
|
import { PermissionConditionOperators, PermissionConditionSchema } from "./permission-types";
|
||||||
|
|
||||||
@ -23,6 +22,14 @@ export enum ProjectPermissionCmekActions {
|
|||||||
Decrypt = "decrypt"
|
Decrypt = "decrypt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionDynamicSecretActions {
|
||||||
|
ReadRootCredential = "read-root-credential",
|
||||||
|
CreateRootCredential = "create-root-credential",
|
||||||
|
EditRootCredential = "edit-root-credential",
|
||||||
|
DeleteRootCredential = "delete-root-credential",
|
||||||
|
Lease = "lease"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSub {
|
export enum ProjectPermissionSub {
|
||||||
Role = "role",
|
Role = "role",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
@ -38,6 +45,8 @@ export enum ProjectPermissionSub {
|
|||||||
Project = "workspace",
|
Project = "workspace",
|
||||||
Secrets = "secrets",
|
Secrets = "secrets",
|
||||||
SecretFolders = "secret-folders",
|
SecretFolders = "secret-folders",
|
||||||
|
SecretImports = "secret-imports",
|
||||||
|
DynamicSecrets = "dynamic-secrets",
|
||||||
SecretRollback = "secret-rollback",
|
SecretRollback = "secret-rollback",
|
||||||
SecretApproval = "secret-approval",
|
SecretApproval = "secret-approval",
|
||||||
SecretRotation = "secret-rotation",
|
SecretRotation = "secret-rotation",
|
||||||
@ -54,19 +63,8 @@ export enum ProjectPermissionSub {
|
|||||||
export type SecretSubjectFields = {
|
export type SecretSubjectFields = {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
// secretName: string;
|
secretName?: string;
|
||||||
// secretTags: string[];
|
secretTags?: string[];
|
||||||
};
|
|
||||||
|
|
||||||
export const CaslSecretsV2SubjectKnexMapper = (field: string) => {
|
|
||||||
switch (field) {
|
|
||||||
case "secretName":
|
|
||||||
return `${TableName.SecretV2}.key`;
|
|
||||||
case "secretTags":
|
|
||||||
return `${TableName.SecretTag}.slug`;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SecretFolderSubjectFields = {
|
export type SecretFolderSubjectFields = {
|
||||||
@ -74,6 +72,16 @@ export type SecretFolderSubjectFields = {
|
|||||||
secretPath: string;
|
secretPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DynamicSecretSubjectFields = {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SecretImportSubjectFields = {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
@ -86,6 +94,20 @@ export type ProjectPermissionSet =
|
|||||||
| (ForcedSubject<ProjectPermissionSub.SecretFolders> & SecretFolderSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.SecretFolders> & SecretFolderSubjectFields)
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
| [
|
||||||
|
ProjectPermissionDynamicSecretActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.DynamicSecrets
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.DynamicSecrets> & DynamicSecretSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
| [
|
||||||
|
ProjectPermissionActions,
|
||||||
|
(
|
||||||
|
| ProjectPermissionSub.SecretImports
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.SecretImports> & SecretImportSubjectFields)
|
||||||
|
)
|
||||||
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
||||||
@ -120,7 +142,9 @@ const CASL_ACTION_SCHEMA_NATIVE_ENUM = <ACTION extends z.EnumLike>(actions: ACTI
|
|||||||
const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
|
const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
|
||||||
z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
|
z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
|
||||||
|
|
||||||
const SecretConditionSchema = z
|
// akhilmhdh: don't modify this for v2
|
||||||
|
// if you want to update create a new schema
|
||||||
|
const SecretConditionV1Schema = z
|
||||||
.object({
|
.object({
|
||||||
environment: z.union([
|
environment: z.union([
|
||||||
z.string(),
|
z.string(),
|
||||||
@ -146,16 +170,50 @@ const SecretConditionSchema = z
|
|||||||
})
|
})
|
||||||
.partial();
|
.partial();
|
||||||
|
|
||||||
export const ProjectPermissionSchema = z.discriminatedUnion("subject", [
|
const SecretConditionV2Schema = z
|
||||||
z.object({
|
.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
environment: z.union([
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
z.string(),
|
||||||
"Describe what action an entity can take."
|
z
|
||||||
),
|
.object({
|
||||||
conditions: SecretConditionSchema.describe(
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
"When specified, only matching conditions will be allowed to access given resource."
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
).optional()
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
}),
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
|
secretPath: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
|
secretName: z.union([
|
||||||
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
]),
|
||||||
|
secretTags: z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
|
})
|
||||||
|
.partial()
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
|
const GeneralPermissionSchema = [
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
@ -259,7 +317,7 @@ export const ProjectPermissionSchema = z.discriminatedUnion("subject", [
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to. "),
|
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
@ -288,26 +346,90 @@ export const ProjectPermissionSchema = z.discriminatedUnion("subject", [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
z.object({
|
|
||||||
subject: z.literal(ProjectPermissionSub.SecretFolders).describe("The entity this permission pertains to."),
|
|
||||||
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read]).describe(
|
|
||||||
"Describe what action an entity can take."
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
|
||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ProjectPermissionV1Schema = z.discriminatedUnion("subject", [
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: SecretConditionV1Schema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.SecretFolders).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read]).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
...GeneralPermissionSchema
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const ProjectPermissionV2Schema = z.discriminatedUnion("subject", [
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: SecretConditionV2Schema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.SecretFolders).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: SecretConditionV1Schema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.SecretImports).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: SecretConditionV1Schema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.DynamicSecrets).describe("The entity this permission pertains to."),
|
||||||
|
inverted: z.boolean().optional().describe("Whether rule allows or forbids."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionDynamicSecretActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
),
|
||||||
|
conditions: SecretConditionV1Schema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
|
).optional()
|
||||||
|
}),
|
||||||
|
...GeneralPermissionSchema
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type TProjectPermissionV2Schema = z.infer<typeof ProjectPermissionV2Schema>;
|
||||||
|
|
||||||
const buildAdminPermissionRules = () => {
|
const buildAdminPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
// Admins get full access to everything
|
// Admins get full access to everything
|
||||||
[
|
[
|
||||||
ProjectPermissionSub.Secrets,
|
ProjectPermissionSub.Secrets,
|
||||||
|
ProjectPermissionSub.SecretFolders,
|
||||||
|
ProjectPermissionSub.SecretImports,
|
||||||
ProjectPermissionSub.SecretApproval,
|
ProjectPermissionSub.SecretApproval,
|
||||||
ProjectPermissionSub.SecretRotation,
|
ProjectPermissionSub.SecretRotation,
|
||||||
ProjectPermissionSub.Member,
|
ProjectPermissionSub.Member,
|
||||||
@ -339,6 +461,17 @@ const buildAdminPermissionRules = () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.DynamicSecrets
|
||||||
|
);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
|
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
|
||||||
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
||||||
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
|
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
|
||||||
@ -370,6 +503,34 @@ const buildMemberPermissionRules = () => {
|
|||||||
],
|
],
|
||||||
ProjectPermissionSub.Secrets
|
ProjectPermissionSub.Secrets
|
||||||
);
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretFolders
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionDynamicSecretActions.ReadRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.EditRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.CreateRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.DeleteRootCredential,
|
||||||
|
ProjectPermissionDynamicSecretActions.Lease
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.DynamicSecrets
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.SecretImports
|
||||||
|
);
|
||||||
|
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
|
||||||
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretRotation);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretRotation);
|
||||||
@ -493,6 +654,9 @@ const buildViewerPermissionRules = () => {
|
|||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretFolders);
|
||||||
|
can(ProjectPermissionDynamicSecretActions.ReadRootCredential, ProjectPermissionSub.DynamicSecrets);
|
||||||
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretImports);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
||||||
@ -530,31 +694,35 @@ export const buildServiceTokenProjectPermission = (
|
|||||||
const canRead = permission.includes("read");
|
const canRead = permission.includes("read");
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, build } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
scopes.forEach(({ secretPath, environment }) => {
|
scopes.forEach(({ secretPath, environment }) => {
|
||||||
if (canWrite) {
|
[ProjectPermissionSub.Secrets, ProjectPermissionSub.SecretImports, ProjectPermissionSub.SecretFolders].forEach(
|
||||||
// TODO: @Akhi
|
(subject) => {
|
||||||
// @ts-expect-error type
|
if (canWrite) {
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets, {
|
// TODO: @Akhi
|
||||||
secretPath: { $glob: secretPath },
|
// @ts-expect-error type
|
||||||
environment
|
can(ProjectPermissionActions.Edit, subject, {
|
||||||
});
|
secretPath: { $glob: secretPath },
|
||||||
// @ts-expect-error type
|
environment
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets, {
|
});
|
||||||
secretPath: { $glob: secretPath },
|
// @ts-expect-error type
|
||||||
environment
|
can(ProjectPermissionActions.Create, subject, {
|
||||||
});
|
secretPath: { $glob: secretPath },
|
||||||
// @ts-expect-error type
|
environment
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets, {
|
});
|
||||||
secretPath: { $glob: secretPath },
|
// @ts-expect-error type
|
||||||
environment
|
can(ProjectPermissionActions.Delete, subject, {
|
||||||
});
|
secretPath: { $glob: secretPath },
|
||||||
}
|
environment
|
||||||
if (canRead) {
|
});
|
||||||
// @ts-expect-error type
|
}
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets, {
|
if (canRead) {
|
||||||
secretPath: { $glob: secretPath },
|
// @ts-expect-error type
|
||||||
environment
|
can(ProjectPermissionActions.Read, subject, {
|
||||||
});
|
secretPath: { $glob: secretPath },
|
||||||
}
|
environment
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return build({ conditionsMatcher });
|
return build({ conditionsMatcher });
|
||||||
@ -595,17 +763,63 @@ export const isAtLeastAsPrivilegedWorkspace = (
|
|||||||
};
|
};
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
export const SecretV2SubjectFieldMapper = (arg: string) => {
|
export const backfillPermissionV1SchemaToV2Schema = (
|
||||||
switch (arg) {
|
data: z.infer<typeof ProjectPermissionV1Schema>[],
|
||||||
case "environment":
|
dontRemoveReadFolderPermission?: boolean
|
||||||
return null;
|
) => {
|
||||||
case "secretPath":
|
let formattedData = UnpackedPermissionSchema.array().parse(data);
|
||||||
return null;
|
const secretSubjects = formattedData.filter((el) => el.subject === ProjectPermissionSub.Secrets);
|
||||||
case "secretName":
|
|
||||||
return `${TableName.SecretV2}.key`;
|
// this means the folder permission as readonly is set
|
||||||
case "secretTags":
|
const hasReadOnlyFolder = formattedData.filter((el) => el.subject === ProjectPermissionSub.SecretFolders);
|
||||||
return `${TableName.SecretTag}.slug`;
|
const secretImportPolicies = secretSubjects.map(({ subject, ...el }) => ({
|
||||||
default:
|
...el,
|
||||||
throw new BadRequestError({ message: `Invalid dynamic knex operator field: ${arg}` });
|
subject: ProjectPermissionSub.SecretImports as const
|
||||||
|
}));
|
||||||
|
|
||||||
|
const secretFolderPolicies = secretSubjects
|
||||||
|
.map(({ subject, ...el }) => ({
|
||||||
|
...el,
|
||||||
|
// read permission is not needed anymore
|
||||||
|
action: el.action.filter((caslAction) => caslAction !== ProjectPermissionActions.Read),
|
||||||
|
subject: ProjectPermissionSub.SecretFolders
|
||||||
|
}))
|
||||||
|
.filter((el) => el.action?.length > 0);
|
||||||
|
|
||||||
|
const dynamicSecretPolicies = secretSubjects.map(({ subject, ...el }) => {
|
||||||
|
const action = el.action.map((e) => {
|
||||||
|
switch (e) {
|
||||||
|
case ProjectPermissionActions.Edit:
|
||||||
|
return ProjectPermissionDynamicSecretActions.EditRootCredential;
|
||||||
|
case ProjectPermissionActions.Create:
|
||||||
|
return ProjectPermissionDynamicSecretActions.CreateRootCredential;
|
||||||
|
case ProjectPermissionActions.Delete:
|
||||||
|
return ProjectPermissionDynamicSecretActions.DeleteRootCredential;
|
||||||
|
case ProjectPermissionActions.Read:
|
||||||
|
return ProjectPermissionDynamicSecretActions.ReadRootCredential;
|
||||||
|
default:
|
||||||
|
return ProjectPermissionDynamicSecretActions.ReadRootCredential;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...el,
|
||||||
|
action: el.action.includes(ProjectPermissionActions.Edit)
|
||||||
|
? [...action, ProjectPermissionDynamicSecretActions.Lease]
|
||||||
|
: action,
|
||||||
|
subject: ProjectPermissionSub.DynamicSecrets
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dontRemoveReadFolderPermission) {
|
||||||
|
formattedData = formattedData.filter((i) => i.subject !== ProjectPermissionSub.SecretFolders);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return formattedData.concat(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore-error this is valid ts
|
||||||
|
secretImportPolicies,
|
||||||
|
dynamicSecretPolicies,
|
||||||
|
hasReadOnlyFolder.length ? [] : secretFolderPolicies
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { UnpackedPermissionSchema } from "@app/server/routes/santizedSchemas/permission";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSet, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "./project-user-additional-privilege-dal";
|
||||||
import {
|
import {
|
||||||
ProjectUserAdditionalPrivilegeTemporaryMode,
|
ProjectUserAdditionalPrivilegeTemporaryMode,
|
||||||
@ -26,6 +31,11 @@ export type TProjectUserAdditionalPrivilegeServiceFactory = ReturnType<
|
|||||||
typeof projectUserAdditionalPrivilegeServiceFactory
|
typeof projectUserAdditionalPrivilegeServiceFactory
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
const unpackPermissions = (permissions: unknown) =>
|
||||||
|
UnpackedPermissionSchema.array().parse(
|
||||||
|
unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility<ProjectPermissionSet>>>[])
|
||||||
|
);
|
||||||
|
|
||||||
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
export const projectUserAdditionalPrivilegeServiceFactory = ({
|
||||||
projectUserAdditionalPrivilegeDAL,
|
projectUserAdditionalPrivilegeDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
@ -42,7 +52,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
...dto
|
...dto
|
||||||
}: TCreateUserPrivilegeDTO) => {
|
}: TCreateUserPrivilegeDTO) => {
|
||||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
if (!projectMembership)
|
||||||
|
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -52,22 +63,41 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.USER,
|
||||||
|
projectMembership.userId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
targetUserPermission.update(targetUserPermission.rules.concat(customPermission));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetUserPermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||||
slug,
|
slug,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
userId: projectMembership.userId
|
userId: projectMembership.userId
|
||||||
});
|
});
|
||||||
if (existingSlug) throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
if (existingSlug)
|
||||||
|
throw new BadRequestError({ message: `Additional privilege with provided slug ${slug} already exists` });
|
||||||
|
|
||||||
|
const packedPermission = JSON.stringify(packRules(customPermission));
|
||||||
if (!dto.isTemporary) {
|
if (!dto.isTemporary) {
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.create({
|
||||||
userId: projectMembership.userId,
|
userId: projectMembership.userId,
|
||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission
|
permissions: packedPermission
|
||||||
});
|
});
|
||||||
return additionalPrivilege;
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
const relativeTempAllocatedTimeInMs = ms(dto.temporaryRange);
|
||||||
@ -75,14 +105,17 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
projectId: projectMembership.projectId,
|
projectId: projectMembership.projectId,
|
||||||
userId: projectMembership.userId,
|
userId: projectMembership.userId,
|
||||||
slug,
|
slug,
|
||||||
permissions: customPermission,
|
permissions: packedPermission,
|
||||||
isTemporary: true,
|
isTemporary: true,
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
temporaryRange: dto.temporaryRange,
|
temporaryRange: dto.temporaryRange,
|
||||||
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
temporaryAccessStartTime: new Date(dto.temporaryAccessStartTime),
|
||||||
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
temporaryAccessEndTime: new Date(new Date(dto.temporaryAccessStartTime).getTime() + relativeTempAllocatedTimeInMs)
|
||||||
});
|
});
|
||||||
return additionalPrivilege;
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateById = async ({
|
const updateById = async ({
|
||||||
@ -94,14 +127,18 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
...dto
|
...dto
|
||||||
}: TUpdateUserPrivilegeDTO) => {
|
}: TUpdateUserPrivilegeDTO) => {
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege not found" });
|
if (!userPrivilege)
|
||||||
|
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||||
|
|
||||||
const projectMembership = await projectMembershipDAL.findOne({
|
const projectMembership = await projectMembershipDAL.findOne({
|
||||||
userId: userPrivilege.userId,
|
userId: userPrivilege.userId,
|
||||||
projectId: userPrivilege.projectId
|
projectId: userPrivilege.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
if (!projectMembership)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -111,6 +148,20 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
const { permission: targetUserPermission } = await permissionService.getProjectPermission(
|
||||||
|
ActorType.USER,
|
||||||
|
projectMembership.userId,
|
||||||
|
projectMembership.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
targetUserPermission.update(targetUserPermission.rules.concat(dto.permissions || []));
|
||||||
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, targetUserPermission);
|
||||||
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to update more privileged identity" });
|
||||||
|
|
||||||
if (dto?.slug) {
|
if (dto?.slug) {
|
||||||
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await projectUserAdditionalPrivilegeDAL.findOne({
|
||||||
@ -119,41 +170,59 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
projectId: projectMembership.projectId
|
projectId: projectMembership.projectId
|
||||||
});
|
});
|
||||||
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
if (existingSlug && existingSlug.id !== userPrivilege.id)
|
||||||
throw new BadRequestError({ message: "Additional privilege of provided slug exist" });
|
throw new BadRequestError({ message: `Additional privilege with provided slug ${dto.slug} already exists` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
const isTemporary = typeof dto?.isTemporary !== "undefined" ? dto.isTemporary : userPrivilege.isTemporary;
|
||||||
|
|
||||||
|
const packedPermission = dto.permissions && JSON.stringify(packRules(dto.permissions));
|
||||||
if (isTemporary) {
|
if (isTemporary) {
|
||||||
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
const temporaryAccessStartTime = dto?.temporaryAccessStartTime || userPrivilege?.temporaryAccessStartTime;
|
||||||
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
const temporaryRange = dto?.temporaryRange || userPrivilege?.temporaryRange;
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
...dto,
|
slug: dto.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
|
isTemporary: dto.isTemporary,
|
||||||
|
temporaryRange: dto.temporaryRange,
|
||||||
|
temporaryMode: dto.temporaryMode,
|
||||||
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
temporaryAccessStartTime: new Date(temporaryAccessStartTime || ""),
|
||||||
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
temporaryAccessEndTime: new Date(new Date(temporaryAccessStartTime || "").getTime() + ms(temporaryRange || ""))
|
||||||
});
|
});
|
||||||
return additionalPrivilege;
|
|
||||||
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
const additionalPrivilege = await projectUserAdditionalPrivilegeDAL.updateById(userPrivilege.id, {
|
||||||
...dto,
|
slug: dto.slug,
|
||||||
|
permissions: packedPermission,
|
||||||
isTemporary: false,
|
isTemporary: false,
|
||||||
temporaryAccessStartTime: null,
|
temporaryAccessStartTime: null,
|
||||||
temporaryAccessEndTime: null,
|
temporaryAccessEndTime: null,
|
||||||
temporaryRange: null,
|
temporaryRange: null,
|
||||||
temporaryMode: null
|
temporaryMode: null
|
||||||
});
|
});
|
||||||
return additionalPrivilege;
|
return {
|
||||||
|
...additionalPrivilege,
|
||||||
|
permissions: unpackPermissions(additionalPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
const deleteById = async ({ actorId, actor, actorOrgId, actorAuthMethod, privilegeId }: TDeleteUserPrivilegeDTO) => {
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege not found" });
|
if (!userPrivilege)
|
||||||
|
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||||
|
|
||||||
const projectMembership = await projectMembershipDAL.findOne({
|
const projectMembership = await projectMembershipDAL.findOne({
|
||||||
userId: userPrivilege.userId,
|
userId: userPrivilege.userId,
|
||||||
projectId: userPrivilege.projectId
|
projectId: userPrivilege.projectId
|
||||||
});
|
});
|
||||||
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
if (!projectMembership)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -165,7 +234,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
const deletedPrivilege = await projectUserAdditionalPrivilegeDAL.deleteById(userPrivilege.id);
|
||||||
return deletedPrivilege;
|
return {
|
||||||
|
...deletedPrivilege,
|
||||||
|
permissions: unpackPermissions(deletedPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPrivilegeDetailsById = async ({
|
const getPrivilegeDetailsById = async ({
|
||||||
@ -176,13 +248,17 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TGetUserPrivilegeDetailsDTO) => {
|
}: TGetUserPrivilegeDetailsDTO) => {
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege not found" });
|
if (!userPrivilege)
|
||||||
|
throw new NotFoundError({ message: `User additional privilege with ID ${privilegeId} not found` });
|
||||||
|
|
||||||
const projectMembership = await projectMembershipDAL.findOne({
|
const projectMembership = await projectMembershipDAL.findOne({
|
||||||
userId: userPrivilege.userId,
|
userId: userPrivilege.userId,
|
||||||
projectId: userPrivilege.projectId
|
projectId: userPrivilege.projectId
|
||||||
});
|
});
|
||||||
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
if (!projectMembership)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Project membership for user with ID '${userPrivilege.userId}' not found in project with ID '${userPrivilege.projectId}'`
|
||||||
|
});
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -193,7 +269,10 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
return userPrivilege;
|
return {
|
||||||
|
...userPrivilege,
|
||||||
|
permissions: unpackPermissions(userPrivilege.permissions)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const listPrivileges = async ({
|
const listPrivileges = async ({
|
||||||
@ -204,7 +283,8 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TListUserPrivilegesDTO) => {
|
}: TListUserPrivilegesDTO) => {
|
||||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
if (!projectMembership)
|
||||||
|
throw new NotFoundError({ message: `Project membership with ID ${projectMembershipId} not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -215,10 +295,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
||||||
|
|
||||||
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find({
|
const userPrivileges = await projectUserAdditionalPrivilegeDAL.find(
|
||||||
userId: projectMembership.userId,
|
{
|
||||||
projectId: projectMembership.projectId
|
userId: projectMembership.userId,
|
||||||
});
|
projectId: projectMembership.projectId
|
||||||
|
},
|
||||||
|
{ sort: [[`${TableName.ProjectUserAdditionalPrivilege}.slug` as "slug", "asc"]] }
|
||||||
|
);
|
||||||
return userPrivileges;
|
return userPrivileges;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { TProjectPermissionV2Schema } from "../permission/project-permission";
|
||||||
|
|
||||||
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
export enum ProjectUserAdditionalPrivilegeTemporaryMode {
|
||||||
Relative = "relative"
|
Relative = "relative"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TCreateUserPrivilegeDTO = (
|
export type TCreateUserPrivilegeDTO = (
|
||||||
| {
|
| {
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
projectMembershipId: string;
|
projectMembershipId: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: false;
|
isTemporary: false;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
projectMembershipId: string;
|
projectMembershipId: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: true;
|
isTemporary: true;
|
||||||
@ -25,7 +27,7 @@ export type TCreateUserPrivilegeDTO = (
|
|||||||
|
|
||||||
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
|
export type TUpdateUserPrivilegeDTO = { privilegeId: string } & Omit<TProjectPermission, "projectId"> &
|
||||||
Partial<{
|
Partial<{
|
||||||
permissions: unknown;
|
permissions: TProjectPermissionV2Schema[];
|
||||||
slug: string;
|
slug: string;
|
||||||
isTemporary: boolean;
|
isTemporary: boolean;
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative;
|
||||||
|
@ -191,7 +191,11 @@ export const samlConfigServiceFactory = ({
|
|||||||
|
|
||||||
const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null };
|
const updateQuery: TSamlConfigsUpdate = { authProvider, isActive, lastUsed: null };
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId });
|
||||||
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
if (!orgBot)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization bot not found for organization with ID '${orgId}'`,
|
||||||
|
name: "OrgBotNotFound"
|
||||||
|
});
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@ -257,7 +261,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
|
|
||||||
ssoConfig = await samlConfigDAL.findById(id);
|
ssoConfig = await samlConfigDAL.findById(id);
|
||||||
}
|
}
|
||||||
if (!ssoConfig) throw new NotFoundError({ message: "Failed to find organization SSO data" });
|
if (!ssoConfig) throw new NotFoundError({ message: `Failed to find SSO data` });
|
||||||
|
|
||||||
// when dto is type id means it's internally used
|
// when dto is type id means it's internally used
|
||||||
if (dto.type === "org") {
|
if (dto.type === "org") {
|
||||||
@ -283,7 +287,11 @@ export const samlConfigServiceFactory = ({
|
|||||||
} = ssoConfig;
|
} = ssoConfig;
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId: ssoConfig.orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId: ssoConfig.orgId });
|
||||||
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
if (!orgBot)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization bot not found in organization with ID '${ssoConfig.orgId}'`,
|
||||||
|
name: "OrgBotNotFound"
|
||||||
|
});
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@ -355,7 +363,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
if (!organization) throw new NotFoundError({ message: `Organization with ID '${orgId}' not found` });
|
||||||
|
|
||||||
let user: TUsers;
|
let user: TUsers;
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
|
@ -183,7 +183,7 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => {
|
const deleteScimToken = async ({ scimTokenId, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteScimTokenDTO) => {
|
||||||
let scimToken = await scimDAL.findById(scimTokenId);
|
let scimToken = await scimDAL.findById(scimTokenId);
|
||||||
if (!scimToken) throw new NotFoundError({ message: "Failed to find SCIM token to delete" });
|
if (!scimToken) throw new NotFoundError({ message: `SCIM token with ID '${scimTokenId}' not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -834,10 +834,12 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = await groupDAL.findAllGroupPossibleMembers({
|
const users = await groupDAL
|
||||||
orgId: group.orgId,
|
.findAllGroupPossibleMembers({
|
||||||
groupId: group.id
|
orgId: group.orgId,
|
||||||
});
|
groupId: group.id
|
||||||
|
})
|
||||||
|
.then((g) => g.members);
|
||||||
|
|
||||||
const orgMemberships = await orgDAL.findMembership({
|
const orgMemberships = await orgDAL.findMembership({
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
|
@ -14,7 +14,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const secretApprovalPolicyFindQuery = (
|
const secretApprovalPolicyFindQuery = (
|
||||||
tx: Knex,
|
tx: Knex,
|
||||||
filter: TFindFilter<TSecretApprovalPolicies>,
|
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
||||||
customFilter?: {
|
customFilter?: {
|
||||||
sapId?: string;
|
sapId?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@ -95,7 +95,10 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env) throw new NotFoundError({ message: "Environment not found" });
|
if (!env)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
||||||
|
});
|
||||||
|
|
||||||
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.create(
|
const doc = await secretApprovalPolicyDAL.create(
|
||||||
@ -178,7 +181,11 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
.filter(Boolean) as string[];
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||||
if (!secretApprovalPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
if (!secretApprovalPolicy) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Secret approval policy with ID '${secretPolicyId}' not found`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -271,7 +278,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TDeleteSapDTO) => {
|
}: TDeleteSapDTO) => {
|
||||||
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||||
if (!sapPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
if (!sapPolicy)
|
||||||
|
throw new NotFoundError({ message: `Secret approval policy with ID '${secretPolicyId}' not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@ -320,7 +328,11 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
const getSecretApprovalPolicy = async (projectId: string, environment: string, path: string) => {
|
const getSecretApprovalPolicy = async (projectId: string, environment: string, path: string) => {
|
||||||
const secretPath = removeTrailingSlash(path);
|
const secretPath = removeTrailingSlash(path);
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env) throw new NotFoundError({ message: "Environment not found" });
|
if (!env) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Environment with slug '${environment}' not found in project with ID ${projectId}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const policies = await secretApprovalPolicyDAL.find({ envId: env.id });
|
const policies = await secretApprovalPolicyDAL.find({ envId: env.id });
|
||||||
if (!policies.length) return;
|
if (!policies.length) return;
|
||||||
@ -344,17 +356,8 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
}: TGetBoardSapDTO) => {
|
}: TGetBoardSapDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
await permissionService.getProjectPermission(actor, actorId, projectId, actorAuthMethod, actorOrgId);
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { secretPath, environment })
|
|
||||||
);
|
|
||||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -369,7 +372,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
if (!sapPolicy) {
|
if (!sapPolicy) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Cannot find secret approval policy"
|
message: `Secret approval policy with ID '${sapId}' not found`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ import {
|
|||||||
fnSecretBulkDelete as fnSecretV2BridgeBulkDelete,
|
fnSecretBulkDelete as fnSecretV2BridgeBulkDelete,
|
||||||
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
||||||
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
||||||
getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge
|
getAllSecretReferences as getAllSecretReferencesV2Bridge
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
@ -204,7 +204,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
|
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(id);
|
||||||
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest)
|
||||||
|
throw new NotFoundError({ message: `Secret approval request with ID '${id}' not found` });
|
||||||
|
|
||||||
const { projectId } = secretApprovalRequest;
|
const { projectId } = secretApprovalRequest;
|
||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
@ -266,12 +267,13 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
: "",
|
: "",
|
||||||
secretComment: el.secretVersion.encryptedComment
|
secretComment: el.secretVersion.encryptedComment
|
||||||
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
? secretManagerDecryptor({ cipherTextBlob: el.secretVersion.encryptedComment }).toString()
|
||||||
: ""
|
: "",
|
||||||
|
tags: el.secretVersion.tags
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
if (!botKey) throw new NotFoundError({ message: "Project bot key not found" });
|
if (!botKey) throw new NotFoundError({ message: `Project bot key not found`, name: "BotKeyNotFound" }); // CLI depends on this error message. TODO(daniel): Make API check for name BotKeyNotFound instead of message
|
||||||
const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
const encrypedSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
secrets = encrypedSecrets.map((el) => ({
|
secrets = encrypedSecrets.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
@ -307,7 +309,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TReviewRequestDTO) => {
|
}: TReviewRequestDTO) => {
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||||
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) {
|
||||||
|
throw new NotFoundError({ message: `Secret approval request with ID '${approvalId}' not found` });
|
||||||
|
}
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -365,7 +369,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TStatusChangeDTO) => {
|
}: TStatusChangeDTO) => {
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||||
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) {
|
||||||
|
throw new NotFoundError({ message: `Secret approval request with ID '${approvalId}' not found` });
|
||||||
|
}
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -414,7 +420,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
bypassReason
|
bypassReason
|
||||||
}: TMergeSecretApprovalRequestDTO) => {
|
}: TMergeSecretApprovalRequestDTO) => {
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||||
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest)
|
||||||
|
throw new NotFoundError({ message: `Secret approval request with ID '${approvalId}' not found` });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@ -462,7 +469,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
||||||
secretApprovalRequest.id
|
secretApprovalRequest.id
|
||||||
);
|
);
|
||||||
if (!secretApprovalSecrets) throw new NotFoundError({ message: "No secrets found" });
|
if (!secretApprovalSecrets) {
|
||||||
|
throw new NotFoundError({ message: `No secrets found in secret change request with ID '${approvalId}'` });
|
||||||
|
}
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
@ -523,11 +532,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||||
key: el.key,
|
key: el.key,
|
||||||
references: el.encryptedValue
|
references: el.encryptedValue
|
||||||
? getAllNestedSecretReferencesV2Bridge(
|
? getAllSecretReferencesV2Bridge(
|
||||||
secretManagerDecryptor({
|
secretManagerDecryptor({
|
||||||
cipherTextBlob: el.encryptedValue
|
cipherTextBlob: el.encryptedValue
|
||||||
}).toString()
|
}).toString()
|
||||||
)
|
).nestedReferences
|
||||||
: [],
|
: [],
|
||||||
type: SecretType.Shared
|
type: SecretType.Shared
|
||||||
})),
|
})),
|
||||||
@ -547,11 +556,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
? {
|
? {
|
||||||
encryptedValue: el.encryptedValue as Buffer,
|
encryptedValue: el.encryptedValue as Buffer,
|
||||||
references: el.encryptedValue
|
references: el.encryptedValue
|
||||||
? getAllNestedSecretReferencesV2Bridge(
|
? getAllSecretReferencesV2Bridge(
|
||||||
secretManagerDecryptor({
|
secretManagerDecryptor({
|
||||||
cipherTextBlob: el.encryptedValue
|
cipherTextBlob: el.encryptedValue
|
||||||
}).toString()
|
}).toString()
|
||||||
)
|
).nestedReferences
|
||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
@ -563,7 +572,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
reminderNote: el.reminderNote,
|
reminderNote: el.reminderNote,
|
||||||
skipMultilineEncoding: el.skipMultilineEncoding,
|
skipMultilineEncoding: el.skipMultilineEncoding,
|
||||||
key: el.key,
|
key: el.key,
|
||||||
tagIds: el?.tags.map(({ id }) => id),
|
tags: el?.tags.map(({ id }) => id),
|
||||||
...encryptedValue
|
...encryptedValue
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -602,7 +611,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
if (!secretApprovalSecrets) throw new NotFoundError({ message: "No secrets found" });
|
if (!secretApprovalSecrets) {
|
||||||
|
throw new NotFoundError({ message: `No secrets found in secret change request with ID '${approvalId}'` });
|
||||||
|
}
|
||||||
|
|
||||||
const conflicts: Array<{ secretId: string; op: SecretOperations }> = [];
|
const conflicts: Array<{ secretId: string; op: SecretOperations }> = [];
|
||||||
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create);
|
let secretCreationCommits = secretApprovalSecrets.filter(({ op }) => op === SecretOperations.Create);
|
||||||
@ -610,10 +621,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
const { secsGroupedByBlindIndex: conflictGroupByBlindIndex } = await fnSecretBlindIndexCheckV2({
|
||||||
folderId,
|
folderId,
|
||||||
secretDAL,
|
secretDAL,
|
||||||
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
|
inputSecrets: secretCreationCommits.map(({ secretBlindIndex, secret }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Secret blind index not found"
|
message: `Secret blind index not found on secret with ID '${secret.id}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { secretBlindIndex };
|
return { secretBlindIndex };
|
||||||
@ -637,10 +648,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
userId: "",
|
userId: "",
|
||||||
inputSecrets: secretUpdationCommits
|
inputSecrets: secretUpdationCommits
|
||||||
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
||||||
.map(({ secretBlindIndex }) => {
|
.map(({ secretBlindIndex, secret }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Secret blind index not found"
|
message: `Secret blind index not found on secret with ID '${secret.id}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { secretBlindIndex };
|
return { secretBlindIndex };
|
||||||
@ -760,10 +771,10 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorId: "",
|
actorId: "",
|
||||||
secretDAL,
|
secretDAL,
|
||||||
secretQueueService,
|
secretQueueService,
|
||||||
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
|
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex, secret }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Secret blind index not found"
|
message: `Secret blind index not found on secret with ID '${secret.id}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { secretBlindIndex, type: SecretType.Shared };
|
return { secretBlindIndex, type: SecretType.Shared };
|
||||||
@ -789,7 +800,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
await snapshotService.performSnapshot(folderId);
|
await snapshotService.performSnapshot(folderId);
|
||||||
const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [folderId]);
|
const [folder] = await folderDAL.findSecretPathByFolderIds(projectId, [folderId]);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder) {
|
||||||
|
throw new NotFoundError({ message: `Folder with ID '${folderId}' not found in project with ID '${projectId}'` });
|
||||||
|
}
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
projectId,
|
projectId,
|
||||||
secretPath: folder.path,
|
secretPath: folder.path,
|
||||||
@ -861,14 +874,18 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Folder not found for the given environment slug & secret path",
|
message: `Folder not found for environment with slug '${environment}' & secret path '${secretPath}'`,
|
||||||
name: "GenSecretApproval"
|
name: "GenSecretApproval"
|
||||||
});
|
});
|
||||||
const folderId = folder.id;
|
const folderId = folder.id;
|
||||||
|
|
||||||
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId });
|
||||||
if (!blindIndexCfg) throw new NotFoundError({ message: "Blind index not found", name: "Update secret" });
|
if (!blindIndexCfg) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Blind index not found for project with ID '${projectId}'`,
|
||||||
|
name: "Update secret"
|
||||||
|
});
|
||||||
|
}
|
||||||
const commits: Omit<TSecretApprovalRequestsSecretsInsert, "requestId">[] = [];
|
const commits: Omit<TSecretApprovalRequestsSecretsInsert, "requestId">[] = [];
|
||||||
const commitTagIds: Record<string, string[]> = {};
|
const commitTagIds: Record<string, string[]> = {};
|
||||||
// for created secret approval change
|
// for created secret approval change
|
||||||
@ -961,7 +978,9 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretDAL
|
secretDAL
|
||||||
});
|
});
|
||||||
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
|
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
|
||||||
if (!i.secretBlindIndex) throw new NotFoundError({ message: "Secret blind index not found" });
|
if (!i.secretBlindIndex) {
|
||||||
|
throw new NotFoundError({ message: `Secret blind index not found for secret with ID '${i.id}'` });
|
||||||
|
}
|
||||||
return i.secretBlindIndex;
|
return i.secretBlindIndex;
|
||||||
});
|
});
|
||||||
const deletedSecretIds = deletedSecrets.map(
|
const deletedSecretIds = deletedSecrets.map(
|
||||||
@ -972,7 +991,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
...deletedSecrets.map((el) => {
|
...deletedSecrets.map((el) => {
|
||||||
const secretId = secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id;
|
const secretId = secretsGroupedByBlindIndex[keyName2BlindIndex[el.secretName]][0].id;
|
||||||
if (!latestSecretVersions[secretId].secretBlindIndex)
|
if (!latestSecretVersions[secretId].secretBlindIndex)
|
||||||
throw new NotFoundError({ message: "Secret blind index not found" });
|
throw new NotFoundError({ message: `Secret blind index not found for secret with ID '${secretId}'` });
|
||||||
return {
|
return {
|
||||||
op: SecretOperations.Delete as const,
|
op: SecretOperations.Delete as const,
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
@ -988,7 +1007,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const tagIds = unique(Object.values(commitTagIds).flat());
|
const tagIds = unique(Object.values(commitTagIds).flat());
|
||||||
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
|
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
|
||||||
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
|
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "One or more tags not found" });
|
||||||
|
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
|
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalRequestDAL.create(
|
const doc = await secretApprovalRequestDAL.create(
|
||||||
@ -1054,7 +1073,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => {
|
const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => {
|
||||||
if (!i.secretBlindIndex) {
|
if (!i.secretBlindIndex) {
|
||||||
throw new NotFoundError({ message: "Secret blind index not found" });
|
throw new NotFoundError({ message: `Secret blind index not found for secret with ID '${i.id}'` });
|
||||||
}
|
}
|
||||||
return i.secretBlindIndex;
|
return i.secretBlindIndex;
|
||||||
});
|
});
|
||||||
@ -1125,15 +1144,11 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
|
||||||
ProjectPermissionActions.Read,
|
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder)
|
if (!folder)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Folder not found for the given environment slug & secret path",
|
message: `Folder not found for the environment slug '${environment}' & secret path '${secretPath}'`,
|
||||||
name: "GenSecretApproval"
|
name: "GenSecretApproval"
|
||||||
});
|
});
|
||||||
const folderId = folder.id;
|
const folderId = folder.id;
|
||||||
@ -1292,6 +1307,23 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const tagIds = unique(Object.values(commitTagIds).flat());
|
const tagIds = unique(Object.values(commitTagIds).flat());
|
||||||
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
|
const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : [];
|
||||||
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
|
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "Tag not found" });
|
||||||
|
const tagsGroupById = groupBy(tags, (i) => i.id);
|
||||||
|
|
||||||
|
commits.forEach((commit) => {
|
||||||
|
let action = ProjectPermissionActions.Create;
|
||||||
|
if (commit.op === SecretOperations.Update) action = ProjectPermissionActions.Edit;
|
||||||
|
if (commit.op === SecretOperations.Delete) action = ProjectPermissionActions.Delete;
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
action,
|
||||||
|
subject(ProjectPermissionSub.Secrets, {
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
secretName: commit.key,
|
||||||
|
secretTags: commitTagIds?.[commit.key]?.map((secretTagId) => tagsGroupById[secretTagId][0].slug)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
|
const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalRequestDAL.create(
|
const doc = await secretApprovalRequestDAL.create(
|
||||||
|
@ -28,8 +28,7 @@ import { TSecretV2BridgeDALFactory } from "@app/services/secret-v2-bridge/secret
|
|||||||
import {
|
import {
|
||||||
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
fnSecretBulkInsert as fnSecretV2BridgeBulkInsert,
|
||||||
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
fnSecretBulkUpdate as fnSecretV2BridgeBulkUpdate,
|
||||||
getAllNestedSecretReferences,
|
getAllSecretReferences
|
||||||
getAllNestedSecretReferences as getAllNestedSecretReferencesV2Bridge
|
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
@ -253,11 +252,12 @@ export const secretReplicationServiceFactory = ({
|
|||||||
const sourceLocalSecrets = await secretV2BridgeDAL.find({ folderId: folder.id, type: SecretType.Shared });
|
const sourceLocalSecrets = await secretV2BridgeDAL.find({ folderId: folder.id, type: SecretType.Shared });
|
||||||
const sourceSecretImports = await secretImportDAL.find({ folderId: folder.id });
|
const sourceSecretImports = await secretImportDAL.find({ folderId: folder.id });
|
||||||
const sourceImportedSecrets = await fnSecretsV2FromImports({
|
const sourceImportedSecrets = await fnSecretsV2FromImports({
|
||||||
allowedImports: sourceSecretImports,
|
secretImports: sourceSecretImports,
|
||||||
secretDAL: secretV2BridgeDAL,
|
secretDAL: secretV2BridgeDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
secretImportDAL,
|
secretImportDAL,
|
||||||
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : "")
|
decryptor: (value) => (value ? secretManagerDecryptor({ cipherTextBlob: value }).toString() : ""),
|
||||||
|
hasSecretAccess: () => true
|
||||||
});
|
});
|
||||||
// secrets that gets replicated across imports
|
// secrets that gets replicated across imports
|
||||||
const sourceDecryptedLocalSecrets = sourceLocalSecrets.map((el) => ({
|
const sourceDecryptedLocalSecrets = sourceLocalSecrets.map((el) => ({
|
||||||
@ -295,7 +295,10 @@ export const secretReplicationServiceFactory = ({
|
|||||||
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
||||||
destinationSecretImport.folderId
|
destinationSecretImport.folderId
|
||||||
]);
|
]);
|
||||||
if (!destinationFolder) throw new NotFoundError({ message: "Imported folder not found" });
|
if (!destinationFolder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Imported folder with ID '${destinationSecretImport.folderId}' not found in project with ID ${projectId}`
|
||||||
|
});
|
||||||
|
|
||||||
let destinationReplicationFolder = await folderDAL.findOne({
|
let destinationReplicationFolder = await folderDAL.findOne({
|
||||||
parentId: destinationFolder.id,
|
parentId: destinationFolder.id,
|
||||||
@ -416,7 +419,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue,
|
encryptedValue: doc.encryptedValue,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: doc.secretValue ? getAllNestedSecretReferencesV2Bridge(doc.secretValue) : []
|
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -442,7 +445,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
encryptedValue: doc.encryptedValue as Buffer,
|
encryptedValue: doc.encryptedValue as Buffer,
|
||||||
encryptedComment: doc.encryptedComment,
|
encryptedComment: doc.encryptedComment,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: doc.secretValue ? getAllNestedSecretReferencesV2Bridge(doc.secretValue) : []
|
references: doc.secretValue ? getAllSecretReferences(doc.secretValue).nestedReferences : []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -506,7 +509,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
if (!botKey) throw new NotFoundError({ message: `Bot key not found for project with ID ${projectId}` });
|
||||||
// these are the secrets to be added in replicated folders
|
// these are the secrets to be added in replicated folders
|
||||||
const sourceLocalSecrets = await secretDAL.find({ folderId: folder.id, type: SecretType.Shared });
|
const sourceLocalSecrets = await secretDAL.find({ folderId: folder.id, type: SecretType.Shared });
|
||||||
const sourceSecretImports = await secretImportDAL.find({ folderId: folder.id });
|
const sourceSecretImports = await secretImportDAL.find({ folderId: folder.id });
|
||||||
@ -545,7 +548,11 @@ export const secretReplicationServiceFactory = ({
|
|||||||
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
||||||
destinationSecretImport.folderId
|
destinationSecretImport.folderId
|
||||||
]);
|
]);
|
||||||
if (!destinationFolder) throw new NotFoundError({ message: "Imported folder not found" });
|
if (!destinationFolder) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Imported folder with ID '${destinationSecretImport.folderId}' not found in project with ID ${projectId}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let destinationReplicationFolder = await folderDAL.findOne({
|
let destinationReplicationFolder = await folderDAL.findOne({
|
||||||
parentId: destinationFolder.id,
|
parentId: destinationFolder.id,
|
||||||
@ -687,7 +694,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretCommentTag: doc.secretCommentTag,
|
secretCommentTag: doc.secretCommentTag,
|
||||||
secretCommentCiphertext: doc.secretCommentCiphertext,
|
secretCommentCiphertext: doc.secretCommentCiphertext,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: getAllNestedSecretReferences(doc.secretValue)
|
references: getAllSecretReferences(doc.secretValue).nestedReferences
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -723,7 +730,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
secretCommentTag: doc.secretCommentTag,
|
secretCommentTag: doc.secretCommentTag,
|
||||||
secretCommentCiphertext: doc.secretCommentCiphertext,
|
secretCommentCiphertext: doc.secretCommentCiphertext,
|
||||||
skipMultilineEncoding: doc.skipMultilineEncoding,
|
skipMultilineEncoding: doc.skipMultilineEncoding,
|
||||||
references: getAllNestedSecretReferences(doc.secretValue)
|
references: getAllSecretReferences(doc.secretValue).nestedReferences
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -85,7 +85,8 @@ export const secretRotationDbFn = async ({
|
|||||||
password,
|
password,
|
||||||
username,
|
username,
|
||||||
client,
|
client,
|
||||||
variables
|
variables,
|
||||||
|
options
|
||||||
}: TSecretRotationDbFn) => {
|
}: TSecretRotationDbFn) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
@ -117,7 +118,8 @@ export const secretRotationDbFn = async ({
|
|||||||
password,
|
password,
|
||||||
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
connectionTimeoutMillis: EXTERNAL_REQUEST_TIMEOUT,
|
||||||
ssl,
|
ssl,
|
||||||
pool: { min: 0, max: 1 }
|
pool: { min: 0, max: 1 },
|
||||||
|
options
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const data = await db.raw(query, variables);
|
const data = await db.raw(query, variables);
|
||||||
@ -153,6 +155,14 @@ export const getDbSetQuery = (db: TDbProviderClients, variables: { username: str
|
|||||||
variables: [variables.username]
|
variables: [variables.username]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (db === TDbProviderClients.MsSqlServer) {
|
||||||
|
return {
|
||||||
|
query: `ALTER LOGIN ?? WITH PASSWORD = '${variables.password}'`,
|
||||||
|
variables: [variables.username]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// add more based on client
|
// add more based on client
|
||||||
return {
|
return {
|
||||||
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
|
query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`,
|
||||||
|
@ -24,4 +24,5 @@ export type TSecretRotationDbFn = {
|
|||||||
query: string;
|
query: string;
|
||||||
variables: unknown[];
|
variables: unknown[];
|
||||||
ca?: string;
|
ca?: string;
|
||||||
|
options?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
@ -94,7 +94,9 @@ export const secretRotationQueueFactory = ({
|
|||||||
// on prod it this will be in days, in development this will be second
|
// on prod it this will be in days, in development this will be second
|
||||||
every: appCfg.NODE_ENV === "development" ? secondsToMillis(interval) : daysToMillisecond(interval),
|
every: appCfg.NODE_ENV === "development" ? secondsToMillis(interval) : daysToMillisecond(interval),
|
||||||
immediately: true
|
immediately: true
|
||||||
}
|
},
|
||||||
|
removeOnComplete: true,
|
||||||
|
removeOnFail: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -114,6 +116,7 @@ export const secretRotationQueueFactory = ({
|
|||||||
|
|
||||||
queue.start(QueueName.SecretRotation, async (job) => {
|
queue.start(QueueName.SecretRotation, async (job) => {
|
||||||
const { rotationId } = job.data;
|
const { rotationId } = job.data;
|
||||||
|
const appCfg = getConfig();
|
||||||
logger.info(`secretRotationQueue.process: [rotationDocument=${rotationId}]`);
|
logger.info(`secretRotationQueue.process: [rotationDocument=${rotationId}]`);
|
||||||
const secretRotation = await secretRotationDAL.findById(rotationId);
|
const secretRotation = await secretRotationDAL.findById(rotationId);
|
||||||
const rotationProvider = rotationTemplates.find(({ name }) => name === secretRotation?.provider);
|
const rotationProvider = rotationTemplates.find(({ name }) => name === secretRotation?.provider);
|
||||||
@ -172,6 +175,15 @@ export const secretRotationQueueFactory = ({
|
|||||||
// set a random value for new password
|
// set a random value for new password
|
||||||
newCredential.internal.rotated_password = alphaNumericNanoId(32);
|
newCredential.internal.rotated_password = alphaNumericNanoId(32);
|
||||||
const { admin_username: username, admin_password: password, host, database, port, ca } = newCredential.inputs;
|
const { admin_username: username, admin_password: password, host, database, port, ca } = newCredential.inputs;
|
||||||
|
|
||||||
|
const options =
|
||||||
|
provider.template.client === TDbProviderClients.MsSqlServer
|
||||||
|
? ({
|
||||||
|
encrypt: appCfg.ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT,
|
||||||
|
cryptoCredentialsDetails: ca ? { ca } : {}
|
||||||
|
} as Record<string, unknown>)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const dbFunctionArg = {
|
const dbFunctionArg = {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
@ -179,8 +191,10 @@ export const secretRotationQueueFactory = ({
|
|||||||
database,
|
database,
|
||||||
port,
|
port,
|
||||||
ca: ca as string,
|
ca: ca as string,
|
||||||
client: provider.template.client === TDbProviderClients.MySql ? "mysql2" : provider.template.client
|
client: provider.template.client === TDbProviderClients.MySql ? "mysql2" : provider.template.client,
|
||||||
|
options
|
||||||
} as TSecretRotationDbFn;
|
} as TSecretRotationDbFn;
|
||||||
|
|
||||||
// set function
|
// set function
|
||||||
await secretRotationDbFn({
|
await secretRotationDbFn({
|
||||||
...dbFunctionArg,
|
...dbFunctionArg,
|
||||||
@ -189,12 +203,17 @@ export const secretRotationQueueFactory = ({
|
|||||||
username: newCredential.internal.username as string
|
username: newCredential.internal.username as string
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// test function
|
// test function
|
||||||
|
const testQuery =
|
||||||
|
provider.template.client === TDbProviderClients.MsSqlServer ? "SELECT GETDATE()" : "SELECT NOW()";
|
||||||
|
|
||||||
await secretRotationDbFn({
|
await secretRotationDbFn({
|
||||||
...dbFunctionArg,
|
...dbFunctionArg,
|
||||||
query: "SELECT NOW()",
|
query: testQuery,
|
||||||
variables: []
|
variables: []
|
||||||
});
|
});
|
||||||
|
|
||||||
newCredential.outputs.db_username = newCredential.internal.username;
|
newCredential.outputs.db_username = newCredential.internal.username;
|
||||||
newCredential.outputs.db_password = newCredential.internal.rotated_password;
|
newCredential.outputs.db_password = newCredential.internal.rotated_password;
|
||||||
// clean up
|
// clean up
|
||||||
@ -332,7 +351,10 @@ export const secretRotationQueueFactory = ({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
if (!botKey)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Project bot not found for project with ID '${secretRotation.projectId}'`
|
||||||
|
});
|
||||||
const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({
|
const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({
|
||||||
secretId,
|
secretId,
|
||||||
value: encryptSymmetric128BitHexKeyUTF8(
|
value: encryptSymmetric128BitHexKeyUTF8(
|
||||||
@ -372,7 +394,9 @@ export const secretRotationQueueFactory = ({
|
|||||||
);
|
);
|
||||||
await secretVersionDAL.insertMany(
|
await secretVersionDAL.insertMany(
|
||||||
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => {
|
updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => {
|
||||||
if (!el.secretBlindIndex) throw new NotFoundError({ message: "Secret blind index not found" });
|
if (!el.secretBlindIndex) {
|
||||||
|
throw new NotFoundError({ message: `Secret blind index not found on secret with ID '${id}` });
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...el,
|
...el,
|
||||||
secretId: id,
|
secretId: id,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
|
|
||||||
import { ProjectVersion } from "@app/db/schemas";
|
import { ProjectVersion, TableName } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
@ -94,28 +94,33 @@ export const secretRotationServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder) throw new NotFoundError({ message: "Secret path not found" });
|
if (!folder) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Secret path with path '${secretPath}' not found in environment with slug '${environment}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
);
|
);
|
||||||
|
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
const shouldUseBridge = project.version === ProjectVersion.V3;
|
const shouldUseBridge = project.version === ProjectVersion.V3;
|
||||||
|
|
||||||
if (shouldUseBridge) {
|
if (shouldUseBridge) {
|
||||||
const selectedSecrets = await secretV2BridgeDAL.find({
|
const selectedSecrets = await secretV2BridgeDAL.find({
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
$in: { id: Object.values(outputs) }
|
$in: { [`${TableName.SecretV2}.id` as "id"]: Object.values(outputs) }
|
||||||
});
|
});
|
||||||
if (selectedSecrets.length !== Object.values(outputs).length)
|
if (selectedSecrets.length !== Object.values(outputs).length)
|
||||||
throw new NotFoundError({ message: "Secrets not found" });
|
throw new NotFoundError({ message: `Secrets not found in folder with ID '${folder.id}'` });
|
||||||
} else {
|
} else {
|
||||||
const selectedSecrets = await secretDAL.find({
|
const selectedSecrets = await secretDAL.find({
|
||||||
folderId: folder.id,
|
folderId: folder.id,
|
||||||
$in: { id: Object.values(outputs) }
|
$in: { id: Object.values(outputs) }
|
||||||
});
|
});
|
||||||
if (selectedSecrets.length !== Object.values(outputs).length)
|
if (selectedSecrets.length !== Object.values(outputs).length)
|
||||||
throw new NotFoundError({ message: "Secrets not found" });
|
throw new NotFoundError({ message: `Secrets not found in folder with ID '${folder.id}'` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(project.orgId);
|
const plan = await licenseService.getPlan(project.orgId);
|
||||||
@ -125,7 +130,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
|
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
|
||||||
if (!selectedTemplate) throw new NotFoundError({ message: "Provider not found" });
|
if (!selectedTemplate) throw new NotFoundError({ message: `Provider with name '${provider}' not found` });
|
||||||
const formattedInputs: Record<string, unknown> = {};
|
const formattedInputs: Record<string, unknown> = {};
|
||||||
Object.entries(inputs).forEach(([key, value]) => {
|
Object.entries(inputs).forEach(([key, value]) => {
|
||||||
const { type } = selectedTemplate.template.inputs.properties[key];
|
const { type } = selectedTemplate.template.inputs.properties[key];
|
||||||
@ -198,7 +203,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
if (!botKey) throw new NotFoundError({ message: `Project bot not found for project with ID '${projectId}'` });
|
||||||
const docs = await secretRotationDAL.find({ projectId });
|
const docs = await secretRotationDAL.find({ projectId });
|
||||||
return docs.map((el) => ({
|
return docs.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
@ -220,7 +225,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
|
|
||||||
const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => {
|
const restartById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TRestartDTO) => {
|
||||||
const doc = await secretRotationDAL.findById(rotationId);
|
const doc = await secretRotationDAL.findById(rotationId);
|
||||||
if (!doc) throw new NotFoundError({ message: "Rotation not found" });
|
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
|
||||||
|
|
||||||
const project = await projectDAL.findById(doc.projectId);
|
const project = await projectDAL.findById(doc.projectId);
|
||||||
const plan = await licenseService.getPlan(project.orgId);
|
const plan = await licenseService.getPlan(project.orgId);
|
||||||
@ -244,7 +249,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
|
|
||||||
const deleteById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TDeleteDTO) => {
|
const deleteById = async ({ actor, actorId, actorOrgId, actorAuthMethod, rotationId }: TDeleteDTO) => {
|
||||||
const doc = await secretRotationDAL.findById(rotationId);
|
const doc = await secretRotationDAL.findById(rotationId);
|
||||||
if (!doc) throw new NotFoundError({ message: "Rotation not found" });
|
if (!doc) throw new NotFoundError({ message: `Rotation with ID '${rotationId}' not found` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { AWS_IAM_TEMPLATE } from "./aws-iam";
|
import { AWS_IAM_TEMPLATE } from "./aws-iam";
|
||||||
|
import { MSSQL_TEMPLATE } from "./mssql";
|
||||||
import { MYSQL_TEMPLATE } from "./mysql";
|
import { MYSQL_TEMPLATE } from "./mysql";
|
||||||
import { POSTGRES_TEMPLATE } from "./postgres";
|
import { POSTGRES_TEMPLATE } from "./postgres";
|
||||||
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
import { SENDGRID_TEMPLATE } from "./sendgrid";
|
||||||
@ -26,6 +27,13 @@ export const rotationTemplates: TSecretRotationProviderTemplate[] = [
|
|||||||
description: "Rotate MySQL@7/MariaDB user credentials",
|
description: "Rotate MySQL@7/MariaDB user credentials",
|
||||||
template: MYSQL_TEMPLATE
|
template: MYSQL_TEMPLATE
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "mssql",
|
||||||
|
title: "Microsoft SQL Server",
|
||||||
|
image: "mssqlserver.png",
|
||||||
|
description: "Rotate Microsoft SQL server user credentials",
|
||||||
|
template: MSSQL_TEMPLATE
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "aws-iam",
|
name: "aws-iam",
|
||||||
title: "AWS IAM",
|
title: "AWS IAM",
|
||||||
|
33
backend/src/ee/services/secret-rotation/templates/mssql.ts
Normal file
33
backend/src/ee/services/secret-rotation/templates/mssql.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { TDbProviderClients, TProviderFunctionTypes } from "./types";
|
||||||
|
|
||||||
|
export const MSSQL_TEMPLATE = {
|
||||||
|
type: TProviderFunctionTypes.DB as const,
|
||||||
|
client: TDbProviderClients.MsSqlServer,
|
||||||
|
inputs: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
admin_username: { type: "string" as const },
|
||||||
|
admin_password: { type: "string" as const },
|
||||||
|
host: { type: "string" as const },
|
||||||
|
database: { type: "string" as const, default: "master" },
|
||||||
|
port: { type: "integer" as const, default: "1433" },
|
||||||
|
username1: {
|
||||||
|
type: "string",
|
||||||
|
default: "infisical-sql-user1",
|
||||||
|
desc: "SQL Server login name that must be created at server level with a matching database user"
|
||||||
|
},
|
||||||
|
username2: {
|
||||||
|
type: "string",
|
||||||
|
default: "infisical-sql-user2",
|
||||||
|
desc: "SQL Server login name that must be created at server level with a matching database user"
|
||||||
|
},
|
||||||
|
ca: { type: "string", desc: "SSL certificate for db auth(string)" }
|
||||||
|
},
|
||||||
|
required: ["admin_username", "admin_password", "host", "database", "username1", "username2", "port"],
|
||||||
|
additionalProperties: false
|
||||||
|
},
|
||||||
|
outputs: {
|
||||||
|
db_username: { type: "string" },
|
||||||
|
db_password: { type: "string" }
|
||||||
|
}
|
||||||
|
};
|
@ -8,7 +8,9 @@ export enum TDbProviderClients {
|
|||||||
// postgres, cockroack db, amazon red shift
|
// postgres, cockroack db, amazon red shift
|
||||||
Pg = "pg",
|
Pg = "pg",
|
||||||
// mysql and maria db
|
// mysql and maria db
|
||||||
MySql = "mysql"
|
MySql = "mysql",
|
||||||
|
|
||||||
|
MsSqlServer = "mssql"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TAwsProviderSystems {
|
export enum TAwsProviderSystems {
|
||||||
|
@ -99,7 +99,11 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${path}' not found in environment with slug '${environment}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
return snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
||||||
};
|
};
|
||||||
@ -131,7 +135,10 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Folder with path '${path}' not found in environment with slug '${environment}'`
|
||||||
|
});
|
||||||
|
|
||||||
const snapshots = await snapshotDAL.find({ folderId: folder.id }, { limit, offset, sort: [["createdAt", "desc"]] });
|
const snapshots = await snapshotDAL.find({ folderId: folder.id }, { limit, offset, sort: [["createdAt", "desc"]] });
|
||||||
return snapshots;
|
return snapshots;
|
||||||
@ -139,7 +146,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
|
|
||||||
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
|
const getSnapshotData = async ({ actorId, actor, actorOrgId, actorAuthMethod, id }: TGetSnapshotDataDTO) => {
|
||||||
const snapshot = await snapshotDAL.findById(id);
|
const snapshot = await snapshotDAL.findById(id);
|
||||||
if (!snapshot) throw new NotFoundError({ message: "Snapshot not found" });
|
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${id}' not found` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@ -173,7 +180,8 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
} else {
|
} else {
|
||||||
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotDataById(id);
|
const encryptedSnapshotDetails = await snapshotDAL.findSecretSnapshotDataById(id);
|
||||||
const { botKey } = await projectBotService.getBotKey(snapshot.projectId);
|
const { botKey } = await projectBotService.getBotKey(snapshot.projectId);
|
||||||
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
if (!botKey)
|
||||||
|
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) => ({
|
||||||
@ -225,7 +233,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
try {
|
try {
|
||||||
if (!licenseService.isValidLicense) throw new InternalServerError({ message: "Invalid license" });
|
if (!licenseService.isValidLicense) throw new InternalServerError({ message: "Invalid license" });
|
||||||
const folder = await folderDAL.findById(folderId);
|
const folder = await folderDAL.findById(folderId);
|
||||||
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: `Folder with ID '${folderId}' not found` });
|
||||||
const shouldUseSecretV2Bridge = folder.projectVersion === 3;
|
const shouldUseSecretV2Bridge = folder.projectVersion === 3;
|
||||||
|
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@ -311,7 +319,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TRollbackSnapshotDTO) => {
|
}: TRollbackSnapshotDTO) => {
|
||||||
const snapshot = await snapshotDAL.findById(snapshotId);
|
const snapshot = await snapshotDAL.findById(snapshotId);
|
||||||
if (!snapshot) throw new NotFoundError({ message: "Snapshot not found" });
|
if (!snapshot) throw new NotFoundError({ message: `Snapshot with ID '${snapshotId}' not found` });
|
||||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
@ -29,7 +29,7 @@ export const KeyStorePrefixes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const KeyStoreTtls = {
|
export const KeyStoreTtls = {
|
||||||
SetSyncSecretIntegrationLastRunTimestampInSeconds: 10,
|
SetSyncSecretIntegrationLastRunTimestampInSeconds: 60,
|
||||||
AccessTokenStatusUpdateInSeconds: 120
|
AccessTokenStatusUpdateInSeconds: 120
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,30 +5,31 @@ export const GROUPS = {
|
|||||||
role: "The role of the group to create."
|
role: "The role of the group to create."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
id: "The id of the group to update",
|
id: "The ID of the group to update.",
|
||||||
name: "The new name of the group to update to.",
|
name: "The new name of the group to update to.",
|
||||||
slug: "The new slug of the group to update to.",
|
slug: "The new slug of the group to update to.",
|
||||||
role: "The new role of the group to update to."
|
role: "The new role of the group to update to."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
id: "The id of the group to delete",
|
id: "The ID of the group to delete.",
|
||||||
slug: "The slug of the group to delete"
|
slug: "The slug of the group to delete."
|
||||||
},
|
},
|
||||||
LIST_USERS: {
|
LIST_USERS: {
|
||||||
id: "The id of the group to list users for",
|
id: "The ID of the group to list users for.",
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th user.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th user.",
|
||||||
limit: "The number of users to return.",
|
limit: "The number of users to return.",
|
||||||
username: "The username to search for."
|
username: "The username to search for.",
|
||||||
|
search: "The text string that user email or name will be filtered by."
|
||||||
},
|
},
|
||||||
ADD_USER: {
|
ADD_USER: {
|
||||||
id: "The id of the group to add the user to.",
|
id: "The ID of the group to add the user to.",
|
||||||
username: "The username of the user to add to the group."
|
username: "The username of the user to add to the group."
|
||||||
},
|
},
|
||||||
GET_BY_ID: {
|
GET_BY_ID: {
|
||||||
id: "The id of the group to fetch"
|
id: "The ID of the group to fetch."
|
||||||
},
|
},
|
||||||
DELETE_USER: {
|
DELETE_USER: {
|
||||||
id: "The id of the group to remove the user from.",
|
id: "The ID of the group to remove the user from.",
|
||||||
username: "The username of the user to remove from the group."
|
username: "The username of the user to remove from the group."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
@ -118,7 +119,7 @@ export const AWS_AUTH = {
|
|||||||
identityId: "The ID of the identity to login.",
|
identityId: "The ID of the identity to login.",
|
||||||
iamHttpRequestMethod: "The HTTP request method used in the signed request.",
|
iamHttpRequestMethod: "The HTTP request method used in the signed request.",
|
||||||
iamRequestUrl:
|
iamRequestUrl:
|
||||||
"The base64-encoded HTTP URL used in the signed request. Most likely, the base64-encoding of https://sts.amazonaws.com/",
|
"The base64-encoded HTTP URL used in the signed request. Most likely, the base64-encoding of https://sts.amazonaws.com/.",
|
||||||
iamRequestBody:
|
iamRequestBody:
|
||||||
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
|
"The base64-encoded body of the signed request. Most likely, the base64-encoding of Action=GetCallerIdentity&Version=2011-06-15.",
|
||||||
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
|
iamRequestHeaders: "The base64-encoded headers of the sts:GetCallerIdentity signed request."
|
||||||
@ -129,8 +130,8 @@ export const AWS_AUTH = {
|
|||||||
"The comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.",
|
"The comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.",
|
||||||
allowedAccountIds:
|
allowedAccountIds:
|
||||||
"The comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.",
|
"The comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.",
|
||||||
accessTokenTTL: "The lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
stsEndpoint: "The endpoint URL for the AWS STS API.",
|
stsEndpoint: "The endpoint URL for the AWS STS API.",
|
||||||
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used.",
|
||||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from."
|
||||||
@ -141,8 +142,8 @@ export const AWS_AUTH = {
|
|||||||
"The new comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.",
|
"The new comma-separated list of trusted IAM principal ARNs that are allowed to authenticate with Infisical.",
|
||||||
allowedAccountIds:
|
allowedAccountIds:
|
||||||
"The new comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.",
|
"The new comma-separated list of trusted AWS account IDs that are allowed to authenticate with Infisical.",
|
||||||
accessTokenTTL: "The new lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
stsEndpoint: "The new endpoint URL for the AWS STS API.",
|
stsEndpoint: "The new endpoint URL for the AWS STS API.",
|
||||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
||||||
@ -166,8 +167,8 @@ export const AZURE_AUTH = {
|
|||||||
allowedServicePrincipalIds:
|
allowedServicePrincipalIds:
|
||||||
"The comma-separated list of Azure AD service principal IDs that are allowed to authenticate with Infisical.",
|
"The comma-separated list of Azure AD service principal IDs that are allowed to authenticate with Infisical.",
|
||||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@ -177,8 +178,8 @@ export const AZURE_AUTH = {
|
|||||||
allowedServicePrincipalIds:
|
allowedServicePrincipalIds:
|
||||||
"The new comma-separated list of Azure AD service principal IDs that are allowed to authenticate with Infisical.",
|
"The new comma-separated list of Azure AD service principal IDs that are allowed to authenticate with Infisical.",
|
||||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The new lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
RETRIEVE: {
|
RETRIEVE: {
|
||||||
@ -202,8 +203,8 @@ export const GCP_AUTH = {
|
|||||||
allowedZones:
|
allowedZones:
|
||||||
"The comma-separated list of trusted zones that the GCE instances must belong to authenticate with Infisical.",
|
"The comma-separated list of trusted zones that the GCE instances must belong to authenticate with Infisical.",
|
||||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@ -215,8 +216,8 @@ export const GCP_AUTH = {
|
|||||||
allowedZones:
|
allowedZones:
|
||||||
"The new comma-separated list of trusted zones that the GCE instances must belong to authenticate with Infisical.",
|
"The new comma-separated list of trusted zones that the GCE instances must belong to authenticate with Infisical.",
|
||||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The new lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
RETRIEVE: {
|
RETRIEVE: {
|
||||||
@ -243,8 +244,8 @@ export const KUBERNETES_AUTH = {
|
|||||||
allowedAudience:
|
allowedAudience:
|
||||||
"The optional audience claim that the service account JWT token must have to authenticate with Infisical.",
|
"The optional audience claim that the service account JWT token must have to authenticate with Infisical.",
|
||||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@ -275,15 +276,15 @@ export const TOKEN_AUTH = {
|
|||||||
ATTACH: {
|
ATTACH: {
|
||||||
identityId: "The ID of the identity to attach the configuration onto.",
|
identityId: "The ID of the identity to attach the configuration onto.",
|
||||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
identityId: "The ID of the identity to update the auth method for.",
|
identityId: "The ID of the identity to update the auth method for.",
|
||||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The new lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
RETRIEVE: {
|
RETRIEVE: {
|
||||||
@ -295,18 +296,18 @@ export const TOKEN_AUTH = {
|
|||||||
GET_TOKENS: {
|
GET_TOKENS: {
|
||||||
identityId: "The ID of the identity to list token metadata for.",
|
identityId: "The ID of the identity to list token metadata for.",
|
||||||
offset: "The offset to start from. If you enter 10, it will start from the 10th token.",
|
offset: "The offset to start from. If you enter 10, it will start from the 10th token.",
|
||||||
limit: "The number of tokens to return"
|
limit: "The number of tokens to return."
|
||||||
},
|
},
|
||||||
CREATE_TOKEN: {
|
CREATE_TOKEN: {
|
||||||
identityId: "The ID of the identity to create the token for.",
|
identityId: "The ID of the identity to create the token for.",
|
||||||
name: "The name of the token to create"
|
name: "The name of the token to create."
|
||||||
},
|
},
|
||||||
UPDATE_TOKEN: {
|
UPDATE_TOKEN: {
|
||||||
tokenId: "The ID of the token to update metadata for",
|
tokenId: "The ID of the token to update metadata for.",
|
||||||
name: "The name of the token to update to"
|
name: "The name of the token to update to."
|
||||||
},
|
},
|
||||||
REVOKE_TOKEN: {
|
REVOKE_TOKEN: {
|
||||||
tokenId: "The ID of the token to revoke"
|
tokenId: "The ID of the token to revoke."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -323,8 +324,8 @@ export const OIDC_AUTH = {
|
|||||||
boundClaims: "The attributes that should be present in the JWT for it to be valid.",
|
boundClaims: "The attributes that should be present in the JWT for it to be valid.",
|
||||||
boundSubject: "The expected principal that is the subject of the JWT.",
|
boundSubject: "The expected principal that is the subject of the JWT.",
|
||||||
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@ -336,8 +337,8 @@ export const OIDC_AUTH = {
|
|||||||
boundClaims: "The new attributes that should be present in the JWT for it to be valid.",
|
boundClaims: "The new attributes that should be present in the JWT for it to be valid.",
|
||||||
boundSubject: "The new expected principal that is the subject of the JWT.",
|
boundSubject: "The new expected principal that is the subject of the JWT.",
|
||||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
||||||
accessTokenTTL: "The new lifetime for an acccess token in seconds.",
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an acccess token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used."
|
||||||
},
|
},
|
||||||
RETRIEVE: {
|
RETRIEVE: {
|
||||||
@ -474,6 +475,7 @@ export const PROJECT_USERS = {
|
|||||||
},
|
},
|
||||||
GET_USER_MEMBERSHIP: {
|
GET_USER_MEMBERSHIP: {
|
||||||
workspaceId: "The ID of the project to get memberships from.",
|
workspaceId: "The ID of the project to get memberships from.",
|
||||||
|
membershipId: "The ID of the user's project membership.",
|
||||||
username: "The username to get project membership of. Email is the default username."
|
username: "The username to get project membership of. Email is the default username."
|
||||||
},
|
},
|
||||||
UPDATE_USER_MEMBERSHIP: {
|
UPDATE_USER_MEMBERSHIP: {
|
||||||
@ -505,8 +507,8 @@ export const PROJECT_IDENTITIES = {
|
|||||||
isTemporary:
|
isTemporary:
|
||||||
"Whether the assigned role is temporary. If isTemporary is set true, must provide temporaryMode, temporaryRange and temporaryAccessStartTime.",
|
"Whether the assigned role is temporary. If isTemporary is set true, must provide temporaryMode, temporaryRange and temporaryAccessStartTime.",
|
||||||
temporaryMode: "Type of temporary expiry.",
|
temporaryMode: "Type of temporary expiry.",
|
||||||
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
|
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s, 2m ,3h, etc.",
|
||||||
temporaryAccessStartTime: "Time to which the temporary access starts"
|
temporaryAccessStartTime: "Time to which the temporary access starts."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DELETE_IDENTITY_MEMBERSHIP: {
|
DELETE_IDENTITY_MEMBERSHIP: {
|
||||||
@ -523,8 +525,8 @@ export const PROJECT_IDENTITIES = {
|
|||||||
isTemporary:
|
isTemporary:
|
||||||
"Whether the assigned role is temporary. If isTemporary is set true, must provide temporaryMode, temporaryRange and temporaryAccessStartTime.",
|
"Whether the assigned role is temporary. If isTemporary is set true, must provide temporaryMode, temporaryRange and temporaryAccessStartTime.",
|
||||||
temporaryMode: "Type of temporary expiry.",
|
temporaryMode: "Type of temporary expiry.",
|
||||||
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s,2m,3h",
|
temporaryRange: "Expiry time for temporary access. In relative mode it could be 1s, 2m, 3h, etc.",
|
||||||
temporaryAccessStartTime: "Time to which the temporary access starts"
|
temporaryAccessStartTime: "Time to which the temporary access starts."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -561,7 +563,7 @@ export const FOLDERS = {
|
|||||||
directory: "The directory to list folders from. (Deprecated in favor of path)"
|
directory: "The directory to list folders from. (Deprecated in favor of path)"
|
||||||
},
|
},
|
||||||
GET_BY_ID: {
|
GET_BY_ID: {
|
||||||
folderId: "The id of the folder to get details."
|
folderId: "The ID of the folder to get details."
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
workspaceId: "The ID of the project to create the folder in.",
|
workspaceId: "The ID of the project to create the folder in.",
|
||||||
@ -594,22 +596,22 @@ 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",
|
||||||
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."
|
||||||
},
|
},
|
||||||
DETACH_TAGS: {
|
DETACH_TAGS: {
|
||||||
secretName: "The name of the secret to detach tags from.",
|
secretName: "The name of the secret to detach tags from.",
|
||||||
secretPath: "The path of the secret to detach tags from.",
|
secretPath: "The path of the secret to detach tags from.",
|
||||||
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.",
|
||||||
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 detach from the secret."
|
tagSlugs: "An array of existing tag slugs to detach from the secret."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const RAW_SECRETS = {
|
export const RAW_SECRETS = {
|
||||||
LIST: {
|
LIST: {
|
||||||
expand: "Whether or not to expand secret references",
|
expand: "Whether or not to expand secret references.",
|
||||||
recursive:
|
recursive:
|
||||||
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
|
"Whether or not to fetch all secrets from the specified base path, and all of its subdirectories. Note, the max depth is 20 deep.",
|
||||||
workspaceId: "The ID of the project to list secrets from.",
|
workspaceId: "The ID of the project to list secrets from.",
|
||||||
@ -618,7 +620,7 @@ export const RAW_SECRETS = {
|
|||||||
environment: "The slug of the environment to list secrets from.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
secretPath: "The secret path to list secrets from.",
|
secretPath: "The secret path to list secrets from.",
|
||||||
includeImports: "Weather to include imported secrets or not.",
|
includeImports: "Weather to include imported secrets or not.",
|
||||||
tagSlugs: "The comma separated tag slugs to filter secrets"
|
tagSlugs: "The comma separated tag slugs to filter secrets."
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
secretName: "The name of the secret to create.",
|
secretName: "The name of the secret to create.",
|
||||||
@ -631,11 +633,11 @@ export const RAW_SECRETS = {
|
|||||||
type: "The type of the secret to create.",
|
type: "The type of the secret to create.",
|
||||||
workspaceId: "The ID of the project to create the secret in.",
|
workspaceId: "The ID of the project to create the secret in.",
|
||||||
tagIds: "The ID of the tags to be attached to the created secret.",
|
tagIds: "The ID of the tags to be attached to the created secret.",
|
||||||
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days",
|
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
||||||
secretReminderNote: "Note to be attached in notification email"
|
secretReminderNote: "Note to be attached in notification email."
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
expand: "Whether or not to expand secret references",
|
expand: "Whether or not to expand secret references.",
|
||||||
secretName: "The name of the secret to get.",
|
secretName: "The name of the secret to get.",
|
||||||
workspaceId: "The ID of the project to get the secret from.",
|
workspaceId: "The ID of the project to get the secret from.",
|
||||||
workspaceSlug: "The slug of the project to get the secret from.",
|
workspaceSlug: "The slug of the project to get the secret from.",
|
||||||
@ -649,16 +651,16 @@ export const RAW_SECRETS = {
|
|||||||
secretName: "The name of the secret to update.",
|
secretName: "The name of the secret to update.",
|
||||||
secretComment: "Update comment to the secret.",
|
secretComment: "Update comment to the secret.",
|
||||||
environment: "The slug of the environment where the secret is located.",
|
environment: "The slug of the environment where the secret is located.",
|
||||||
secretPath: "The path of the secret to update",
|
secretPath: "The path of the secret to update.",
|
||||||
secretValue: "The new value of the secret.",
|
secretValue: "The new value of the secret.",
|
||||||
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
skipMultilineEncoding: "Skip multiline encoding for the secret value.",
|
||||||
type: "The type of the secret to update.",
|
type: "The type of the secret to update.",
|
||||||
projectSlug: "The slug of the project to update the secret in.",
|
projectSlug: "The slug of the project to update the secret in.",
|
||||||
workspaceId: "The ID of the project to update the secret in.",
|
workspaceId: "The ID of the project to update the secret in.",
|
||||||
tagIds: "The ID of the tags to be attached to the updated secret.",
|
tagIds: "The ID of the tags to be attached to the updated secret.",
|
||||||
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days",
|
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
||||||
secretReminderNote: "Note to be attached in notification email",
|
secretReminderNote: "Note to be attached in notification email.",
|
||||||
newSecretName: "The new name for the secret"
|
newSecretName: "The new name for the secret."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
secretName: "The name of the secret to delete.",
|
secretName: "The name of the secret to delete.",
|
||||||
@ -667,6 +669,12 @@ export const RAW_SECRETS = {
|
|||||||
type: "The type of the secret to delete.",
|
type: "The type of the secret to delete.",
|
||||||
projectSlug: "The slug of the project to delete the secret in.",
|
projectSlug: "The slug of the project to delete the secret in.",
|
||||||
workspaceId: "The ID of the project where the secret is located."
|
workspaceId: "The ID of the project where the secret is located."
|
||||||
|
},
|
||||||
|
GET_REFERENCE_TREE: {
|
||||||
|
secretName: "The name of the secret to get the reference tree for.",
|
||||||
|
workspaceId: "The ID of the project where the secret is located.",
|
||||||
|
environment: "The slug of the environment where the the secret is located.",
|
||||||
|
secretPath: "The folder path where the secret is located."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -789,7 +797,7 @@ export const DYNAMIC_SECRETS = {
|
|||||||
environmentSlug: "The slug of the environment to update the dynamic secret in.",
|
environmentSlug: "The slug of the environment to update the dynamic secret in.",
|
||||||
path: "The path to update the dynamic secret in.",
|
path: "The path to update the dynamic secret in.",
|
||||||
name: "The name of the dynamic secret.",
|
name: "The name of the dynamic secret.",
|
||||||
inputs: "The new partial values for the configurated provider of the dynamic secret",
|
inputs: "The new partial values for the configured provider of the dynamic secret",
|
||||||
defaultTTL: "The default TTL that will be applied for all the leases.",
|
defaultTTL: "The default TTL that will be applied for all the leases.",
|
||||||
maxTTL: "The maximum limit a TTL can be leases or renewed.",
|
maxTTL: "The maximum limit a TTL can be leases or renewed.",
|
||||||
newName: "The new name for the dynamic secret."
|
newName: "The new name for the dynamic secret."
|
||||||
@ -800,7 +808,7 @@ export const DYNAMIC_SECRETS = {
|
|||||||
path: "The path to delete the dynamic secret in.",
|
path: "The path to delete the dynamic secret in.",
|
||||||
name: "The name of the dynamic secret.",
|
name: "The name of the dynamic secret.",
|
||||||
isForced:
|
isForced:
|
||||||
"A boolean flag to delete the the dynamic secret from infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
"A boolean flag to delete the the dynamic secret from Infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -816,7 +824,7 @@ export const DYNAMIC_SECRET_LEASES = {
|
|||||||
environmentSlug: "The slug of the environment of the dynamic secret in.",
|
environmentSlug: "The slug of the environment of the dynamic secret in.",
|
||||||
path: "The path of the dynamic secret in.",
|
path: "The path of the dynamic secret in.",
|
||||||
dynamicSecretName: "The name of the dynamic secret.",
|
dynamicSecretName: "The name of the dynamic secret.",
|
||||||
ttl: "The lease lifetime ttl. If not provided the default TTL of dynamic secret will be used."
|
ttl: "The lease lifetime TTL. If not provided the default TTL of dynamic secret will be used."
|
||||||
},
|
},
|
||||||
RENEW: {
|
RENEW: {
|
||||||
projectSlug: "The slug of the project of the dynamic secret in.",
|
projectSlug: "The slug of the project of the dynamic secret in.",
|
||||||
@ -831,7 +839,7 @@ export const DYNAMIC_SECRET_LEASES = {
|
|||||||
path: "The path of the dynamic secret in.",
|
path: "The path of the dynamic secret in.",
|
||||||
leaseId: "The ID of the dynamic secret lease.",
|
leaseId: "The ID of the dynamic secret lease.",
|
||||||
isForced:
|
isForced:
|
||||||
"A boolean flag to delete the the dynamic secret from infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
"A boolean flag to delete the the dynamic secret from Infisical without trying to remove it from external provider. Used when the dynamic secret got modified externally."
|
||||||
}
|
}
|
||||||
} as const;
|
} as const;
|
||||||
export const SECRET_TAGS = {
|
export const SECRET_TAGS = {
|
||||||
@ -840,11 +848,11 @@ export const SECRET_TAGS = {
|
|||||||
},
|
},
|
||||||
GET_TAG_BY_ID: {
|
GET_TAG_BY_ID: {
|
||||||
projectId: "The ID of the project to get tags from.",
|
projectId: "The ID of the project to get tags from.",
|
||||||
tagId: "The ID of the tag to get details"
|
tagId: "The ID of the tag to get details."
|
||||||
},
|
},
|
||||||
GET_TAG_BY_SLUG: {
|
GET_TAG_BY_SLUG: {
|
||||||
projectId: "The ID of the project to get tags from.",
|
projectId: "The ID of the project to get tags from.",
|
||||||
tagSlug: "The slug of the tag to get details"
|
tagSlug: "The slug of the tag to get details."
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the tag in.",
|
projectId: "The ID of the project to create the tag in.",
|
||||||
@ -854,7 +862,7 @@ export const SECRET_TAGS = {
|
|||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
projectId: "The ID of the project to update the tag in.",
|
projectId: "The ID of the project to update the tag in.",
|
||||||
tagId: "The ID of the tag to get details",
|
tagId: "The ID of the tag to get details.",
|
||||||
name: "The name of the tag to update.",
|
name: "The name of the tag to update.",
|
||||||
slug: "The slug of the tag to update.",
|
slug: "The slug of the tag to update.",
|
||||||
color: "The color of the tag to update."
|
color: "The color of the tag to update."
|
||||||
@ -888,8 +896,8 @@ The permission object for the privilege.
|
|||||||
privilegePermission: "The permission object for the privilege.",
|
privilegePermission: "The permission object for the privilege.",
|
||||||
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
||||||
isTemporary: "Whether the privilege is temporary.",
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
temporaryMode: "Type of temporary access given. Types: relative",
|
temporaryMode: "Type of temporary access given. Types: relative.",
|
||||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
temporaryRange: "TTL for the temporary time. Eg: 1m, 1h, 1d.",
|
||||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@ -914,8 +922,8 @@ The permission object for the privilege.
|
|||||||
`,
|
`,
|
||||||
privilegePermission: "The permission object for the privilege.",
|
privilegePermission: "The permission object for the privilege.",
|
||||||
isTemporary: "Whether the privilege is temporary.",
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
temporaryMode: "Type of temporary access given. Types: relative",
|
temporaryMode: "Type of temporary access given. Types: relative.",
|
||||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
temporaryRange: "TTL for the temporary time. Eg: 1m, 1h, 1d.",
|
||||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
@ -931,62 +939,102 @@ The permission object for the privilege.
|
|||||||
LIST: {
|
LIST: {
|
||||||
projectSlug: "The slug of the project of the identity in.",
|
projectSlug: "The slug of the project of the identity in.",
|
||||||
identityId: "The ID of the identity to list.",
|
identityId: "The ID of the identity to list.",
|
||||||
unpacked: "Whether the system should send the permissions as unpacked"
|
unpacked: "Whether the system should send the permissions as unpacked."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PROJECT_USER_ADDITIONAL_PRIVILEGE = {
|
export const PROJECT_USER_ADDITIONAL_PRIVILEGE = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectMembershipId: "Project membership id of user",
|
projectMembershipId: "Project membership ID of user.",
|
||||||
slug: "The slug of the privilege to create.",
|
slug: "The slug of the privilege to create.",
|
||||||
permissions:
|
permissions:
|
||||||
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape",
|
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape.",
|
||||||
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
isPackPermission: "Whether the server should pack (compact) the permission object.",
|
||||||
isTemporary: "Whether the privilege is temporary.",
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
temporaryMode: "Type of temporary access given. Types: relative",
|
temporaryMode: "Type of temporary access given. Types: relative.",
|
||||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
temporaryRange: "TTL for the temporary time. Eg: 1m, 1h, 1d.",
|
||||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
privilegeId: "The id of privilege object",
|
privilegeId: "The ID of privilege object.",
|
||||||
slug: "The slug of the privilege to create.",
|
slug: "The slug of the privilege to create.",
|
||||||
newSlug: "The new slug of the privilege to create.",
|
newSlug: "The new slug of the privilege to create.",
|
||||||
permissions:
|
permissions:
|
||||||
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape",
|
"The permission object for the privilege. Refer https://casl.js.org/v6/en/guide/define-rules#the-shape-of-raw-rule to understand the shape.",
|
||||||
isPackPermission: "Whether the server should pack(compact) the permission object.",
|
isPackPermission: "Whether the server should pack (compact) the permission object.",
|
||||||
isTemporary: "Whether the privilege is temporary.",
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
temporaryMode: "Type of temporary access given. Types: relative",
|
temporaryMode: "Type of temporary access given. Types: relative.",
|
||||||
temporaryRange: "TTL for the temporay time. Eg: 1m, 1h, 1d",
|
temporaryRange: "TTL for the temporary time. Eg: 1m, 1h, 1d.",
|
||||||
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
temporaryAccessStartTime: "ISO time for which temporary access should begin."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
privilegeId: "The id of privilege object"
|
privilegeId: "The ID of privilege object."
|
||||||
},
|
},
|
||||||
GET_BY_PRIVILEGEID: {
|
GET_BY_PRIVILEGE_ID: {
|
||||||
privilegeId: "The id of privilege object"
|
privilegeId: "The ID of privilege object."
|
||||||
},
|
},
|
||||||
LIST: {
|
LIST: {
|
||||||
projectMembershipId: "Project membership id of user"
|
projectMembershipId: "Project membership ID of user."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IDENTITY_ADDITIONAL_PRIVILEGE_V2 = {
|
||||||
|
CREATE: {
|
||||||
|
identityId: "The ID of the identity to create the privilege for.",
|
||||||
|
projectId: "The ID of the project of the identity in.",
|
||||||
|
slug: "The slug of the privilege to create.",
|
||||||
|
permission: "The permission for the privilege.",
|
||||||
|
isTemporary: "Whether the privilege is temporary or permanent.",
|
||||||
|
temporaryMode: "Type of temporary access given. Types: relative.",
|
||||||
|
temporaryRange: "The TTL for the temporary access given. Eg: 1m, 1h, 1d.",
|
||||||
|
temporaryAccessStartTime: "The start time in ISO format when the temporary access should begin."
|
||||||
|
},
|
||||||
|
UPDATE: {
|
||||||
|
id: "The ID of the identity privilege.",
|
||||||
|
identityId: "The ID of the identity to update.",
|
||||||
|
slug: "The slug of the privilege to update.",
|
||||||
|
privilegePermission: "The permission for the privilege.",
|
||||||
|
isTemporary: "Whether the privilege is temporary.",
|
||||||
|
temporaryMode: "Type of temporary access given. Types: relative.",
|
||||||
|
temporaryRange: "The TTL for the temporary access given. Eg: 1m, 1h, 1d.",
|
||||||
|
temporaryAccessStartTime: "The start time in ISO format when the temporary access should begin."
|
||||||
|
},
|
||||||
|
DELETE: {
|
||||||
|
id: "The ID of the identity privilege.",
|
||||||
|
identityId: "The ID of the identity to delete.",
|
||||||
|
slug: "The slug of the privilege to delete."
|
||||||
|
},
|
||||||
|
GET_BY_SLUG: {
|
||||||
|
projectSlug: "The slug of the project of the identity in.",
|
||||||
|
identityId: "The ID of the identity to list.",
|
||||||
|
slug: "The slug of the privilege."
|
||||||
|
},
|
||||||
|
GET_BY_ID: {
|
||||||
|
id: "The ID of the identity privilege."
|
||||||
|
},
|
||||||
|
LIST: {
|
||||||
|
projectId: "The ID of the project that the identity is in.",
|
||||||
|
identityId: "The ID of the identity to list."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const INTEGRATION_AUTH = {
|
export const INTEGRATION_AUTH = {
|
||||||
GET: {
|
GET: {
|
||||||
integrationAuthId: "The id of integration authentication object."
|
integrationAuthId: "The ID of integration authentication object."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
integration: "The slug of the integration to be unauthorized.",
|
integration: "The slug of the integration to be unauthorized.",
|
||||||
projectId: "The ID of the project to delete the integration auth from."
|
projectId: "The ID of the project to delete the integration auth from."
|
||||||
},
|
},
|
||||||
DELETE_BY_ID: {
|
DELETE_BY_ID: {
|
||||||
integrationAuthId: "The id of integration authentication object to delete."
|
integrationAuthId: "The ID of integration authentication object to delete."
|
||||||
},
|
},
|
||||||
CREATE_ACCESS_TOKEN: {
|
CREATE_ACCESS_TOKEN: {
|
||||||
workspaceId: "The ID of the project to create the integration auth for.",
|
workspaceId: "The ID of the project to create the integration auth for.",
|
||||||
integration: "The slug of integration for the auth object.",
|
integration: "The slug of integration for the auth object.",
|
||||||
accessId: "The unique authorized access id of the external integration provider.",
|
accessId: "The unique authorized access ID of the external integration provider.",
|
||||||
accessToken: "The unique authorized access token of the external integration provider.",
|
accessToken: "The unique authorized access token of the external integration provider.",
|
||||||
awsAssumeIamRoleArn: "The AWS IAM Role to be assumed by Infisical",
|
awsAssumeIamRoleArn: "The AWS IAM Role to be assumed by Infisical.",
|
||||||
url: "",
|
url: "",
|
||||||
namespace: "",
|
namespace: "",
|
||||||
refreshToken: "The refresh token for integration authorization."
|
refreshToken: "The refresh token for integration authorization."
|
||||||
@ -1005,16 +1053,16 @@ export const INTEGRATION = {
|
|||||||
targetEnvironment:
|
targetEnvironment:
|
||||||
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
"The target environment of the integration provider. Used in cloudflare pages, TeamCity, Gitlab integrations.",
|
||||||
targetEnvironmentId:
|
targetEnvironmentId:
|
||||||
"The target environment id of the integration provider. Used in cloudflare pages, teamcity, gitlab integrations.",
|
"The target environment ID of the integration provider. Used in cloudflare pages, teamcity, gitlab integrations.",
|
||||||
targetService:
|
targetService:
|
||||||
"The service based grouping identifier of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank",
|
"The service based grouping identifier of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank.",
|
||||||
targetServiceId:
|
targetServiceId:
|
||||||
"The service based grouping identifier ID of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank",
|
"The service based grouping identifier ID of the external provider. Used in Terraform cloud, Checkly, Railway and NorthFlank.",
|
||||||
owner: "External integration providers service entity owner. Used in Github.",
|
owner: "External integration providers service entity owner. Used in Github.",
|
||||||
url: "The self-hosted URL of the platform to integrate with",
|
url: "The self-hosted URL of the platform to integrate with.",
|
||||||
path: "Path to save the synced secrets. Used by Gitlab, AWS Parameter Store, Vault",
|
path: "Path to save the synced secrets. Used by Gitlab, AWS Parameter Store, Vault.",
|
||||||
region: "AWS region to sync secrets to.",
|
region: "AWS region to sync secrets to.",
|
||||||
scope: "Scope of the provider. Used by Github, Qovery",
|
scope: "Scope of the provider. Used by Github, Qovery.",
|
||||||
metadata: {
|
metadata: {
|
||||||
secretPrefix: "The prefix for the saved secret. Used by GCP.",
|
secretPrefix: "The prefix for the saved secret. Used by GCP.",
|
||||||
secretSuffix: "The suffix for the saved secret. Used by GCP.",
|
secretSuffix: "The suffix for the saved secret. Used by GCP.",
|
||||||
@ -1026,12 +1074,12 @@ export const INTEGRATION = {
|
|||||||
githubVisibility:
|
githubVisibility:
|
||||||
"Define where the secrets from the Github Integration should be visible. Option 'selected' lets you directly define which repositories to sync secrets to.",
|
"Define where the secrets from the Github Integration should be visible. Option 'selected' lets you directly define which repositories to sync secrets to.",
|
||||||
githubVisibilityRepoIds:
|
githubVisibilityRepoIds:
|
||||||
"The repository IDs to sync secrets to when using the Github Integration. Only applicable when using Organization scope, and visibility is set to 'selected'",
|
"The repository IDs to sync secrets to when using the Github Integration. Only applicable when using Organization scope, and visibility is set to 'selected'.",
|
||||||
kmsKeyId: "The ID of the encryption key from AWS KMS.",
|
kmsKeyId: "The ID of the encryption key from AWS KMS.",
|
||||||
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store.",
|
shouldDisableDelete: "The flag to disable deletion of secrets in AWS Parameter Store.",
|
||||||
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
|
shouldMaskSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Masked'.",
|
||||||
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
|
shouldProtectSecrets: "Specifies if the secrets synced from Infisical to Gitlab should be marked as 'Protected'.",
|
||||||
shouldEnableDelete: "The flag to enable deletion of secrets"
|
shouldEnableDelete: "The flag to enable deletion of secrets."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
@ -1050,7 +1098,7 @@ export const INTEGRATION = {
|
|||||||
integrationId: "The ID of the integration object."
|
integrationId: "The ID of the integration object."
|
||||||
},
|
},
|
||||||
SYNC: {
|
SYNC: {
|
||||||
integrationId: "The ID of the integration object to manually sync"
|
integrationId: "The ID of the integration object to manually sync."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1058,7 +1106,7 @@ export const AUDIT_LOG_STREAMS = {
|
|||||||
CREATE: {
|
CREATE: {
|
||||||
url: "The HTTP URL to push logs to.",
|
url: "The HTTP URL to push logs to.",
|
||||||
headers: {
|
headers: {
|
||||||
desc: "The HTTP headers attached for the external prrovider requests.",
|
desc: "The HTTP headers attached for the external provider requests.",
|
||||||
key: "The HTTP header key name.",
|
key: "The HTTP header key name.",
|
||||||
value: "The HTTP header value."
|
value: "The HTTP header value."
|
||||||
}
|
}
|
||||||
@ -1067,7 +1115,7 @@ export const AUDIT_LOG_STREAMS = {
|
|||||||
id: "The ID of the audit log stream to update.",
|
id: "The ID of the audit log stream to update.",
|
||||||
url: "The HTTP URL to push logs to.",
|
url: "The HTTP URL to push logs to.",
|
||||||
headers: {
|
headers: {
|
||||||
desc: "The HTTP headers attached for the external prrovider requests.",
|
desc: "The HTTP headers attached for the external provider requests.",
|
||||||
key: "The HTTP header key name.",
|
key: "The HTTP header key name.",
|
||||||
value: "The HTTP header value."
|
value: "The HTTP header value."
|
||||||
}
|
}
|
||||||
@ -1083,16 +1131,16 @@ export const AUDIT_LOG_STREAMS = {
|
|||||||
export const CERTIFICATE_AUTHORITIES = {
|
export const CERTIFICATE_AUTHORITIES = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectSlug: "Slug of the project to create the CA in.",
|
projectSlug: "Slug of the project to create the CA in.",
|
||||||
type: "The type of CA to create",
|
type: "The type of CA to create.",
|
||||||
friendlyName: "A friendly name for the CA",
|
friendlyName: "A friendly name for the CA.",
|
||||||
organization: "The organization (O) for the CA",
|
organization: "The organization (O) for the CA.",
|
||||||
ou: "The organization unit (OU) for the CA",
|
ou: "The organization unit (OU) for the CA.",
|
||||||
country: "The country name (C) for the CA",
|
country: "The country name (C) for the CA.",
|
||||||
province: "The state of province name for the CA",
|
province: "The state of province name for the CA.",
|
||||||
locality: "The locality name for the CA",
|
locality: "The locality name for the CA.",
|
||||||
commonName: "The common name (CN) for the CA",
|
commonName: "The common name (CN) for the CA.",
|
||||||
notBefore: "The date and time when the CA becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notBefore: "The date and time when the CA becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
notAfter: "The date and time when the CA expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notAfter: "The date and time when the CA expires in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
maxPathLength:
|
maxPathLength:
|
||||||
"The maximum number of intermediate CAs that may follow this CA in the certificate / CA chain. A maxPathLength of -1 implies no path limit on the chain.",
|
"The maximum number of intermediate CAs that may follow this CA in the certificate / CA chain. A maxPathLength of -1 implies no path limit on the chain.",
|
||||||
keyAlgorithm:
|
keyAlgorithm:
|
||||||
@ -1101,238 +1149,240 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
"Whether or not certificates for this CA can only be issued through certificate templates."
|
"Whether or not certificates for this CA can only be issued through certificate templates."
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
caId: "The ID of the CA to get"
|
caId: "The ID of the CA to get."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
caId: "The ID of the CA to update",
|
caId: "The ID of the CA to update.",
|
||||||
status: "The status of the CA to update to. This can be one of active or disabled",
|
status: "The status of the CA to update to. This can be one of active or disabled.",
|
||||||
requireTemplateForIssuance:
|
requireTemplateForIssuance:
|
||||||
"Whether or not certificates for this CA can only be issued through certificate templates."
|
"Whether or not certificates for this CA can only be issued through certificate templates."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
caId: "The ID of the CA to delete"
|
caId: "The ID of the CA to delete."
|
||||||
},
|
},
|
||||||
GET_CSR: {
|
GET_CSR: {
|
||||||
caId: "The ID of the CA to generate CSR from",
|
caId: "The ID of the CA to generate CSR from.",
|
||||||
csr: "The generated CSR from the CA"
|
csr: "The generated CSR from the CA."
|
||||||
},
|
},
|
||||||
RENEW_CA_CERT: {
|
RENEW_CA_CERT: {
|
||||||
caId: "The ID of the CA to renew the CA certificate for",
|
caId: "The ID of the CA to renew the CA certificate for.",
|
||||||
type: "The type of behavior to use for the renewal operation. Currently Infisical is only able to renew a CA certificate with the same key pair.",
|
type: "The type of behavior to use for the renewal operation. Currently Infisical is only able to renew a CA certificate with the same key pair.",
|
||||||
notAfter: "The expiry date and time for the renewed CA certificate in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notAfter: "The expiry date and time for the renewed CA certificate in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
certificate: "The renewed CA certificate body",
|
certificate: "The renewed CA certificate body.",
|
||||||
certificateChain: "The certificate chain of the CA",
|
certificateChain: "The certificate chain of the CA.",
|
||||||
serialNumber: "The serial number of the renewed CA certificate"
|
serialNumber: "The serial number of the renewed CA certificate."
|
||||||
},
|
},
|
||||||
GET_CERT: {
|
GET_CERT: {
|
||||||
caId: "The ID of the CA to get the certificate body and certificate chain from",
|
caId: "The ID of the CA to get the certificate body and certificate chain from.",
|
||||||
certificate: "The certificate body of the CA",
|
certificate: "The certificate body of the CA.",
|
||||||
certificateChain: "The certificate chain of the CA",
|
certificateChain: "The certificate chain of the CA.",
|
||||||
serialNumber: "The serial number of the CA certificate"
|
serialNumber: "The serial number of the CA certificate."
|
||||||
},
|
},
|
||||||
GET_CERT_BY_ID: {
|
GET_CERT_BY_ID: {
|
||||||
caId: "The ID of the CA to get the CA certificate from",
|
caId: "The ID of the CA to get the CA certificate from.",
|
||||||
caCertId: "The ID of the CA certificate to get"
|
caCertId: "The ID of the CA certificate to get."
|
||||||
},
|
},
|
||||||
GET_CA_CERTS: {
|
GET_CA_CERTS: {
|
||||||
caId: "The ID of the CA to get the CA certificates for",
|
caId: "The ID of the CA to get the CA certificates for.",
|
||||||
certificate: "The certificate body of the CA certificate",
|
certificate: "The certificate body of the CA certificate.",
|
||||||
certificateChain: "The certificate chain of the CA certificate",
|
certificateChain: "The certificate chain of the CA certificate.",
|
||||||
serialNumber: "The serial number of the CA certificate",
|
serialNumber: "The serial number of the CA certificate.",
|
||||||
version: "The version of the CA certificate. The version is incremented for each CA renewal operation."
|
version: "The version of the CA certificate. The version is incremented for each CA renewal operation."
|
||||||
},
|
},
|
||||||
SIGN_INTERMEDIATE: {
|
SIGN_INTERMEDIATE: {
|
||||||
caId: "The ID of the CA to sign the intermediate certificate with",
|
caId: "The ID of the CA to sign the intermediate certificate with.",
|
||||||
csr: "The pem-encoded CSR to sign with the CA",
|
csr: "The pem-encoded CSR to sign with the CA.",
|
||||||
notBefore: "The date and time when the intermediate CA becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notBefore: "The date and time when the intermediate CA becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
notAfter: "The date and time when the intermediate CA expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notAfter: "The date and time when the intermediate CA expires in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
maxPathLength:
|
maxPathLength:
|
||||||
"The maximum number of intermediate CAs that may follow this CA in the certificate / CA chain. A maxPathLength of -1 implies no path limit on the chain.",
|
"The maximum number of intermediate CAs that may follow this CA in the certificate / CA chain. A maxPathLength of -1 implies no path limit on the chain.",
|
||||||
certificate: "The signed intermediate certificate",
|
certificate: "The signed intermediate certificate.",
|
||||||
certificateChain: "The certificate chain of the intermediate certificate",
|
certificateChain: "The certificate chain of the intermediate certificate.",
|
||||||
issuingCaCertificate: "The certificate of the issuing CA",
|
issuingCaCertificate: "The certificate of the issuing CA.",
|
||||||
serialNumber: "The serial number of the intermediate certificate"
|
serialNumber: "The serial number of the intermediate certificate."
|
||||||
},
|
},
|
||||||
IMPORT_CERT: {
|
IMPORT_CERT: {
|
||||||
caId: "The ID of the CA to import the certificate for",
|
caId: "The ID of the CA to import the certificate for.",
|
||||||
certificate: "The certificate body to import",
|
certificate: "The certificate body to import.",
|
||||||
certificateChain: "The certificate chain to import"
|
certificateChain: "The certificate chain to import."
|
||||||
},
|
},
|
||||||
ISSUE_CERT: {
|
ISSUE_CERT: {
|
||||||
caId: "The ID of the CA to issue the certificate from",
|
caId: "The ID of the CA to issue the certificate from.",
|
||||||
certificateTemplateId: "The ID of the certificate template to issue the certificate from",
|
certificateTemplateId: "The ID of the certificate template to issue the certificate from.",
|
||||||
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
pkiCollectionId: "The ID of the PKI collection to add the certificate to.",
|
||||||
friendlyName: "A friendly name for the certificate",
|
friendlyName: "A friendly name for the certificate.",
|
||||||
commonName: "The common name (CN) for the certificate",
|
commonName: "The common name (CN) for the certificate.",
|
||||||
altNames:
|
altNames:
|
||||||
"A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses.",
|
"A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses.",
|
||||||
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
|
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
|
||||||
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
certificate: "The issued certificate",
|
certificate: "The issued certificate.",
|
||||||
issuingCaCertificate: "The certificate of the issuing CA",
|
issuingCaCertificate: "The certificate of the issuing CA.",
|
||||||
certificateChain: "The certificate chain of the issued certificate",
|
certificateChain: "The certificate chain of the issued certificate.",
|
||||||
privateKey: "The private key of the issued certificate",
|
privateKey: "The private key of the issued certificate.",
|
||||||
serialNumber: "The serial number of the issued certificate",
|
serialNumber: "The serial number of the issued certificate.",
|
||||||
keyUsages: "The key usage extension of the certificate",
|
keyUsages: "The key usage extension of the certificate.",
|
||||||
extendedKeyUsages: "The extended key usage extension of the certificate"
|
extendedKeyUsages: "The extended key usage extension of the certificate."
|
||||||
},
|
},
|
||||||
SIGN_CERT: {
|
SIGN_CERT: {
|
||||||
caId: "The ID of the CA to issue the certificate from",
|
caId: "The ID of the CA to issue the certificate from.",
|
||||||
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
pkiCollectionId: "The ID of the PKI collection to add the certificate to.",
|
||||||
keyUsages: "The key usage extension of the certificate",
|
keyUsages: "The key usage extension of the certificate.",
|
||||||
extendedKeyUsages: "The extended key usage extension of the certificate",
|
extendedKeyUsages: "The extended key usage extension of the certificate.",
|
||||||
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance.",
|
||||||
friendlyName: "A friendly name for the certificate",
|
friendlyName: "A friendly name for the certificate.",
|
||||||
commonName: "The common name (CN) for the certificate",
|
commonName: "The common name (CN) for the certificate.",
|
||||||
altNames:
|
altNames:
|
||||||
"A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses.",
|
"A comma-delimited list of Subject Alternative Names (SANs) for the certificate; these can be host names or email addresses.",
|
||||||
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
|
ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...",
|
||||||
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format",
|
notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format.",
|
||||||
certificate: "The issued certificate",
|
certificate: "The issued certificate.",
|
||||||
issuingCaCertificate: "The certificate of the issuing CA",
|
issuingCaCertificate: "The certificate of the issuing CA.",
|
||||||
certificateChain: "The certificate chain of the issued certificate",
|
certificateChain: "The certificate chain of the issued certificate.",
|
||||||
serialNumber: "The serial number of the issued certificate"
|
serialNumber: "The serial number of the issued certificate."
|
||||||
},
|
},
|
||||||
GET_CRLS: {
|
GET_CRLS: {
|
||||||
caId: "The ID of the CA to get the certificate revocation lists (CRLs) for",
|
caId: "The ID of the CA to get the certificate revocation lists (CRLs) for.",
|
||||||
id: "The ID of certificate revocation list (CRL)",
|
id: "The ID of certificate revocation list (CRL).",
|
||||||
crl: "The certificate revocation list (CRL)"
|
crl: "The certificate revocation list (CRL)."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CERTIFICATES = {
|
export const CERTIFICATES = {
|
||||||
GET: {
|
GET: {
|
||||||
serialNumber: "The serial number of the certificate to get"
|
serialNumber: "The serial number of the certificate to get."
|
||||||
},
|
},
|
||||||
REVOKE: {
|
REVOKE: {
|
||||||
serialNumber:
|
serialNumber:
|
||||||
"The serial number of the certificate to revoke. The revoked certificate will be added to the certificate revocation list (CRL) of the CA.",
|
"The serial number of the certificate to revoke. The revoked certificate will be added to the certificate revocation list (CRL) of the CA.",
|
||||||
revocationReason: "The reason for revoking the certificate.",
|
revocationReason: "The reason for revoking the certificate.",
|
||||||
revokedAt: "The date and time when the certificate was revoked",
|
revokedAt: "The date and time when the certificate was revoked.",
|
||||||
serialNumberRes: "The serial number of the revoked certificate."
|
serialNumberRes: "The serial number of the revoked certificate."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
serialNumber: "The serial number of the certificate to delete"
|
serialNumber: "The serial number of the certificate to delete."
|
||||||
},
|
},
|
||||||
GET_CERT: {
|
GET_CERT: {
|
||||||
serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for",
|
serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for.",
|
||||||
certificate: "The certificate body of the certificate",
|
certificate: "The certificate body of the certificate.",
|
||||||
certificateChain: "The certificate chain of the certificate",
|
certificateChain: "The certificate chain of the certificate.",
|
||||||
serialNumberRes: "The serial number of the certificate"
|
serialNumberRes: "The serial number of the certificate."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CERTIFICATE_TEMPLATES = {
|
export const CERTIFICATE_TEMPLATES = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
caId: "The ID of the certificate authority to associate the template with",
|
caId: "The ID of the certificate authority to associate the template with.",
|
||||||
pkiCollectionId: "The ID of the PKI collection to bind to the template",
|
pkiCollectionId: "The ID of the PKI collection to bind to the template.",
|
||||||
name: "The name of the template",
|
name: "The name of the template.",
|
||||||
commonName: "The regular expression string to use for validating common names",
|
commonName: "The regular expression string to use for validating common names.",
|
||||||
subjectAlternativeName: "The regular expression string to use for validating subject alternative names",
|
subjectAlternativeName: "The regular expression string to use for validating subject alternative names.",
|
||||||
ttl: "The max TTL for the template",
|
ttl: "The max TTL for the template.",
|
||||||
keyUsages: "The key usage constraint or default value for when template is used during certificate issuance",
|
keyUsages: "The key usage constraint or default value for when template is used during certificate issuance.",
|
||||||
extendedKeyUsages:
|
extendedKeyUsages:
|
||||||
"The extended key usage constraint or default value for when template is used during certificate issuance"
|
"The extended key usage constraint or default value for when template is used during certificate issuance."
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
certificateTemplateId: "The ID of the certificate template to get"
|
certificateTemplateId: "The ID of the certificate template to get."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
certificateTemplateId: "The ID of the certificate template to update",
|
certificateTemplateId: "The ID of the certificate template to update.",
|
||||||
caId: "The ID of the certificate authority to update the association with the template",
|
caId: "The ID of the certificate authority to update the association with the template.",
|
||||||
pkiCollectionId: "The ID of the PKI collection to update the binding to the template",
|
pkiCollectionId: "The ID of the PKI collection to update the binding to the template.",
|
||||||
name: "The updated name of the template",
|
name: "The updated name of the template.",
|
||||||
commonName: "The updated regular expression string for validating common names",
|
commonName: "The updated regular expression string for validating common names.",
|
||||||
subjectAlternativeName: "The updated regular expression string for validating subject alternative names",
|
subjectAlternativeName: "The updated regular expression string for validating subject alternative names.",
|
||||||
ttl: "The updated max TTL for the template",
|
ttl: "The updated max TTL for the template.",
|
||||||
keyUsages:
|
keyUsages:
|
||||||
"The updated key usage constraint or default value for when template is used during certificate issuance",
|
"The updated key usage constraint or default value for when template is used during certificate issuance.",
|
||||||
extendedKeyUsages:
|
extendedKeyUsages:
|
||||||
"The updated extended key usage constraint or default value for when template is used during certificate issuance"
|
"The updated extended key usage constraint or default value for when template is used during certificate issuance."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
certificateTemplateId: "The ID of the certificate template to delete"
|
certificateTemplateId: "The ID of the certificate template to delete."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CA_CRLS = {
|
export const CA_CRLS = {
|
||||||
GET: {
|
GET: {
|
||||||
crlId: "The ID of the certificate revocation list (CRL) to get",
|
crlId: "The ID of the certificate revocation list (CRL) to get.",
|
||||||
crl: "The certificate revocation list (CRL)"
|
crl: "The certificate revocation list (CRL)."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ALERTS = {
|
export const ALERTS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the alert in",
|
projectId: "The ID of the project to create the alert in.",
|
||||||
pkiCollectionId: "The ID of the PKI collection to bind to the alert",
|
pkiCollectionId: "The ID of the PKI collection to bind to the alert.",
|
||||||
name: "The name of the alert",
|
name: "The name of the alert.",
|
||||||
alertBeforeDays: "The number of days before the certificate expires to trigger the alert",
|
alertBeforeDays: "The number of days before the certificate expires to trigger the alert.",
|
||||||
emails: "The email addresses to send the alert email to"
|
emails: "The email addresses to send the alert email to."
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
alertId: "The ID of the alert to get"
|
alertId: "The ID of the alert to get."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
alertId: "The ID of the alert to update",
|
alertId: "The ID of the alert to update.",
|
||||||
name: "The name of the alert to update to",
|
name: "The name of the alert to update to.",
|
||||||
alertBeforeDays: "The number of days before the certificate expires to trigger the alert to update to",
|
alertBeforeDays: "The number of days before the certificate expires to trigger the alert to update to.",
|
||||||
pkiCollectionId: "The ID of the PKI collection to bind to the alert to update to",
|
pkiCollectionId: "The ID of the PKI collection to bind to the alert to update to.",
|
||||||
emails: "The email addresses to send the alert email to update to"
|
emails: "The email addresses to send the alert email to update to."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
alertId: "The ID of the alert to delete"
|
alertId: "The ID of the alert to delete."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PKI_COLLECTIONS = {
|
export const PKI_COLLECTIONS = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the PKI collection in",
|
projectId: "The ID of the project to create the PKI collection in.",
|
||||||
name: "The name of the PKI collection",
|
name: "The name of the PKI collection.",
|
||||||
description: "A description for the PKI collection"
|
description: "A description for the PKI collection."
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
collectionId: "The ID of the PKI collection to get"
|
collectionId: "The ID of the PKI collection to get."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
collectionId: "The ID of the PKI collection to update",
|
collectionId: "The ID of the PKI collection to update.",
|
||||||
name: "The name of the PKI collection to update to",
|
name: "The name of the PKI collection to update to.",
|
||||||
description: "The description for the PKI collection to update to"
|
description: "The description for the PKI collection to update to."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
collectionId: "The ID of the PKI collection to delete"
|
collectionId: "The ID of the PKI collection to delete."
|
||||||
},
|
},
|
||||||
LIST_ITEMS: {
|
LIST_ITEMS: {
|
||||||
collectionId: "The ID of the PKI collection to list items from",
|
collectionId: "The ID of the PKI collection to list items from.",
|
||||||
type: "The type of the PKI collection item to list",
|
type: "The type of the PKI collection item to list.",
|
||||||
offset: "The offset to start from",
|
offset: "The offset to start from.",
|
||||||
limit: "The number of items to return"
|
limit: "The number of items to return."
|
||||||
},
|
},
|
||||||
ADD_ITEM: {
|
ADD_ITEM: {
|
||||||
collectionId: "The ID of the PKI collection to add the item to",
|
collectionId: "The ID of the PKI collection to add the item to.",
|
||||||
type: "The type of the PKI collection item to add",
|
type: "The type of the PKI collection item to add.",
|
||||||
itemId: "The resource ID of the PKI collection item to add"
|
itemId: "The resource ID of the PKI collection item to add."
|
||||||
},
|
},
|
||||||
DELETE_ITEM: {
|
DELETE_ITEM: {
|
||||||
collectionId: "The ID of the PKI collection to delete the item from",
|
collectionId: "The ID of the PKI collection to delete the item from.",
|
||||||
collectionItemId: "The ID of the PKI collection item to delete",
|
collectionItemId: "The ID of the PKI collection item to delete.",
|
||||||
type: "The type of the deleted PKI collection item",
|
type: "The type of the deleted PKI collection item.",
|
||||||
itemId: "The resource ID of the deleted PKI collection item"
|
itemId: "The resource ID of the deleted PKI collection item."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PROJECT_ROLE = {
|
export const PROJECT_ROLE = {
|
||||||
CREATE: {
|
CREATE: {
|
||||||
projectSlug: "Slug of the project to create the role for.",
|
projectSlug: "Slug of the project to create the role for.",
|
||||||
|
projectId: "Id of the project to create the role for.",
|
||||||
slug: "The slug of the role.",
|
slug: "The slug of the role.",
|
||||||
name: "The name of the role.",
|
name: "The name of the role.",
|
||||||
description: "The description for the role.",
|
description: "The description for the role.",
|
||||||
permissions: "The permissions assigned to the role."
|
permissions: "The permissions assigned to the role."
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
projectSlug: "Slug of the project to update the role for.",
|
projectSlug: "The slug of the project to update the role for.",
|
||||||
|
projectId: "The ID of the project to update the role for.",
|
||||||
roleId: "The ID of the role to update",
|
roleId: "The ID of the role to update",
|
||||||
slug: "The slug of the role.",
|
slug: "The slug of the role.",
|
||||||
name: "The name of the role.",
|
name: "The name of the role.",
|
||||||
@ -1340,15 +1390,18 @@ export const PROJECT_ROLE = {
|
|||||||
permissions: "The permissions assigned to the role."
|
permissions: "The permissions assigned to the role."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
projectSlug: "Slug of the project to delete this role for.",
|
projectSlug: "The slug of the project to delete this role for.",
|
||||||
|
projectId: "The ID of the project to delete the role for.",
|
||||||
roleId: "The ID of the role to update"
|
roleId: "The ID of the role to update"
|
||||||
},
|
},
|
||||||
GET_ROLE_BY_SLUG: {
|
GET_ROLE_BY_SLUG: {
|
||||||
projectSlug: "The slug of the project.",
|
projectSlug: "The slug of the project.",
|
||||||
roleSlug: "The slug of the role to get details"
|
projectId: "The ID of the project.",
|
||||||
|
roleSlug: "The slug of the role to get details."
|
||||||
},
|
},
|
||||||
LIST: {
|
LIST: {
|
||||||
projectSlug: "The slug of the project to list the roles of."
|
projectSlug: "The slug of the project to list the roles of.",
|
||||||
|
projectId: "The ID of the project."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,111 +0,0 @@
|
|||||||
import { AnyAbility, ExtractSubjectType } from "@casl/ability";
|
|
||||||
import { AbilityQuery, rulesToQuery } from "@casl/ability/extra";
|
|
||||||
import { Tables } from "knex/types/tables";
|
|
||||||
|
|
||||||
import { BadRequestError, UnauthorizedError } from "../errors";
|
|
||||||
import { TKnexDynamicOperator } from "../knex/dynamic";
|
|
||||||
|
|
||||||
type TBuildKnexQueryFromCaslDTO<K extends AnyAbility> = {
|
|
||||||
ability: K;
|
|
||||||
subject: ExtractSubjectType<Parameters<K["rulesFor"]>[1]>;
|
|
||||||
action: Parameters<K["rulesFor"]>[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildKnexQueryFromCaslOperators = <K extends AnyAbility>({
|
|
||||||
ability,
|
|
||||||
subject,
|
|
||||||
action
|
|
||||||
}: TBuildKnexQueryFromCaslDTO<K>) => {
|
|
||||||
const query = rulesToQuery(ability, action, subject, (rule) => {
|
|
||||||
if (!rule.ast) throw new Error("Ast not defined");
|
|
||||||
return rule.ast;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (query === null) throw new UnauthorizedError({ message: `You don't have permission to do ${action} ${subject}` });
|
|
||||||
return query;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TFieldMapper<T extends keyof Tables> = {
|
|
||||||
[K in T]: `${K}.${Exclude<keyof Tables[K]["base"], symbol>}`;
|
|
||||||
}[T];
|
|
||||||
|
|
||||||
type TFormatCaslFieldsWithTableNames<T extends keyof Tables> = {
|
|
||||||
// handle if any missing operator else throw error let the app break because this is executing again the db
|
|
||||||
missingOperatorCallback?: (operator: string) => void;
|
|
||||||
fieldMapping: (arg: string) => TFieldMapper<T> | null;
|
|
||||||
dynamicQuery: TKnexDynamicOperator;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatCaslOperatorFieldsWithTableNames = <T extends keyof Tables>({
|
|
||||||
missingOperatorCallback = (arg) => {
|
|
||||||
throw new BadRequestError({ message: `Unknown permission operator: ${arg}` });
|
|
||||||
},
|
|
||||||
dynamicQuery: dynamicQueryAst,
|
|
||||||
fieldMapping
|
|
||||||
}: TFormatCaslFieldsWithTableNames<T>) => {
|
|
||||||
const stack: [TKnexDynamicOperator, TKnexDynamicOperator | null][] = [[dynamicQueryAst, null]];
|
|
||||||
|
|
||||||
while (stack.length) {
|
|
||||||
const [filterAst, parentAst] = stack.pop()!;
|
|
||||||
|
|
||||||
if (filterAst.operator === "and" || filterAst.operator === "or" || filterAst.operator === "not") {
|
|
||||||
filterAst.value.forEach((el) => {
|
|
||||||
stack.push([el, filterAst]);
|
|
||||||
});
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
filterAst.operator === "eq" ||
|
|
||||||
filterAst.operator === "ne" ||
|
|
||||||
filterAst.operator === "in" ||
|
|
||||||
filterAst.operator === "endsWith" ||
|
|
||||||
filterAst.operator === "startsWith"
|
|
||||||
) {
|
|
||||||
const attrPath = fieldMapping(filterAst.field);
|
|
||||||
if (attrPath) {
|
|
||||||
filterAst.field = attrPath;
|
|
||||||
} else if (parentAst && Array.isArray(parentAst.value)) {
|
|
||||||
parentAst.value = parentAst.value.filter((childAst) => childAst !== filterAst) as string[];
|
|
||||||
} else throw new Error("Unknown casl field");
|
|
||||||
// eslint-disable-next-line no-continue
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parentAst && Array.isArray(parentAst.value)) {
|
|
||||||
parentAst.value = parentAst.value.filter((childAst) => childAst !== filterAst) as string[];
|
|
||||||
} else {
|
|
||||||
missingOperatorCallback?.(filterAst.operator);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dynamicQueryAst;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const convertCaslOperatorToKnexOperator = <T extends keyof Tables>(
|
|
||||||
caslKnexOperators: AbilityQuery,
|
|
||||||
fieldMapping: (arg: string) => TFieldMapper<T> | null
|
|
||||||
) => {
|
|
||||||
const value = [];
|
|
||||||
if (caslKnexOperators.$and) {
|
|
||||||
value.push({
|
|
||||||
operator: "not" as const,
|
|
||||||
value: caslKnexOperators.$and as TKnexDynamicOperator[]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (caslKnexOperators.$or) {
|
|
||||||
value.push({
|
|
||||||
operator: "or" as const,
|
|
||||||
value: caslKnexOperators.$or as TKnexDynamicOperator[]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatCaslOperatorFieldsWithTableNames({
|
|
||||||
dynamicQuery: {
|
|
||||||
operator: "and",
|
|
||||||
value
|
|
||||||
},
|
|
||||||
fieldMapping
|
|
||||||
});
|
|
||||||
};
|
|
@ -162,7 +162,8 @@ const envSchema = z
|
|||||||
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false"),
|
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false"),
|
||||||
SSL_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default("x-ssl-client-cert"),
|
SSL_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default("x-ssl-client-cert"),
|
||||||
WORKFLOW_SLACK_CLIENT_ID: zpStr(z.string().optional()),
|
WORKFLOW_SLACK_CLIENT_ID: zpStr(z.string().optional()),
|
||||||
WORKFLOW_SLACK_CLIENT_SECRET: zpStr(z.string().optional())
|
WORKFLOW_SLACK_CLIENT_SECRET: zpStr(z.string().optional()),
|
||||||
|
ENABLE_MSSQL_SECRET_ROTATION_ENCRYPT: zodStrBool.default("true")
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
@ -81,3 +81,25 @@ export const chunkArray = <T>(array: T[], chunkSize: number): T[][] => {
|
|||||||
}
|
}
|
||||||
return chunks;
|
return chunks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns all items from the first list that
|
||||||
|
* do not exist in the second list.
|
||||||
|
*/
|
||||||
|
export const diff = <T>(
|
||||||
|
root: readonly T[],
|
||||||
|
other: readonly T[],
|
||||||
|
identity: (item: T) => string | number | symbol = (t: T) => t as unknown as string | number | symbol
|
||||||
|
): T[] => {
|
||||||
|
if (!root?.length && !other?.length) return [];
|
||||||
|
if (root?.length === undefined) return [...other];
|
||||||
|
if (!other?.length) return [...root];
|
||||||
|
const bKeys = other.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[identity(item)] = true;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string | number | symbol, boolean>
|
||||||
|
);
|
||||||
|
return root.filter((a) => !bKeys[identity(a)]);
|
||||||
|
};
|
||||||
|
@ -2,32 +2,31 @@ import { Knex } from "knex";
|
|||||||
|
|
||||||
import { UnauthorizedError } from "../errors";
|
import { UnauthorizedError } from "../errors";
|
||||||
|
|
||||||
type TKnexDynamicPrimitiveOperator = {
|
type TKnexDynamicPrimitiveOperator<T extends object> = {
|
||||||
operator: "eq" | "ne" | "startsWith" | "endsWith";
|
operator: "eq" | "ne" | "startsWith" | "endsWith";
|
||||||
value: string;
|
value: string;
|
||||||
field: string;
|
field: Extract<keyof T, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TKnexDynamicInOperator = {
|
type TKnexDynamicInOperator<T extends object> = {
|
||||||
operator: "in";
|
operator: "in";
|
||||||
value: string[] | number[];
|
value: string[] | number[];
|
||||||
field: string;
|
field: Extract<keyof T, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TKnexNonGroupOperator = TKnexDynamicInOperator | TKnexDynamicPrimitiveOperator;
|
type TKnexNonGroupOperator<T extends object> = TKnexDynamicInOperator<T> | TKnexDynamicPrimitiveOperator<T>;
|
||||||
|
|
||||||
type TKnexGroupOperator = {
|
type TKnexGroupOperator<T extends object> = {
|
||||||
operator: "and" | "or" | "not";
|
operator: "and" | "or" | "not";
|
||||||
value: (TKnexNonGroupOperator | TKnexGroupOperator)[];
|
value: (TKnexNonGroupOperator<T> | TKnexGroupOperator<T>)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// akhilmhdh: This is still in pending state and not yet ready. If you want to use it ping me.
|
export type TKnexDynamicOperator<T extends object> = TKnexGroupOperator<T> | TKnexNonGroupOperator<T>;
|
||||||
// used when you need to write a complex query with the orm
|
|
||||||
// use it when you need complex or and and condition - most of the time not needed
|
|
||||||
// majorly used with casl permission to filter data based on permission
|
|
||||||
export type TKnexDynamicOperator = TKnexGroupOperator | TKnexNonGroupOperator;
|
|
||||||
|
|
||||||
export const buildDynamicKnexQuery = (dynamicQuery: TKnexDynamicOperator, rootQueryBuild: Knex.QueryBuilder) => {
|
export const buildDynamicKnexQuery = <T extends object>(
|
||||||
|
rootQueryBuild: Knex.QueryBuilder,
|
||||||
|
dynamicQuery: TKnexDynamicOperator<T>
|
||||||
|
) => {
|
||||||
const stack = [{ filterAst: dynamicQuery, queryBuilder: rootQueryBuild }];
|
const stack = [{ filterAst: dynamicQuery, queryBuilder: rootQueryBuild }];
|
||||||
|
|
||||||
while (stack.length) {
|
while (stack.length) {
|
||||||
@ -50,34 +49,25 @@ export const buildDynamicKnexQuery = (dynamicQuery: TKnexDynamicOperator, rootQu
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "and": {
|
case "and": {
|
||||||
void queryBuilder.andWhere((subQueryBuilder) => {
|
filterAst.value.forEach((el) => {
|
||||||
filterAst.value.forEach((el) => {
|
void queryBuilder.andWhere((subQueryBuilder) => {
|
||||||
stack.push({
|
buildDynamicKnexQuery(subQueryBuilder, el);
|
||||||
queryBuilder: subQueryBuilder,
|
|
||||||
filterAst: el
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "or": {
|
case "or": {
|
||||||
void queryBuilder.orWhere((subQueryBuilder) => {
|
filterAst.value.forEach((el) => {
|
||||||
filterAst.value.forEach((el) => {
|
void queryBuilder.orWhere((subQueryBuilder) => {
|
||||||
stack.push({
|
buildDynamicKnexQuery(subQueryBuilder, el);
|
||||||
queryBuilder: subQueryBuilder,
|
|
||||||
filterAst: el
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "not": {
|
case "not": {
|
||||||
void queryBuilder.whereNot((subQueryBuilder) => {
|
filterAst.value.forEach((el) => {
|
||||||
filterAst.value.forEach((el) => {
|
void queryBuilder.whereNot((subQueryBuilder) => {
|
||||||
stack.push({
|
buildDynamicKnexQuery(subQueryBuilder, el);
|
||||||
queryBuilder: subQueryBuilder,
|
|
||||||
filterAst: el
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -3,6 +3,7 @@ import { Knex } from "knex";
|
|||||||
import { Tables } from "knex/types/tables";
|
import { Tables } from "knex/types/tables";
|
||||||
|
|
||||||
import { DatabaseError } from "../errors";
|
import { DatabaseError } from "../errors";
|
||||||
|
import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
||||||
|
|
||||||
export * from "./connection";
|
export * from "./connection";
|
||||||
export * from "./join";
|
export * from "./join";
|
||||||
@ -20,9 +21,10 @@ export const withTransaction = <K extends object>(db: Knex, dal: K) => ({
|
|||||||
export type TFindFilter<R extends object = object> = Partial<R> & {
|
export type TFindFilter<R extends object = object> = Partial<R> & {
|
||||||
$in?: Partial<{ [k in keyof R]: R[k][] }>;
|
$in?: Partial<{ [k in keyof R]: R[k][] }>;
|
||||||
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
||||||
|
$complex?: TKnexDynamicOperator<R>;
|
||||||
};
|
};
|
||||||
export const buildFindFilter =
|
export const buildFindFilter =
|
||||||
<R extends object = object>({ $in, $search, ...filter }: TFindFilter<R>) =>
|
<R extends object = object>({ $in, $search, $complex, ...filter }: TFindFilter<R>) =>
|
||||||
(bd: Knex.QueryBuilder<R, R>) => {
|
(bd: Knex.QueryBuilder<R, R>) => {
|
||||||
void bd.where(filter);
|
void bd.where(filter);
|
||||||
if ($in) {
|
if ($in) {
|
||||||
@ -39,6 +41,9 @@ export const buildFindFilter =
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if ($complex) {
|
||||||
|
return buildDynamicKnexQuery(bd, $complex);
|
||||||
|
}
|
||||||
return bd;
|
return bd;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -57,3 +57,10 @@ export enum OrderByDirection {
|
|||||||
ASC = "asc",
|
ASC = "asc",
|
||||||
DESC = "desc"
|
DESC = "desc"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ProjectServiceActor = {
|
||||||
|
type: ActorType;
|
||||||
|
id: string;
|
||||||
|
authMethod: ActorAuthMethod;
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
@ -18,6 +18,7 @@ export type TAuthMode =
|
|||||||
user: TUsers;
|
user: TUsers;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
authMethod: AuthMethod;
|
authMethod: AuthMethod;
|
||||||
|
isMfaVerified?: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
authMode: AuthMode.API_KEY;
|
authMode: AuthMode.API_KEY;
|
||||||
@ -121,7 +122,8 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
|
|||||||
tokenVersionId,
|
tokenVersionId,
|
||||||
actor,
|
actor,
|
||||||
orgId: orgId as string,
|
orgId: orgId as string,
|
||||||
authMethod: token.authMethod
|
authMethod: token.authMethod,
|
||||||
|
isMfaVerified: token.isMfaVerified
|
||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
void res.status(HttpStatusCodes.Forbidden).send({
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
statusCode: HttpStatusCodes.Forbidden,
|
statusCode: HttpStatusCodes.Forbidden,
|
||||||
error: "PermissionDenied",
|
error: "PermissionDenied",
|
||||||
message: `You are not allowed to ${error.action} on ${error.subjectType}`
|
message: `You are not allowed to ${error.action} on ${error.subjectType} - ${JSON.stringify(error.subject)}`
|
||||||
});
|
});
|
||||||
} else if (error instanceof ForbiddenRequestError) {
|
} else if (error instanceof ForbiddenRequestError) {
|
||||||
void res.status(HttpStatusCodes.Forbidden).send({
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
|
@ -15,8 +15,12 @@ export const fastifySwagger = fp(async (fastify) => {
|
|||||||
},
|
},
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
url: "https://app.infisical.com",
|
url: "https://us.infisical.com",
|
||||||
description: "Production server"
|
description: "Production server (US)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: "https://eu.infisical.com",
|
||||||
|
description: "Production server (EU)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: "http://localhost:8080",
|
url: "http://localhost:8080",
|
||||||
|
@ -5,6 +5,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { registerCertificateEstRouter } from "@app/ee/routes/est/certificate-est-router";
|
import { registerCertificateEstRouter } from "@app/ee/routes/est/certificate-est-router";
|
||||||
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
import { registerV1EERoutes } from "@app/ee/routes/v1";
|
||||||
|
import { registerV2EERoutes } from "@app/ee/routes/v2";
|
||||||
import { accessApprovalPolicyApproverDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
|
import { accessApprovalPolicyApproverDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-approver-dal";
|
||||||
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
|
import { accessApprovalPolicyDALFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-dal";
|
||||||
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||||
@ -32,6 +33,7 @@ import { groupServiceFactory } from "@app/ee/services/group/group-service";
|
|||||||
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal";
|
import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-dal";
|
||||||
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
|
import { identityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
|
||||||
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
|
||||||
import { ldapGroupMapDALFactory } from "@app/ee/services/ldap-config/ldap-group-map-dal";
|
import { ldapGroupMapDALFactory } from "@app/ee/services/ldap-config/ldap-group-map-dal";
|
||||||
@ -1075,9 +1077,16 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
identityProjectDAL
|
identityProjectDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const identityProjectAdditionalPrivilegeV2Service = identityProjectAdditionalPrivilegeV2ServiceFactory({
|
||||||
|
projectDAL,
|
||||||
|
identityProjectAdditionalPrivilegeDAL,
|
||||||
|
permissionService,
|
||||||
|
identityProjectDAL
|
||||||
|
});
|
||||||
|
|
||||||
const identityTokenAuthService = identityTokenAuthServiceFactory({
|
const identityTokenAuthService = identityTokenAuthServiceFactory({
|
||||||
identityTokenAuthDAL,
|
identityTokenAuthDAL,
|
||||||
identityDAL,
|
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
@ -1086,7 +1095,6 @@ export const registerRoutes = async (
|
|||||||
const identityUaService = identityUaServiceFactory({
|
const identityUaService = identityUaServiceFactory({
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
identityDAL,
|
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityUaClientSecretDAL,
|
identityUaClientSecretDAL,
|
||||||
identityUaDAL,
|
identityUaDAL,
|
||||||
@ -1096,7 +1104,6 @@ export const registerRoutes = async (
|
|||||||
identityKubernetesAuthDAL,
|
identityKubernetesAuthDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityDAL,
|
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService
|
||||||
@ -1105,7 +1112,6 @@ export const registerRoutes = async (
|
|||||||
identityGcpAuthDAL,
|
identityGcpAuthDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityDAL,
|
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
@ -1114,7 +1120,6 @@ export const registerRoutes = async (
|
|||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityAwsAuthDAL,
|
identityAwsAuthDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityDAL,
|
|
||||||
licenseService,
|
licenseService,
|
||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
@ -1123,7 +1128,6 @@ export const registerRoutes = async (
|
|||||||
identityAzureAuthDAL,
|
identityAzureAuthDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityDAL,
|
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
@ -1132,7 +1136,6 @@ export const registerRoutes = async (
|
|||||||
identityOidcAuthDAL,
|
identityOidcAuthDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityDAL,
|
|
||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService,
|
||||||
orgBotDAL
|
orgBotDAL
|
||||||
@ -1324,6 +1327,7 @@ export const registerRoutes = async (
|
|||||||
telemetry: telemetryService,
|
telemetry: telemetryService,
|
||||||
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
|
projectUserAdditionalPrivilege: projectUserAdditionalPrivilegeService,
|
||||||
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
|
identityProjectAdditionalPrivilege: identityProjectAdditionalPrivilegeService,
|
||||||
|
identityProjectAdditionalPrivilegeV2: identityProjectAdditionalPrivilegeV2Service,
|
||||||
secretSharing: secretSharingService,
|
secretSharing: secretSharingService,
|
||||||
userEngagement: userEngagementService,
|
userEngagement: userEngagementService,
|
||||||
externalKms: externalKmsService,
|
externalKms: externalKmsService,
|
||||||
@ -1422,7 +1426,13 @@ export const registerRoutes = async (
|
|||||||
},
|
},
|
||||||
{ prefix: "/api/v1" }
|
{ prefix: "/api/v1" }
|
||||||
);
|
);
|
||||||
await server.register(registerV2Routes, { prefix: "/api/v2" });
|
await server.register(
|
||||||
|
async (v2Server) => {
|
||||||
|
await v2Server.register(registerV2EERoutes);
|
||||||
|
await v2Server.register(registerV2Routes);
|
||||||
|
},
|
||||||
|
{ prefix: "/api/v2" }
|
||||||
|
);
|
||||||
await server.register(registerV3Routes, { prefix: "/api/v3" });
|
await server.register(registerV3Routes, { prefix: "/api/v3" });
|
||||||
|
|
||||||
server.addHook("onClose", async () => {
|
server.addHook("onClose", async () => {
|
||||||
|
@ -9,9 +9,10 @@ import {
|
|||||||
SecretApprovalPoliciesSchema,
|
SecretApprovalPoliciesSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { UnpackedPermissionSchema } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
|
|
||||||
|
import { UnpackedPermissionSchema } from "./santizedSchemas/permission";
|
||||||
|
|
||||||
// sometimes the return data must be santizied to avoid leaking important values
|
// sometimes the return data must be santizied to avoid leaking important values
|
||||||
// always prefer pick over omit in zod
|
// always prefer pick over omit in zod
|
||||||
export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
|
export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
|
||||||
@ -149,13 +150,44 @@ export const ProjectSpecificPrivilegePermissionSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedIdentityPrivilegeSchema = IdentityProjectAdditionalPrivilegeSchema.extend({
|
export const SanitizedIdentityPrivilegeSchema = IdentityProjectAdditionalPrivilegeSchema.extend({
|
||||||
permissions: UnpackedPermissionSchema.array()
|
permissions: UnpackedPermissionSchema.array().transform((permissions) =>
|
||||||
|
permissions.filter(
|
||||||
|
(caslRule) =>
|
||||||
|
![
|
||||||
|
ProjectPermissionSub.DynamicSecrets,
|
||||||
|
ProjectPermissionSub.SecretImports,
|
||||||
|
ProjectPermissionSub.SecretFolders
|
||||||
|
].includes((caslRule?.subject as ProjectPermissionSub) || "")
|
||||||
|
)
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const SanitizedRoleSchema = ProjectRolesSchema.extend({
|
export const SanitizedRoleSchema = ProjectRolesSchema.extend({
|
||||||
permissions: UnpackedPermissionSchema.array()
|
permissions: UnpackedPermissionSchema.array()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const SanitizedRoleSchemaV1 = ProjectRolesSchema.extend({
|
||||||
|
permissions: UnpackedPermissionSchema.array().transform((caslPermission) =>
|
||||||
|
// first map and remove other actions of folder permission
|
||||||
|
caslPermission
|
||||||
|
.map((caslRule) =>
|
||||||
|
caslRule.subject === ProjectPermissionSub.SecretFolders
|
||||||
|
? {
|
||||||
|
...caslRule,
|
||||||
|
action: caslRule.action.filter((caslAction) => caslAction === ProjectPermissionActions.Read)
|
||||||
|
}
|
||||||
|
: caslRule
|
||||||
|
)
|
||||||
|
// now filter out dynamic secret, secret import permission
|
||||||
|
.filter(
|
||||||
|
(caslRule) =>
|
||||||
|
![ProjectPermissionSub.DynamicSecrets, ProjectPermissionSub.SecretImports].includes(
|
||||||
|
(caslRule?.subject as ProjectPermissionSub) || ""
|
||||||
|
) && caslRule.action.length > 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
|
export const SanitizedDynamicSecretSchema = DynamicSecretsSchema.omit({
|
||||||
inputIV: true,
|
inputIV: true,
|
||||||
inputTag: true,
|
inputTag: true,
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
|
import { UnpackedPermissionSchema } from "./permission";
|
||||||
|
|
||||||
|
export const SanitizedIdentityPrivilegeSchema = IdentityProjectAdditionalPrivilegeSchema.extend({
|
||||||
|
permissions: UnpackedPermissionSchema.array()
|
||||||
|
});
|
16
backend/src/server/routes/santizedSchemas/permission.ts
Normal file
16
backend/src/server/routes/santizedSchemas/permission.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
|
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const UnpackedPermissionSchema = z.object({
|
||||||
|
subject: z
|
||||||
|
.union([z.string().min(1), z.string().array()])
|
||||||
|
.transform((el) => (typeof el !== "string" ? el[0] : el))
|
||||||
|
.optional(),
|
||||||
|
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
|
||||||
|
conditions: z.unknown().optional(),
|
||||||
|
inverted: z.boolean().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const unpackPermissions = (permissions: unknown) =>
|
||||||
|
UnpackedPermissionSchema.array().parse(unpackRules((permissions || []) as PackRule<RawRuleOf<MongoAbility>>[]));
|
@ -0,0 +1,7 @@
|
|||||||
|
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
|
|
||||||
|
import { UnpackedPermissionSchema } from "./permission";
|
||||||
|
|
||||||
|
export const SanitizedUserProjectAdditionalPrivilegeSchema = ProjectUserAdditionalPrivilegeSchema.extend({
|
||||||
|
permissions: UnpackedPermissionSchema.array()
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user