mirror of
https://github.com/Infisical/infisical.git
synced 2025-08-24 20:43:19 +00:00
Compare commits
199 Commits
doc/add-gr
...
daniel/inc
Author | SHA1 | Date | |
---|---|---|---|
|
acf054d992 | ||
|
f50b0876e4 | ||
|
c30763c98f | ||
|
6fc95c3ff8 | ||
|
eef1f2b6ef | ||
|
128b1cf856 | ||
|
6b9944001e | ||
|
1cc22a6195 | ||
|
af643468fd | ||
|
f8358a0807 | ||
|
3eefb98f30 | ||
|
8f39f953f8 | ||
|
5e4af7e568 | ||
|
24bd13403a | ||
|
4149cbdf07 | ||
|
ced3ab97e8 | ||
|
20bcf8aab8 | ||
|
0814245ce6 | ||
|
2d2f27ea46 | ||
|
4aeb2bf65e | ||
|
24da76db19 | ||
|
3c49936eee | ||
|
b416e79d63 | ||
|
92c529587b | ||
|
3b74c232dc | ||
|
6164dc32d7 | ||
|
37e7040eea | ||
|
a7ebb4b241 | ||
|
2fc562ff2d | ||
|
b5c83fea4d | ||
|
b586f98926 | ||
|
e6205c086f | ||
|
2ca34099ed | ||
|
5da6c12941 | ||
|
e2612b75fc | ||
|
ca5edb95f1 | ||
|
724e2b3692 | ||
|
2c93561a3b | ||
|
0b24cc8631 | ||
|
6c6e932899 | ||
|
c66a711890 | ||
|
787f8318fe | ||
|
9a27873af5 | ||
|
0abab57d83 | ||
|
d5662dfef4 | ||
|
ee2ee48b47 | ||
|
896d977b95 | ||
|
d1966b60a8 | ||
|
e3cbcf5853 | ||
|
bdf1f7c601 | ||
|
24b23d4f90 | ||
|
09c1a5f778 | ||
|
73a9cf01f3 | ||
|
97e860cf21 | ||
|
25b55087cf | ||
|
25f694bbdb | ||
|
7cd85cf84a | ||
|
cf5c886b6f | ||
|
e667c7c988 | ||
|
fd254fbeec | ||
|
859c556425 | ||
|
a3cad030e5 | ||
|
342e9f99d3 | ||
|
8ed04d0b75 | ||
|
5b5a8ff03f | ||
|
e0199084ad | ||
|
67a6deed72 | ||
|
355113e15d | ||
|
40c589eced | ||
|
ec4f175f73 | ||
|
2273c21eb2 | ||
|
97c2b15e29 | ||
|
2f90ee067b | ||
|
7b64288019 | ||
|
e6e1ed7ca9 | ||
|
73838190fd | ||
|
d32fad87d1 | ||
|
67db9679fa | ||
|
3edd48a8b3 | ||
|
a4091bfcdd | ||
|
24483631a0 | ||
|
0f74a1a011 | ||
|
62d6e3763b | ||
|
39ea7a032f | ||
|
3ac125f9c7 | ||
|
7667a7e665 | ||
|
d7499fc5c5 | ||
|
f6885b239b | ||
|
4928322cdb | ||
|
77e191d63e | ||
|
15c98a1d2e | ||
|
ed757bdeff | ||
|
65241ad8bf | ||
|
6a7760f33f | ||
|
fdc62e21ef | ||
|
32f866f834 | ||
|
fbf52850e8 | ||
|
ab9b207f96 | ||
|
5532b9cfea | ||
|
449d3f0304 | ||
|
f0210c2607 | ||
|
ad88aaf17f | ||
|
0485b56e8d | ||
|
b65842f5c1 | ||
|
22b6e0afcd | ||
|
b0e536e576 | ||
|
54e4314e88 | ||
|
d00b1847cc | ||
|
be02617855 | ||
|
b5065f13c9 | ||
|
659b6d5d19 | ||
|
9c33251c44 | ||
|
1a0896475c | ||
|
7e820745a4 | ||
|
fa63c150dd | ||
|
1a2495a95c | ||
|
d79099946a | ||
|
27afad583b | ||
|
acde0867a0 | ||
|
d44f99bac2 | ||
|
2b35e20b1d | ||
|
da15957c3f | ||
|
208fc3452d | ||
|
ba1db870a4 | ||
|
7885a3b0ff | ||
|
66485f0464 | ||
|
0741058c1d | ||
|
3a6e79c575 | ||
|
70aa73482e | ||
|
2fa30bdd0e | ||
|
b28fe30bba | ||
|
9ba39e99c6 | ||
|
0e6aed7497 | ||
|
7e11fbe7a3 | ||
|
23abab987f | ||
|
a44b3efeb7 | ||
|
1992a09ac2 | ||
|
efa54e0c46 | ||
|
bde2d5e0a6 | ||
|
4090c894fc | ||
|
221bde01f8 | ||
|
b191a3c2f4 | ||
|
032197ee9f | ||
|
d5a4eb609a | ||
|
e7f1980b80 | ||
|
d430293c66 | ||
|
180d2692cd | ||
|
433e58655a | ||
|
5ffb6b7232 | ||
|
55ca9149d5 | ||
|
4ea57ca9a0 | ||
|
7ac4b0b79f | ||
|
2d51ed317f | ||
|
02c51b05b6 | ||
|
cd09f03f0b | ||
|
bc475e0f08 | ||
|
441b008709 | ||
|
4d81a0251e | ||
|
59da513481 | ||
|
c17047a193 | ||
|
f50a881273 | ||
|
afd6dd5257 | ||
|
3a43d7c5d5 | ||
|
65375886bd | ||
|
8495107849 | ||
|
c011d99b8b | ||
|
adc3542750 | ||
|
82e3241f1b | ||
|
2bca46886a | ||
|
971987c786 | ||
|
cd71a13bb7 | ||
|
98290fe31b | ||
|
9f15fb1474 | ||
|
301a867f8b | ||
|
658a044e85 | ||
|
2c1e29445d | ||
|
3f4c4f7418 | ||
|
592cc13b1f | ||
|
e70c2f3d10 | ||
|
bac865eab1 | ||
|
3d8fbc0a58 | ||
|
1fcfab7efa | ||
|
499334eef1 | ||
|
9fd76b8729 | ||
|
80d450e980 | ||
|
a1f2629366 | ||
|
bf8e1f2bfd | ||
|
f7d10ceeda | ||
|
095883a94e | ||
|
51638b7c71 | ||
|
adaddad370 | ||
|
cf6ff58f16 | ||
|
974e21d856 | ||
|
6733349af0 | ||
|
f63c6b725b | ||
|
14c89c9be5 | ||
|
0df80c5b2d | ||
|
c577f51c19 | ||
|
24d121ab59 |
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
- [ ] Bug fix
|
- [ ] Bug fix
|
||||||
- [ ] New feature
|
- [ ] New feature
|
||||||
|
- [ ] Improvement
|
||||||
- [ ] Breaking change
|
- [ ] Breaking change
|
||||||
- [ ] Documentation
|
- [ ] Documentation
|
||||||
|
|
||||||
|
@@ -73,6 +73,11 @@ We're on a mission to make security tooling more accessible to everyone, not jus
|
|||||||
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
|
- **[Infisical PKI Issuer for Kubernetes](https://infisical.com/docs/documentation/platform/pki/pki-issuer)**: Deliver TLS certificates to your Kubernetes workloads with automatic renewal.
|
||||||
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
|
- **[Enrollment over Secure Transport](https://infisical.com/docs/documentation/platform/pki/est)**: Enroll and manage certificates via EST protocol.
|
||||||
|
|
||||||
|
### Key Management (KMS):
|
||||||
|
|
||||||
|
- **[Cryptograhic Keys](https://infisical.com/docs/documentation/platform/kms)**: Centrally manage keys across projects through a user-friendly interface or via the API.
|
||||||
|
- **[Encrypt and Decrypt Data](https://infisical.com/docs/documentation/platform/kms#guide-to-encrypting-data)**: Use symmetric keys to encrypt and decrypt data.
|
||||||
|
|
||||||
### General Platform:
|
### General Platform:
|
||||||
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
|
- **Authentication Methods**: Authenticate machine identities with Infisical using a cloud-native or platform agnostic authentication method ([Kubernetes Auth](https://infisical.com/docs/documentation/platform/identities/kubernetes-auth), [GCP Auth](https://infisical.com/docs/documentation/platform/identities/gcp-auth), [Azure Auth](https://infisical.com/docs/documentation/platform/identities/azure-auth), [AWS Auth](https://infisical.com/docs/documentation/platform/identities/aws-auth), [OIDC Auth](https://infisical.com/docs/documentation/platform/identities/oidc-auth/general), [Universal Auth](https://infisical.com/docs/documentation/platform/identities/universal-auth)).
|
||||||
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
|
- **[Access Controls](https://infisical.com/docs/documentation/platform/access-controls/overview)**: Define advanced authorization controls for users and machine identities with [RBAC](https://infisical.com/docs/documentation/platform/access-controls/role-based-access-controls), [additional privileges](https://infisical.com/docs/documentation/platform/access-controls/additional-privileges), [temporary access](https://infisical.com/docs/documentation/platform/access-controls/temporary-access), [access requests](https://infisical.com/docs/documentation/platform/access-controls/access-requests), [approval workflows](https://infisical.com/docs/documentation/platform/pr-workflows), and more.
|
||||||
|
@@ -510,7 +510,7 @@ describe("Service token fail cases", async () => {
|
|||||||
authorization: `Bearer ${serviceToken}`
|
authorization: `Bearer ${serviceToken}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expect(fetchSecrets.statusCode).toBe(401);
|
expect(fetchSecrets.statusCode).toBe(403);
|
||||||
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
||||||
await deleteServiceToken();
|
await deleteServiceToken();
|
||||||
});
|
});
|
||||||
@@ -532,7 +532,7 @@ describe("Service token fail cases", async () => {
|
|||||||
authorization: `Bearer ${serviceToken}`
|
authorization: `Bearer ${serviceToken}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expect(fetchSecrets.statusCode).toBe(401);
|
expect(fetchSecrets.statusCode).toBe(403);
|
||||||
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
expect(fetchSecrets.json().error).toBe("PermissionDenied");
|
||||||
await deleteServiceToken();
|
await deleteServiceToken();
|
||||||
});
|
});
|
||||||
@@ -557,7 +557,7 @@ describe("Service token fail cases", async () => {
|
|||||||
authorization: `Bearer ${serviceToken}`
|
authorization: `Bearer ${serviceToken}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
expect(writeSecrets.statusCode).toBe(401);
|
expect(writeSecrets.statusCode).toBe(403);
|
||||||
expect(writeSecrets.json().error).toBe("PermissionDenied");
|
expect(writeSecrets.json().error).toBe("PermissionDenied");
|
||||||
|
|
||||||
// but read access should still work fine
|
// but read access should still work fine
|
||||||
|
@@ -1075,7 +1075,7 @@ describe("Secret V3 Raw Router Without E2EE enabled", async () => {
|
|||||||
},
|
},
|
||||||
body: createSecretReqBody
|
body: createSecretReqBody
|
||||||
});
|
});
|
||||||
expect(createSecRes.statusCode).toBe(400);
|
expect(createSecRes.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Update secret raw", async () => {
|
test("Update secret raw", async () => {
|
||||||
@@ -1093,7 +1093,7 @@ describe("Secret V3 Raw Router Without E2EE enabled", async () => {
|
|||||||
},
|
},
|
||||||
body: updateSecretReqBody
|
body: updateSecretReqBody
|
||||||
});
|
});
|
||||||
expect(updateSecRes.statusCode).toBe(400);
|
expect(updateSecRes.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Delete secret raw", async () => {
|
test("Delete secret raw", async () => {
|
||||||
@@ -1110,6 +1110,6 @@ describe("Secret V3 Raw Router Without E2EE enabled", async () => {
|
|||||||
},
|
},
|
||||||
body: deletedSecretReqBody
|
body: deletedSecretReqBody
|
||||||
});
|
});
|
||||||
expect(deletedSecRes.statusCode).toBe(400);
|
expect(deletedSecRes.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
31
backend/package-lock.json
generated
31
backend/package-lock.json
generated
@@ -61,6 +61,7 @@
|
|||||||
"jwks-rsa": "^3.1.0",
|
"jwks-rsa": "^3.1.0",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
|
"ldif": "^0.5.1",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"mongodb": "^6.8.1",
|
"mongodb": "^6.8.1",
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
"safe-regex": "^2.1.1",
|
"safe-regex": "^2.1.1",
|
||||||
"scim-patch": "^0.8.3",
|
"scim-patch": "^0.8.3",
|
||||||
"scim2-parse-filter": "^0.2.10",
|
"scim2-parse-filter": "^0.2.10",
|
||||||
|
"sjcl": "^1.0.8",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
"tedious": "^18.2.1",
|
"tedious": "^18.2.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
@@ -117,6 +119,7 @@
|
|||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
"@types/safe-regex": "^1.1.6",
|
"@types/safe-regex": "^1.1.6",
|
||||||
|
"@types/sjcl": "^1.0.34",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
@@ -7296,6 +7299,13 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/sjcl": {
|
||||||
|
"version": "1.0.34",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sjcl/-/sjcl-1.0.34.tgz",
|
||||||
|
"integrity": "sha512-bQHEeK5DTQRunIfQeUMgtpPsNNCcZyQ9MJuAfW1I7iN0LDunTc78Fu17STbLMd7KiEY/g2zHVApippa70h6HoQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/uuid": {
|
"node_modules/@types/uuid": {
|
||||||
"version": "9.0.7",
|
"version": "9.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz",
|
||||||
@@ -13008,6 +13018,12 @@
|
|||||||
"verror": "^1.10.1"
|
"verror": "^1.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ldif": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ldif/-/ldif-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-8s46m/r2lSFO2+DqMxqWiJ10iiL4tuR5LC/KndV+E5//OAOzOx5s3HS5O34PJ5+kyaCA+K2oCaEPaDRfXUnQow==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/leven": {
|
"node_modules/leven": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
||||||
@@ -16397,6 +16413,15 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sjcl": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==",
|
||||||
|
"license": "(BSD-2-Clause OR GPL-2.0-only)",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/slash": {
|
"node_modules/slash": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
|
||||||
@@ -17874,12 +17899,14 @@
|
|||||||
"node_modules/tweetnacl": {
|
"node_modules/tweetnacl": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
||||||
|
"license": "Unlicense"
|
||||||
},
|
},
|
||||||
"node_modules/tweetnacl-util": {
|
"node_modules/tweetnacl-util": {
|
||||||
"version": "0.15.1",
|
"version": "0.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
|
||||||
"integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw=="
|
"integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
|
||||||
|
"license": "Unlicense"
|
||||||
},
|
},
|
||||||
"node_modules/type-check": {
|
"node_modules/type-check": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
|
@@ -80,6 +80,7 @@
|
|||||||
"@types/prompt-sync": "^4.2.3",
|
"@types/prompt-sync": "^4.2.3",
|
||||||
"@types/resolve": "^1.20.6",
|
"@types/resolve": "^1.20.6",
|
||||||
"@types/safe-regex": "^1.1.6",
|
"@types/safe-regex": "^1.1.6",
|
||||||
|
"@types/sjcl": "^1.0.34",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"@typescript-eslint/parser": "^6.20.0",
|
"@typescript-eslint/parser": "^6.20.0",
|
||||||
@@ -158,6 +159,7 @@
|
|||||||
"jwks-rsa": "^3.1.0",
|
"jwks-rsa": "^3.1.0",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
|
"ldif": "0.5.1",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"mongodb": "^6.8.1",
|
"mongodb": "^6.8.1",
|
||||||
@@ -182,6 +184,7 @@
|
|||||||
"safe-regex": "^2.1.1",
|
"safe-regex": "^2.1.1",
|
||||||
"scim-patch": "^0.8.3",
|
"scim-patch": "^0.8.3",
|
||||||
"scim2-parse-filter": "^0.2.10",
|
"scim2-parse-filter": "^0.2.10",
|
||||||
|
"sjcl": "^1.0.8",
|
||||||
"smee-client": "^2.0.0",
|
"smee-client": "^2.0.0",
|
||||||
"tedious": "^18.2.1",
|
"tedious": "^18.2.1",
|
||||||
"tweetnacl": "^1.0.3",
|
"tweetnacl": "^1.0.3",
|
||||||
|
4
backend/src/@types/fastify.d.ts
vendored
4
backend/src/@types/fastify.d.ts
vendored
@@ -38,6 +38,8 @@ import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-se
|
|||||||
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
||||||
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
import { TCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
|
||||||
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
import { TCertificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
|
import { TCmekServiceFactory } from "@app/services/cmek/cmek-service";
|
||||||
|
import { TExternalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
||||||
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
import { TGroupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
import { TIdentityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { TIdentityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
@@ -181,6 +183,8 @@ declare module "fastify" {
|
|||||||
orgAdmin: TOrgAdminServiceFactory;
|
orgAdmin: TOrgAdminServiceFactory;
|
||||||
slack: TSlackServiceFactory;
|
slack: TSlackServiceFactory;
|
||||||
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
||||||
|
cmek: TCmekServiceFactory;
|
||||||
|
migration: TExternalMigrationServiceFactory;
|
||||||
};
|
};
|
||||||
// this is exclusive use for middlewares in which we need to inject data
|
// this is exclusive use for middlewares in which we need to inject data
|
||||||
// everywhere else access using service layer
|
// everywhere else access using service layer
|
||||||
|
8
backend/src/@types/knex.d.ts
vendored
8
backend/src/@types/knex.d.ts
vendored
@@ -101,6 +101,9 @@ import {
|
|||||||
TIdentityKubernetesAuths,
|
TIdentityKubernetesAuths,
|
||||||
TIdentityKubernetesAuthsInsert,
|
TIdentityKubernetesAuthsInsert,
|
||||||
TIdentityKubernetesAuthsUpdate,
|
TIdentityKubernetesAuthsUpdate,
|
||||||
|
TIdentityMetadata,
|
||||||
|
TIdentityMetadataInsert,
|
||||||
|
TIdentityMetadataUpdate,
|
||||||
TIdentityOidcAuths,
|
TIdentityOidcAuths,
|
||||||
TIdentityOidcAuthsInsert,
|
TIdentityOidcAuthsInsert,
|
||||||
TIdentityOidcAuthsUpdate,
|
TIdentityOidcAuthsUpdate,
|
||||||
@@ -546,6 +549,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityUniversalAuthsInsert,
|
TIdentityUniversalAuthsInsert,
|
||||||
TIdentityUniversalAuthsUpdate
|
TIdentityUniversalAuthsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityMetadata]: KnexOriginal.CompositeTableType<
|
||||||
|
TIdentityMetadata,
|
||||||
|
TIdentityMetadataInsert,
|
||||||
|
TIdentityMetadataUpdate
|
||||||
|
>;
|
||||||
[TableName.IdentityKubernetesAuth]: KnexOriginal.CompositeTableType<
|
[TableName.IdentityKubernetesAuth]: KnexOriginal.CompositeTableType<
|
||||||
TIdentityKubernetesAuths,
|
TIdentityKubernetesAuths,
|
||||||
TIdentityKubernetesAuthsInsert,
|
TIdentityKubernetesAuthsInsert,
|
||||||
|
4
backend/src/@types/ldif.d.ts
vendored
Normal file
4
backend/src/@types/ldif.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
declare module "ldif" {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Untyped, the function returns `any`.
|
||||||
|
function parse(input: string, ...args: any[]): any;
|
||||||
|
}
|
@@ -3,34 +3,74 @@ import { Knex } from "knex";
|
|||||||
import { TableName } from "../schemas";
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasAccessApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasAccessApproverUserId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverUserId");
|
||||||
|
const hasSecretApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.SecretApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasSecretApproverUserId = await knex.schema.hasColumn(TableName.SecretApprovalPolicyApprover, "approverUserId");
|
||||||
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover)) {
|
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover)) {
|
||||||
// add column approverGroupId to AccessApprovalPolicyApprover
|
|
||||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (table) => {
|
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (table) => {
|
||||||
// make nullable
|
// add column approverGroupId to AccessApprovalPolicyApprover
|
||||||
table.uuid("approverGroupId").nullable().references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
if (!hasAccessApproverGroupId) {
|
||||||
|
table.uuid("approverGroupId").nullable().references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
// make approverUserId nullable
|
// make approverUserId nullable
|
||||||
table.uuid("approverUserId").nullable().alter();
|
if (hasAccessApproverUserId) {
|
||||||
|
table.uuid("approverUserId").nullable().alter();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
// add column approverGroupId to SecretApprovalPolicyApprover
|
|
||||||
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (table) => {
|
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (table) => {
|
||||||
table.uuid("approverGroupId").references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
// add column approverGroupId to SecretApprovalPolicyApprover
|
||||||
table.uuid("approverUserId").nullable().alter();
|
if (!hasSecretApproverGroupId) {
|
||||||
|
table.uuid("approverGroupId").nullable().references("id").inTable(TableName.Groups).onDelete("CASCADE");
|
||||||
|
}
|
||||||
|
|
||||||
|
// make approverUserId nullable
|
||||||
|
if (hasSecretApproverUserId) {
|
||||||
|
table.uuid("approverUserId").nullable().alter();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasAccessApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.AccessApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasAccessApproverUserId = await knex.schema.hasColumn(TableName.AccessApprovalPolicyApprover, "approverUserId");
|
||||||
|
const hasSecretApproverGroupId = await knex.schema.hasColumn(
|
||||||
|
TableName.SecretApprovalPolicyApprover,
|
||||||
|
"approverGroupId"
|
||||||
|
);
|
||||||
|
const hasSecretApproverUserId = await knex.schema.hasColumn(TableName.SecretApprovalPolicyApprover, "approverUserId");
|
||||||
|
|
||||||
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover)) {
|
if (await knex.schema.hasTable(TableName.AccessApprovalPolicyApprover)) {
|
||||||
// remove
|
|
||||||
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (table) => {
|
await knex.schema.alterTable(TableName.AccessApprovalPolicyApprover, (table) => {
|
||||||
table.dropColumn("approverGroupId");
|
if (hasAccessApproverGroupId) {
|
||||||
table.uuid("approverUserId").notNullable().alter();
|
table.dropColumn("approverGroupId");
|
||||||
|
}
|
||||||
|
// make approverUserId not nullable
|
||||||
|
if (hasAccessApproverUserId) {
|
||||||
|
table.uuid("approverUserId").notNullable().alter();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove
|
// remove
|
||||||
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (table) => {
|
await knex.schema.alterTable(TableName.SecretApprovalPolicyApprover, (table) => {
|
||||||
table.dropColumn("approverGroupId");
|
if (hasSecretApproverGroupId) {
|
||||||
table.uuid("approverUserId").notNullable().alter();
|
table.dropColumn("approverGroupId");
|
||||||
|
}
|
||||||
|
// make approverUserId not nullable
|
||||||
|
if (hasSecretApproverUserId) {
|
||||||
|
table.uuid("approverUserId").notNullable().alter();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,24 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.IdentityMetadata))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityMetadata, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("key").notNullable();
|
||||||
|
tb.string("value").notNullable();
|
||||||
|
tb.uuid("orgId").notNullable();
|
||||||
|
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
tb.uuid("userId");
|
||||||
|
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
tb.uuid("identityId");
|
||||||
|
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityMetadata);
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
t.string("iv").nullable().alter();
|
||||||
|
t.string("tag").nullable().alter();
|
||||||
|
t.string("encryptedValue").nullable().alter();
|
||||||
|
|
||||||
|
t.binary("encryptedSecret").nullable();
|
||||||
|
t.string("hashedHex").nullable().alter();
|
||||||
|
|
||||||
|
t.string("identifier", 64).nullable();
|
||||||
|
t.unique("identifier");
|
||||||
|
t.index("identifier");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.SecretSharing)) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretSharing, (t) => {
|
||||||
|
t.dropColumn("encryptedSecret");
|
||||||
|
|
||||||
|
t.dropColumn("identifier");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -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.OidcConfig, "lastUsed"))) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
|
||||||
|
tb.datetime("lastUsed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.OidcConfig, "lastUsed")) {
|
||||||
|
await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
|
||||||
|
tb.dropColumn("lastUsed");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,46 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { dropConstraintIfExists } from "@app/db/migrations/utils/dropConstraintIfExists";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
// drop constraint if exists (won't exist if rolled back, see below)
|
||||||
|
await dropConstraintIfExists(TableName.KmsKey, "kms_keys_orgid_slug_unique", knex);
|
||||||
|
|
||||||
|
// projectId for CMEK functionality
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
table.string("projectId").nullable().references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
|
||||||
|
if (hasOrgId) {
|
||||||
|
table.unique(["orgId", "projectId", "slug"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasSlug) {
|
||||||
|
table.renameColumn("slug", "name");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasOrgId = await knex.schema.hasColumn(TableName.KmsKey, "orgId");
|
||||||
|
const hasName = await knex.schema.hasColumn(TableName.KmsKey, "name");
|
||||||
|
|
||||||
|
// remove projectId for CMEK functionality
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
if (hasName) {
|
||||||
|
table.renameColumn("name", "slug");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOrgId) {
|
||||||
|
table.dropUnique(["orgId", "projectId", "slug"]);
|
||||||
|
}
|
||||||
|
table.dropColumn("projectId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (!hasSlug) {
|
||||||
|
// add slug back temporarily and set value equal to name
|
||||||
|
await knex.schema
|
||||||
|
.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
table.string("slug", 32);
|
||||||
|
})
|
||||||
|
.then(() => knex(TableName.KmsKey).update("slug", knex.ref("name")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.KmsKey)) {
|
||||||
|
const hasSlug = await knex.schema.hasColumn(TableName.KmsKey, "slug");
|
||||||
|
|
||||||
|
if (hasSlug) {
|
||||||
|
await knex.schema.alterTable(TableName.KmsKey, (table) => {
|
||||||
|
table.dropColumn("slug");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
|
export const dropConstraintIfExists = (tableName: TableName, constraintName: string, knex: Knex) =>
|
||||||
|
knex.raw(`ALTER TABLE ${tableName} DROP CONSTRAINT IF EXISTS ${constraintName};`);
|
@@ -54,7 +54,7 @@ export const getSecretManagerDataKey = async (knex: Knex, projectId: string) =>
|
|||||||
} else {
|
} else {
|
||||||
const [kmsDoc] = await knex(TableName.KmsKey)
|
const [kmsDoc] = await knex(TableName.KmsKey)
|
||||||
.insert({
|
.insert({
|
||||||
slug: slugify(alphaNumericNanoId(8).toLowerCase()),
|
name: slugify(alphaNumericNanoId(8).toLowerCase()),
|
||||||
orgId: project.orgId,
|
orgId: project.orgId,
|
||||||
isReserved: false
|
isReserved: false
|
||||||
})
|
})
|
||||||
|
23
backend/src/db/schemas/identity-metadata.ts
Normal file
23
backend/src/db/schemas/identity-metadata.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Code generated by automation script, DO NOT EDIT.
|
||||||
|
// Automated by pulling database and generating zod schema
|
||||||
|
// To update. Just run npm run generate:schema
|
||||||
|
// Written by akhilmhdh.
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const IdentityMetadataSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
key: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
identityId: z.string().uuid().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityMetadata = z.infer<typeof IdentityMetadataSchema>;
|
||||||
|
export type TIdentityMetadataInsert = Omit<z.input<typeof IdentityMetadataSchema>, TImmutableDBKeys>;
|
||||||
|
export type TIdentityMetadataUpdate = Partial<Omit<z.input<typeof IdentityMetadataSchema>, TImmutableDBKeys>>;
|
@@ -31,6 +31,7 @@ export * from "./identity-aws-auths";
|
|||||||
export * from "./identity-azure-auths";
|
export * from "./identity-azure-auths";
|
||||||
export * from "./identity-gcp-auths";
|
export * from "./identity-gcp-auths";
|
||||||
export * from "./identity-kubernetes-auths";
|
export * from "./identity-kubernetes-auths";
|
||||||
|
export * from "./identity-metadata";
|
||||||
export * from "./identity-oidc-auths";
|
export * from "./identity-oidc-auths";
|
||||||
export * from "./identity-org-memberships";
|
export * from "./identity-org-memberships";
|
||||||
export * from "./identity-project-additional-privilege";
|
export * from "./identity-project-additional-privilege";
|
||||||
|
@@ -13,9 +13,11 @@ export const KmsKeysSchema = z.object({
|
|||||||
isDisabled: z.boolean().default(false).nullable().optional(),
|
isDisabled: z.boolean().default(false).nullable().optional(),
|
||||||
isReserved: z.boolean().default(true).nullable().optional(),
|
isReserved: z.boolean().default(true).nullable().optional(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
projectId: z.string().nullable().optional(),
|
||||||
|
slug: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||||
|
@@ -70,6 +70,8 @@ export enum TableName {
|
|||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
||||||
|
// used by both identity and users
|
||||||
|
IdentityMetadata = "identity_metadata",
|
||||||
ScimToken = "scim_tokens",
|
ScimToken = "scim_tokens",
|
||||||
AccessApprovalPolicy = "access_approval_policies",
|
AccessApprovalPolicy = "access_approval_policies",
|
||||||
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
AccessApprovalPolicyApprover = "access_approval_policies_approvers",
|
||||||
|
@@ -26,7 +26,8 @@ export const OidcConfigsSchema = z.object({
|
|||||||
isActive: z.boolean(),
|
isActive: z.boolean(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid()
|
orgId: z.string().uuid(),
|
||||||
|
lastUsed: z.date().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
export type TOidcConfigs = z.infer<typeof OidcConfigsSchema>;
|
||||||
|
@@ -5,14 +5,16 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const SecretSharingSchema = z.object({
|
export const SecretSharingSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
encryptedValue: z.string(),
|
encryptedValue: z.string().nullable().optional(),
|
||||||
iv: z.string(),
|
iv: z.string().nullable().optional(),
|
||||||
tag: z.string(),
|
tag: z.string().nullable().optional(),
|
||||||
hashedHex: z.string(),
|
hashedHex: z.string().nullable().optional(),
|
||||||
expiresAt: z.date(),
|
expiresAt: z.date(),
|
||||||
userId: z.string().uuid().nullable().optional(),
|
userId: z.string().uuid().nullable().optional(),
|
||||||
orgId: z.string().uuid().nullable().optional(),
|
orgId: z.string().uuid().nullable().optional(),
|
||||||
@@ -22,7 +24,9 @@ export const SecretSharingSchema = z.object({
|
|||||||
accessType: z.string().default("anyone"),
|
accessType: z.string().default("anyone"),
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
lastViewedAt: z.date().nullable().optional(),
|
lastViewedAt: z.date().nullable().optional(),
|
||||||
password: z.string().nullable().optional()
|
password: z.string().nullable().optional(),
|
||||||
|
encryptedSecret: zodBuffer.nullable().optional(),
|
||||||
|
identifier: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
||||||
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
import { ApproverType } from "@app/ee/services/access-approval-policy/access-approval-policy-types";
|
||||||
import { EnforcementLevel } from "@app/lib/types";
|
import { EnforcementLevel } from "@app/lib/types";
|
||||||
|
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 { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
import { sapPubSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -11,6 +12,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
projectSlug: z.string().trim(),
|
projectSlug: z.string().trim(),
|
||||||
@@ -18,7 +22,10 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
secretPath: z.string().trim().default("/"),
|
secretPath: z.string().trim().default("/"),
|
||||||
environment: z.string(),
|
environment: z.string(),
|
||||||
approvers: z
|
approvers: z
|
||||||
.object({ type: z.nativeEnum(ApproverType), id: z.string() })
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
|
])
|
||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
@@ -30,7 +37,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.accessApprovalPolicy.createAccessApprovalPolicy({
|
const approval = await server.services.accessApprovalPolicy.createAccessApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -49,6 +56,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/",
|
url: "/",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
projectSlug: z.string().trim()
|
projectSlug: z.string().trim()
|
||||||
@@ -115,6 +125,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:policyId",
|
url: "/:policyId",
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
policyId: z.string()
|
policyId: z.string()
|
||||||
@@ -127,7 +140,10 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.optional()
|
.optional()
|
||||||
.transform((val) => (val === "" ? "/" : val)),
|
.transform((val) => (val === "" ? "/" : val)),
|
||||||
approvers: z
|
approvers: z
|
||||||
.object({ type: z.nativeEnum(ApproverType), id: z.string() })
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
|
])
|
||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
approvals: z.number().min(1).optional(),
|
approvals: z.number().min(1).optional(),
|
||||||
@@ -139,7 +155,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
await server.services.accessApprovalPolicy.updateAccessApprovalPolicy({
|
await server.services.accessApprovalPolicy.updateAccessApprovalPolicy({
|
||||||
policyId: req.params.policyId,
|
policyId: req.params.policyId,
|
||||||
@@ -155,6 +171,9 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
server.route({
|
server.route({
|
||||||
url: "/:policyId",
|
url: "/:policyId",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
policyId: z.string()
|
policyId: z.string()
|
||||||
@@ -165,7 +184,7 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.accessApprovalPolicy.deleteAccessApprovalPolicy({
|
const approval = await server.services.accessApprovalPolicy.deleteAccessApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -177,4 +196,44 @@ export const registerAccessApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
return { approval };
|
return { approval };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:policyId",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
policyId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approval: sapPubSchema.extend({
|
||||||
|
approvers: z
|
||||||
|
.object({
|
||||||
|
type: z.nativeEnum(ApproverType),
|
||||||
|
id: z.string().nullable().optional(),
|
||||||
|
name: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.nullable()
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approval = await server.services.accessApprovalPolicy.getAccessApprovalPolicyById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.params
|
||||||
|
});
|
||||||
|
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -26,7 +26,7 @@ const sanitizedExternalSchemaForGetAll = KmsKeysSchema.pick({
|
|||||||
isDisabled: true,
|
isDisabled: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
slug: true
|
name: true
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
externalKms: ExternalKmsSchema.pick({
|
externalKms: ExternalKmsSchema.pick({
|
||||||
@@ -57,7 +57,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().min(1).trim().toLowerCase(),
|
name: z.string().min(1).trim().toLowerCase(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsInputSchema
|
provider: ExternalKmsInputSchema
|
||||||
}),
|
}),
|
||||||
@@ -74,7 +74,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
provider: req.body.provider,
|
provider: req.body.provider,
|
||||||
description: req.body.description
|
description: req.body.description
|
||||||
});
|
});
|
||||||
@@ -87,7 +87,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
provider: req.body.provider.type,
|
provider: req.body.provider.type,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
description: req.body.description
|
description: req.body.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: z.string().trim().min(1)
|
id: z.string().trim().min(1)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
slug: z.string().min(1).trim().toLowerCase().optional(),
|
name: z.string().min(1).trim().toLowerCase().optional(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
provider: ExternalKmsInputUpdateSchema
|
provider: ExternalKmsInputUpdateSchema
|
||||||
}),
|
}),
|
||||||
@@ -125,7 +125,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
provider: req.body.provider,
|
provider: req.body.provider,
|
||||||
description: req.body.description,
|
description: req.body.description,
|
||||||
id: req.params.id
|
id: req.params.id
|
||||||
@@ -139,7 +139,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
provider: req.body.provider.type,
|
provider: req.body.provider.type,
|
||||||
slug: req.body.slug,
|
name: req.body.name,
|
||||||
description: req.body.description
|
description: req.body.description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.DELETE_KMS,
|
type: EventType.DELETE_KMS,
|
||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
slug: externalKms.slug
|
name: externalKms.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -224,7 +224,7 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.GET_KMS,
|
type: EventType.GET_KMS,
|
||||||
metadata: {
|
metadata: {
|
||||||
kmsId: externalKms.id,
|
kmsId: externalKms.id,
|
||||||
slug: externalKms.slug
|
name: externalKms.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -260,13 +260,13 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/slug/:slug",
|
url: "/name/:name",
|
||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
slug: z.string().trim().min(1)
|
name: z.string().trim().min(1)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -276,12 +276,12 @@ export const registerExternalKmsRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const externalKms = await server.services.externalKms.findBySlug({
|
const externalKms = await server.services.externalKms.findByName({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
slug: req.params.slug
|
name: req.params.name
|
||||||
});
|
});
|
||||||
return { externalKms };
|
return { externalKms };
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ 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 { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
import { IDENTITY_ADDITIONAL_PRIVILEGE } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { UnauthorizedError } from "@app/lib/errors";
|
||||||
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";
|
||||||
@@ -61,7 +61,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, privilegePermission } = req.body;
|
const { permissions, privilegePermission } = req.body;
|
||||||
if (!permissions && !privilegePermission) {
|
if (!permissions && !privilegePermission) {
|
||||||
throw new BadRequestError({ message: "Permission or privilegePermission must be provided" });
|
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = privilegePermission
|
const permission = privilegePermission
|
||||||
@@ -140,7 +140,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, privilegePermission } = req.body;
|
const { permissions, privilegePermission } = req.body;
|
||||||
if (!permissions && !privilegePermission) {
|
if (!permissions && !privilegePermission) {
|
||||||
throw new BadRequestError({ message: "Permission or privilegePermission must be provided" });
|
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = privilegePermission
|
const permission = privilegePermission
|
||||||
@@ -224,7 +224,7 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, privilegePermission, ...updatedInfo } = req.body.privilegeDetails;
|
const { permissions, privilegePermission, ...updatedInfo } = req.body.privilegeDetails;
|
||||||
if (!permissions && !privilegePermission) {
|
if (!permissions && !privilegePermission) {
|
||||||
throw new BadRequestError({ message: "Permission or privilegePermission must be provided" });
|
throw new UnauthorizedError({ message: "Permission or privilegePermission must be provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const permission = privilegePermission
|
const permission = privilegePermission
|
||||||
|
@@ -3,10 +3,11 @@ 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 { 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 { ProjectPermissionSchema, SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
|
import { SanitizedRoleSchema } from "@app/server/routes/sanitizedSchemas";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
||||||
|
@@ -203,7 +203,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
secretManagerKmsKey: z.object({
|
secretManagerKmsKey: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
isExternal: z.boolean()
|
isExternal: z.boolean()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -243,7 +243,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
secretManagerKmsKey: z.object({
|
secretManagerKmsKey: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
isExternal: z.boolean()
|
isExternal: z.boolean()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -268,7 +268,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
secretManagerKmsKey: {
|
secretManagerKmsKey: {
|
||||||
id: secretManagerKmsKey.id,
|
id: secretManagerKmsKey.id,
|
||||||
slug: secretManagerKmsKey.slug
|
name: secretManagerKmsKey.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +336,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
secretManagerKmsKey: z.object({
|
secretManagerKmsKey: z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
slug: z.string(),
|
name: z.string(),
|
||||||
isExternal: z.boolean()
|
isExternal: z.boolean()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { RateLimitSchema } from "@app/db/schemas";
|
import { RateLimitSchema } from "@app/db/schemas";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -29,7 +29,7 @@ export const registerRateLimitRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async () => {
|
handler: async () => {
|
||||||
const rateLimit = await server.services.rateLimit.getRateLimits();
|
const rateLimit = await server.services.rateLimit.getRateLimits();
|
||||||
if (!rateLimit) {
|
if (!rateLimit) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
name: "Get Rate Limit Error",
|
name: "Get Rate Limit Error",
|
||||||
message: "Rate limit configuration does not exist."
|
message: "Rate limit configuration does not exist."
|
||||||
});
|
});
|
||||||
|
@@ -61,7 +61,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
id: samlConfigId
|
id: samlConfigId
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new BadRequestError({ message: "Missing sso identitier or org slug" });
|
throw new BadRequestError({ message: "Missing sso identifier or org slug" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ssoConfig = await server.services.saml.getSaml(ssoLookupDetails);
|
const ssoConfig = await server.services.saml.getSaml(ssoLookupDetails);
|
||||||
@@ -100,6 +100,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
async (req, profile, cb) => {
|
async (req, profile, cb) => {
|
||||||
try {
|
try {
|
||||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||||
|
|
||||||
const email =
|
const email =
|
||||||
profile?.email ??
|
profile?.email ??
|
||||||
// entra sends data in this format
|
// entra sends data in this format
|
||||||
@@ -123,6 +124,14 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userMetadata = Object.keys(profile.attributes || {})
|
||||||
|
.map((key) => {
|
||||||
|
// for the ones like in format: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email
|
||||||
|
const formatedKey = key.startsWith("http") ? key.split("/").at(-1) || "" : key;
|
||||||
|
return { key: formatedKey, value: String((profile.attributes as Record<string, string>)[key]) };
|
||||||
|
})
|
||||||
|
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
||||||
externalId: profile.nameID,
|
externalId: profile.nameID,
|
||||||
email,
|
email,
|
||||||
@@ -130,7 +139,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
lastName: lastName as string,
|
lastName: lastName as string,
|
||||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
||||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
|
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string,
|
||||||
|
metadata: userMetadata
|
||||||
});
|
});
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -28,7 +28,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
.default("/")
|
.default("/")
|
||||||
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
.transform((val) => (val ? removeTrailingSlash(val) : val)),
|
||||||
approvers: z
|
approvers: z
|
||||||
.object({ type: z.nativeEnum(ApproverType), id: z.string() })
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
|
])
|
||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
@@ -40,7 +43,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
|
const approval = await server.services.secretApprovalPolicy.createSecretApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -69,7 +72,10 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
approvers: z
|
approvers: z
|
||||||
.object({ type: z.nativeEnum(ApproverType), id: z.string() })
|
.discriminatedUnion("type", [
|
||||||
|
z.object({ type: z.literal(ApproverType.Group), id: z.string() }),
|
||||||
|
z.object({ type: z.literal(ApproverType.User), id: z.string().optional(), name: z.string().optional() })
|
||||||
|
])
|
||||||
.array()
|
.array()
|
||||||
.min(1, { message: "At least one approver should be provided" }),
|
.min(1, { message: "At least one approver should be provided" }),
|
||||||
approvals: z.number().min(1).default(1),
|
approvals: z.number().min(1).default(1),
|
||||||
@@ -87,7 +93,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
|
const approval = await server.services.secretApprovalPolicy.updateSecretApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -117,7 +123,7 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
|
const approval = await server.services.secretApprovalPolicy.deleteSecretApprovalPolicy({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -168,6 +174,44 @@ export const registerSecretApprovalPolicyRouter = async (server: FastifyZodProvi
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/:sapId",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
sapId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
approval: sapPubSchema.extend({
|
||||||
|
approvers: z
|
||||||
|
.object({
|
||||||
|
id: z.string().nullable().optional(),
|
||||||
|
type: z.nativeEnum(ApproverType),
|
||||||
|
name: z.string().nullable().optional()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const approval = await server.services.secretApprovalPolicy.getSecretApprovalPolicyById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.params
|
||||||
|
});
|
||||||
|
|
||||||
|
return { approval };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
url: "/board",
|
url: "/board",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
@@ -12,16 +12,29 @@ export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPo
|
|||||||
export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||||
const accessApprovalPolicyOrm = ormify(db, TableName.AccessApprovalPolicy);
|
const accessApprovalPolicyOrm = ormify(db, TableName.AccessApprovalPolicy);
|
||||||
|
|
||||||
const accessApprovalPolicyFindQuery = async (tx: Knex, filter: TFindFilter<TAccessApprovalPolicies>) => {
|
const accessApprovalPolicyFindQuery = async (
|
||||||
|
tx: Knex,
|
||||||
|
filter: TFindFilter<TAccessApprovalPolicies>,
|
||||||
|
customFilter?: {
|
||||||
|
policyId?: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
const result = await tx(TableName.AccessApprovalPolicy)
|
const result = await tx(TableName.AccessApprovalPolicy)
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
.where(buildFindFilter(filter))
|
.where(buildFindFilter(filter))
|
||||||
|
.where((qb) => {
|
||||||
|
if (customFilter?.policyId) {
|
||||||
|
void qb.where(`${TableName.AccessApprovalPolicy}.id`, "=", customFilter.policyId);
|
||||||
|
}
|
||||||
|
})
|
||||||
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.AccessApprovalPolicyApprover,
|
TableName.AccessApprovalPolicyApprover,
|
||||||
`${TableName.AccessApprovalPolicy}.id`,
|
`${TableName.AccessApprovalPolicy}.id`,
|
||||||
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
`${TableName.AccessApprovalPolicyApprover}.policyId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.AccessApprovalPolicyApprover}.approverUserId`, `${TableName.Users}.id`)
|
||||||
|
.select(tx.ref("username").withSchema(TableName.Users).as("approverUsername"))
|
||||||
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
.select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover))
|
.select(tx.ref("approverGroupId").withSchema(TableName.AccessApprovalPolicyApprover))
|
||||||
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
.select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
|
||||||
@@ -76,9 +89,15 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => {
|
const find = async (
|
||||||
|
filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>,
|
||||||
|
customFilter?: {
|
||||||
|
policyId?: string;
|
||||||
|
},
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
|
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter, customFilter);
|
||||||
|
|
||||||
const formattedDocs = sqlNestRelationships({
|
const formattedDocs = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
@@ -97,9 +116,10 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "approverUserId",
|
key: "approverUserId",
|
||||||
label: "approvers" as const,
|
label: "approvers" as const,
|
||||||
mapper: ({ approverUserId: id }) => ({
|
mapper: ({ approverUserId: id, approverUsername }) => ({
|
||||||
id,
|
id,
|
||||||
type: ApproverType.User
|
type: ApproverType.User,
|
||||||
|
name: approverUsername
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TVerifyApprovers } from "./access-approval-policy-types";
|
import { TIsApproversValid } from "./access-approval-policy-types";
|
||||||
|
|
||||||
export const verifyApprovers = async ({
|
export const isApproversValid = async ({
|
||||||
userIds,
|
userIds,
|
||||||
projectId,
|
projectId,
|
||||||
orgId,
|
orgId,
|
||||||
@@ -14,9 +13,9 @@ export const verifyApprovers = async ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
secretPath,
|
secretPath,
|
||||||
permissionService
|
permissionService
|
||||||
}: TVerifyApprovers) => {
|
}: TIsApproversValid) => {
|
||||||
for await (const userId of userIds) {
|
try {
|
||||||
try {
|
for await (const userId of userIds) {
|
||||||
const { permission: approverPermission } = await permissionService.getProjectPermission(
|
const { permission: approverPermission } = await permissionService.getProjectPermission(
|
||||||
ActorType.USER,
|
ActorType.USER,
|
||||||
userId,
|
userId,
|
||||||
@@ -29,8 +28,9 @@ export const verifyApprovers = async ({
|
|||||||
ProjectPermissionActions.Create,
|
ProjectPermissionActions.Create,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: envSlug, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment: envSlug, secretPath })
|
||||||
);
|
);
|
||||||
} catch (err) {
|
|
||||||
throw new BadRequestError({ message: "One or more approvers doesn't have access to be specified secret path" });
|
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
@@ -2,19 +2,21 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
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 { verifyApprovers } from "./access-approval-policy-fns";
|
import { isApproversValid } from "./access-approval-policy-fns";
|
||||||
import {
|
import {
|
||||||
ApproverType,
|
ApproverType,
|
||||||
TCreateAccessApprovalPolicy,
|
TCreateAccessApprovalPolicy,
|
||||||
TDeleteAccessApprovalPolicy,
|
TDeleteAccessApprovalPolicy,
|
||||||
|
TGetAccessApprovalPolicyByIdDTO,
|
||||||
TGetAccessPolicyCountByEnvironmentDTO,
|
TGetAccessPolicyCountByEnvironmentDTO,
|
||||||
TListAccessApprovalPoliciesDTO,
|
TListAccessApprovalPoliciesDTO,
|
||||||
TUpdateAccessApprovalPolicy
|
TUpdateAccessApprovalPolicy
|
||||||
@@ -28,6 +30,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
|
accessApprovalPolicyApproverDAL: TAccessApprovalPolicyApproverDALFactory;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
|
||||||
groupDAL: TGroupDALFactory;
|
groupDAL: TGroupDALFactory;
|
||||||
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
|
export type TAccessApprovalPolicyServiceFactory = ReturnType<typeof accessApprovalPolicyServiceFactory>;
|
||||||
@@ -38,7 +41,8 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
groupDAL,
|
groupDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
userDAL
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createAccessApprovalPolicy = async ({
|
const createAccessApprovalPolicy = async ({
|
||||||
name,
|
name,
|
||||||
@@ -54,17 +58,23 @@ 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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project 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
|
||||||
.filter((approver) => approver.type === ApproverType.Group)
|
.filter((approver) => approver.type === ApproverType.Group)
|
||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id) as string[];
|
||||||
|
|
||||||
const userApprovers = approvers
|
const userApprovers = approvers
|
||||||
.filter((approver) => approver.type === ApproverType.User)
|
.filter((approver) => approver.type === ApproverType.User)
|
||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
if (!groupApprovers && approvals > userApprovers.length)
|
const userApproverNames = approvers
|
||||||
|
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
if (!groupApprovers && approvals > userApprovers.length + userApproverNames.length)
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -79,9 +89,28 @@ 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 BadRequestError({ message: "Environment not found" });
|
if (!env) throw new NotFoundError({ message: "Environment not found" });
|
||||||
|
|
||||||
|
let approverUserIds = userApprovers;
|
||||||
|
if (userApproverNames.length) {
|
||||||
|
const approverUsers = await userDAL.find({
|
||||||
|
$in: {
|
||||||
|
username: userApproverNames
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const approverNamesFromDb = approverUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userApproverNames.filter((username) => !approverNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid approver user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
approverUserIds = approverUserIds.concat(approverUsers.map((user) => user.id));
|
||||||
|
}
|
||||||
|
|
||||||
const verifyAllApprovers = userApprovers;
|
|
||||||
const usersPromises: Promise<
|
const usersPromises: Promise<
|
||||||
{
|
{
|
||||||
id: string;
|
id: string;
|
||||||
@@ -92,14 +121,18 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
isPartOfGroup: boolean;
|
isPartOfGroup: boolean;
|
||||||
}[]
|
}[]
|
||||||
>[] = [];
|
>[] = [];
|
||||||
|
const verifyAllApprovers = [...approverUserIds];
|
||||||
|
|
||||||
for (const groupId of groupApprovers) {
|
for (const groupId of groupApprovers) {
|
||||||
usersPromises.push(groupDAL.findAllGroupMembers({ orgId: actorOrgId, groupId, offset: 0 }));
|
usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }));
|
||||||
}
|
}
|
||||||
const verifyGroupApprovers = (await Promise.all(usersPromises)).flat().map((user) => user.id);
|
const verifyGroupApprovers = (await Promise.all(usersPromises))
|
||||||
|
.flat()
|
||||||
|
.filter((user) => user.isPartOfGroup)
|
||||||
|
.map((user) => user.id);
|
||||||
verifyAllApprovers.push(...verifyGroupApprovers);
|
verifyAllApprovers.push(...verifyGroupApprovers);
|
||||||
|
|
||||||
await verifyApprovers({
|
const approversValid = await isApproversValid({
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
envSlug: environment,
|
envSlug: environment,
|
||||||
@@ -109,6 +142,12 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
userIds: verifyAllApprovers
|
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(
|
||||||
{
|
{
|
||||||
@@ -120,9 +159,9 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
if (userApprovers) {
|
if (approverUserIds.length) {
|
||||||
await accessApprovalPolicyApproverDAL.insertMany(
|
await accessApprovalPolicyApproverDAL.insertMany(
|
||||||
userApprovers.map((userId) => ({
|
approverUserIds.map((userId) => ({
|
||||||
approverUserId: userId,
|
approverUserId: userId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
@@ -153,7 +192,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project 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(
|
||||||
@@ -182,19 +221,29 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
enforcementLevel
|
enforcementLevel
|
||||||
}: TUpdateAccessApprovalPolicy) => {
|
}: TUpdateAccessApprovalPolicy) => {
|
||||||
const groupApprovers = approvers
|
const groupApprovers = approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.Group)
|
.filter((approver) => approver.type === ApproverType.Group)
|
||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id) as string[];
|
||||||
|
|
||||||
const userApprovers = approvers
|
const userApprovers = approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.User)
|
.filter((approver) => approver.type === ApproverType.User)
|
||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
const userApproverNames = approvers
|
||||||
|
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
const accessApprovalPolicy = await accessApprovalPolicyDAL.findById(policyId);
|
||||||
const currentAppovals = approvals || accessApprovalPolicy.approvals;
|
const currentAppovals = approvals || accessApprovalPolicy.approvals;
|
||||||
if (groupApprovers?.length === 0 && userApprovers && currentAppovals > userApprovers.length) {
|
if (
|
||||||
|
groupApprovers?.length === 0 &&
|
||||||
|
userApprovers &&
|
||||||
|
currentAppovals > userApprovers.length + userApproverNames.length
|
||||||
|
) {
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
if (!accessApprovalPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -219,18 +268,45 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
|
|
||||||
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
if (userApprovers) {
|
if (userApprovers.length || userApproverNames.length) {
|
||||||
await verifyApprovers({
|
let userApproverIds = userApprovers;
|
||||||
|
if (userApproverNames.length) {
|
||||||
|
const approverUsers = await userDAL.find({
|
||||||
|
$in: {
|
||||||
|
username: userApproverNames
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const approverNamesFromDb = approverUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userApproverNames.filter((username) => !approverNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid approver user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
const approversValid = await isApproversValid({
|
||||||
projectId: accessApprovalPolicy.projectId,
|
projectId: accessApprovalPolicy.projectId,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
envSlug: accessApprovalPolicy.environment.slug,
|
envSlug: accessApprovalPolicy.environment.slug,
|
||||||
secretPath: doc.secretPath!,
|
secretPath: doc.secretPath!,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
permissionService,
|
permissionService,
|
||||||
userIds: userApprovers
|
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(
|
||||||
userApprovers.map((userId) => ({
|
userApproverIds.map((userId) => ({
|
||||||
approverUserId: userId,
|
approverUserId: userId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
@@ -251,11 +327,14 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
>[] = [];
|
>[] = [];
|
||||||
|
|
||||||
for (const groupId of groupApprovers) {
|
for (const groupId of groupApprovers) {
|
||||||
usersPromises.push(groupDAL.findAllGroupMembers({ orgId: actorOrgId, groupId, offset: 0 }));
|
usersPromises.push(groupDAL.findAllGroupPossibleMembers({ orgId: actorOrgId, groupId, offset: 0 }));
|
||||||
}
|
}
|
||||||
const verifyGroupApprovers = (await Promise.all(usersPromises)).flat().map((user) => user.id);
|
const verifyGroupApprovers = (await Promise.all(usersPromises))
|
||||||
|
.flat()
|
||||||
|
.filter((user) => user.isPartOfGroup)
|
||||||
|
.map((user) => user.id);
|
||||||
|
|
||||||
await verifyApprovers({
|
const approversValid = await isApproversValid({
|
||||||
projectId: accessApprovalPolicy.projectId,
|
projectId: accessApprovalPolicy.projectId,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
envSlug: accessApprovalPolicy.environment.slug,
|
envSlug: accessApprovalPolicy.environment.slug,
|
||||||
@@ -264,6 +343,13 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
userIds: verifyGroupApprovers
|
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,
|
||||||
@@ -290,7 +376,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TDeleteAccessApprovalPolicy) => {
|
}: TDeleteAccessApprovalPolicy) => {
|
||||||
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
const policy = await accessApprovalPolicyDAL.findById(policyId);
|
||||||
if (!policy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
if (!policy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -318,7 +404,7 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
}: TGetAccessPolicyCountByEnvironmentDTO) => {
|
}: TGetAccessPolicyCountByEnvironmentDTO) => {
|
||||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||||
|
|
||||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -327,22 +413,53 @@ export const accessApprovalPolicyServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (!membership) throw new BadRequestError({ message: "User not found in project" });
|
if (!membership) {
|
||||||
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
|
}
|
||||||
|
|
||||||
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
const environment = await projectEnvDAL.findOne({ projectId: project.id, slug: envSlug });
|
||||||
if (!environment) throw new BadRequestError({ message: "Environment not found" });
|
if (!environment) throw new NotFoundError({ message: "Environment 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 BadRequestError({ message: "No policies found" });
|
if (!policies) throw new NotFoundError({ message: "No policies found" });
|
||||||
|
|
||||||
return { count: policies.length };
|
return { count: policies.length };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getAccessApprovalPolicyById = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
policyId
|
||||||
|
}: TGetAccessApprovalPolicyByIdDTO) => {
|
||||||
|
const [policy] = await accessApprovalPolicyDAL.find({}, { policyId });
|
||||||
|
|
||||||
|
if (!policy) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Cannot find access approval policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
policy.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
return policy;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getAccessPolicyCountByEnvSlug,
|
getAccessPolicyCountByEnvSlug,
|
||||||
createAccessApprovalPolicy,
|
createAccessApprovalPolicy,
|
||||||
deleteAccessApprovalPolicy,
|
deleteAccessApprovalPolicy,
|
||||||
updateAccessApprovalPolicy,
|
updateAccessApprovalPolicy,
|
||||||
getAccessApprovalPolicyByProjectSlug
|
getAccessApprovalPolicyByProjectSlug,
|
||||||
|
getAccessApprovalPolicyById
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -3,7 +3,7 @@ import { ActorAuthMethod } from "@app/services/auth/auth-type";
|
|||||||
|
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
|
||||||
export type TVerifyApprovers = {
|
export type TIsApproversValid = {
|
||||||
userIds: string[];
|
userIds: string[];
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
envSlug: string;
|
envSlug: string;
|
||||||
@@ -22,7 +22,7 @@ export type TCreateAccessApprovalPolicy = {
|
|||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
approvers: { type: ApproverType; id: string }[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
name: string;
|
name: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
@@ -31,7 +31,7 @@ export type TCreateAccessApprovalPolicy = {
|
|||||||
export type TUpdateAccessApprovalPolicy = {
|
export type TUpdateAccessApprovalPolicy = {
|
||||||
policyId: string;
|
policyId: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
approvers?: { type: ApproverType; id: string }[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
||||||
secretPath?: string;
|
secretPath?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
@@ -46,6 +46,10 @@ export type TGetAccessPolicyCountByEnvironmentDTO = {
|
|||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetAccessApprovalPolicyByIdDTO = {
|
||||||
|
policyId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TListAccessApprovalPoliciesDTO = {
|
export type TListAccessApprovalPoliciesDTO = {
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||||
|
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TVerifyPermission } from "./access-approval-request-types";
|
import { TVerifyPermission } from "./access-approval-request-types";
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export const verifyRequestedPermissions = ({ permissions }: TVerifyPermission) =
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!permission || !permission.length) {
|
if (!permission || !permission.length) {
|
||||||
throw new UnauthorizedError({ message: "No permission provided" });
|
throw new BadRequestError({ message: "No permission provided" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestedPermissions: string[] = [];
|
const requestedPermissions: string[] = [];
|
||||||
@@ -39,10 +39,10 @@ export const verifyRequestedPermissions = ({ permissions }: TVerifyPermission) =
|
|||||||
const permissionEnv = firstPermission.conditions?.environment;
|
const permissionEnv = firstPermission.conditions?.environment;
|
||||||
|
|
||||||
if (!permissionEnv || typeof permissionEnv !== "string") {
|
if (!permissionEnv || typeof permissionEnv !== "string") {
|
||||||
throw new UnauthorizedError({ message: "Permission environment is not a string" });
|
throw new BadRequestError({ message: "Permission environment is not a string" });
|
||||||
}
|
}
|
||||||
if (!permissionSecretPath || typeof permissionSecretPath !== "string") {
|
if (!permissionSecretPath || typeof permissionSecretPath !== "string") {
|
||||||
throw new UnauthorizedError({ message: "Permission path is not a string" });
|
throw new BadRequestError({ message: "Permission path is not a string" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@@ -3,7 +3,7 @@ import ms from "ms";
|
|||||||
|
|
||||||
import { ProjectMembershipRole } from "@app/db/schemas";
|
import { ProjectMembershipRole } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -17,7 +17,7 @@ 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 { verifyApprovers } from "../access-approval-policy/access-approval-policy-fns";
|
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";
|
||||||
@@ -58,7 +58,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
TAccessApprovalRequestReviewerDALFactory,
|
TAccessApprovalRequestReviewerDALFactory,
|
||||||
"create" | "find" | "findOne" | "transaction"
|
"create" | "find" | "findOne" | "transaction"
|
||||||
>;
|
>;
|
||||||
groupDAL: Pick<TGroupDALFactory, "findAllGroupMembers">;
|
groupDAL: Pick<TGroupDALFactory, "findAllGroupPossibleMembers">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
userDAL: Pick<
|
userDAL: Pick<
|
||||||
@@ -99,7 +99,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 UnauthorizedError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project 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(
|
||||||
@@ -109,23 +109,25 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
|
if (!membership) {
|
||||||
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
|
}
|
||||||
|
|
||||||
const requestedByUser = await userDAL.findById(actorId);
|
const requestedByUser = await userDAL.findById(actorId);
|
||||||
if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" });
|
if (!requestedByUser) throw new ForbiddenRequestError({ message: "User not found" });
|
||||||
|
|
||||||
await projectDAL.checkProjectUpgradeStatus(project.id);
|
await projectDAL.checkProjectUpgradeStatus(project.id);
|
||||||
|
|
||||||
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 UnauthorizedError({ message: "Environment not found" });
|
if (!environment) throw new NotFoundError({ message: "Environment not found" });
|
||||||
|
|
||||||
const policy = await accessApprovalPolicyDAL.findOne({
|
const policy = await accessApprovalPolicyDAL.findOne({
|
||||||
envId: environment.id,
|
envId: environment.id,
|
||||||
secretPath
|
secretPath
|
||||||
});
|
});
|
||||||
if (!policy) throw new UnauthorizedError({ message: "No policy matching criteria was found." });
|
if (!policy) throw new NotFoundError({ message: "No policy matching criteria was found." });
|
||||||
|
|
||||||
const approverIds: string[] = [];
|
const approverIds: string[] = [];
|
||||||
const approverGroupIds: string[] = [];
|
const approverGroupIds: string[] = [];
|
||||||
@@ -145,14 +147,14 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
const groupUsers = (
|
const groupUsers = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
approverGroupIds.map((groupApproverId) =>
|
approverGroupIds.map((groupApproverId) =>
|
||||||
groupDAL.findAllGroupMembers({
|
groupDAL.findAllGroupPossibleMembers({
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
groupId: groupApproverId
|
groupId: groupApproverId
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).flat();
|
).flat();
|
||||||
approverIds.push(...groupUsers.map((user) => user.id));
|
approverIds.push(...groupUsers.filter((user) => user.isPartOfGroup).map((user) => user.id));
|
||||||
|
|
||||||
const approverUsers = await userDAL.find({
|
const approverUsers = await userDAL.find({
|
||||||
$in: {
|
$in: {
|
||||||
@@ -262,7 +264,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 UnauthorizedError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -271,7 +273,9 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
|
if (!membership) {
|
||||||
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
|
}
|
||||||
|
|
||||||
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
|
const policies = await accessApprovalPolicyDAL.find({ projectId: project.id });
|
||||||
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
|
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
|
||||||
@@ -296,7 +300,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TReviewAccessRequestDTO) => {
|
}: TReviewAccessRequestDTO) => {
|
||||||
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
|
const accessApprovalRequest = await accessApprovalRequestDAL.findById(requestId);
|
||||||
if (!accessApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!accessApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
||||||
|
|
||||||
const { policy } = accessApprovalRequest;
|
const { policy } = accessApprovalRequest;
|
||||||
const { membership, hasRole } = await permissionService.getProjectPermission(
|
const { membership, hasRole } = await permissionService.getProjectPermission(
|
||||||
@@ -307,19 +311,21 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
|
if (!membership) {
|
||||||
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!hasRole(ProjectMembershipRole.Admin) &&
|
!hasRole(ProjectMembershipRole.Admin) &&
|
||||||
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
|
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
|
||||||
!policy.approvers.find((approver) => approver.userId === actorId) // The request isn't performed by an assigned approver
|
!policy.approvers.find((approver) => approver.userId === actorId) // The request isn't performed by an assigned approver
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "You are not authorized to approve this request" });
|
throw new ForbiddenRequestError({ message: "You are not authorized to approve this request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewerProjectMembership = await projectMembershipDAL.findById(membership.id);
|
const reviewerProjectMembership = await projectMembershipDAL.findById(membership.id);
|
||||||
|
|
||||||
await verifyApprovers({
|
const approversValid = await isApproversValid({
|
||||||
projectId: accessApprovalRequest.projectId,
|
projectId: accessApprovalRequest.projectId,
|
||||||
orgId: actorOrgId,
|
orgId: actorOrgId,
|
||||||
envSlug: accessApprovalRequest.environment,
|
envSlug: accessApprovalRequest.environment,
|
||||||
@@ -329,6 +335,10 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
userIds: [reviewerProjectMembership.userId]
|
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" });
|
||||||
@@ -411,7 +421,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 UnauthorizedError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const { membership } = await permissionService.getProjectPermission(
|
const { membership } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -420,7 +430,9 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
if (!membership) throw new BadRequestError({ message: "User not found in project" });
|
if (!membership) {
|
||||||
|
throw new ForbiddenRequestError({ message: "You are not a member of this project" });
|
||||||
|
}
|
||||||
|
|
||||||
const count = await accessApprovalRequestDAL.getCount({ projectId: project.id });
|
const count = await accessApprovalRequestDAL.getCount({ projectId: project.id });
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ import { SecretKeyEncoding } from "@app/db/schemas";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { request } from "@app/lib/config/request";
|
import { request } from "@app/lib/config/request";
|
||||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
|
|
||||||
import { AUDIT_LOG_STREAM_TIMEOUT } from "../audit-log/audit-log-queue";
|
import { AUDIT_LOG_STREAM_TIMEOUT } from "../audit-log/audit-log-queue";
|
||||||
@@ -43,14 +43,15 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TCreateAuditLogStreamDTO) => {
|
}: TCreateAuditLogStreamDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan.auditLogStreams)
|
if (!plan.auditLogStreams) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create audit log streams due to plan restriction. Upgrade plan to create group."
|
message: "Failed to create audit log streams due to plan restriction. Upgrade plan to create group."
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -120,7 +121,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TUpdateAuditLogStreamDTO) => {
|
}: TUpdateAuditLogStreamDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID attached to authentication token" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
if (!plan.auditLogStreams)
|
if (!plan.auditLogStreams)
|
||||||
@@ -129,7 +130,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const logStream = await auditLogStreamDAL.findById(id);
|
const logStream = await auditLogStreamDAL.findById(id);
|
||||||
if (!logStream) throw new BadRequestError({ message: "Audit log stream not found" });
|
if (!logStream) throw new NotFoundError({ message: "Audit log stream 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);
|
||||||
@@ -178,10 +179,10 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TDeleteAuditLogStreamDTO) => {
|
const deleteById = async ({ id, actor, actorId, actorOrgId, actorAuthMethod }: TDeleteAuditLogStreamDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from 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 BadRequestError({ message: "Audit log stream not found" });
|
if (!logStream) throw new NotFoundError({ message: "Audit log stream 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);
|
||||||
@@ -193,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 BadRequestError({ message: "Audit log stream not found" });
|
if (!logStream) throw new NotFoundError({ message: "Audit log stream 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);
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
@@ -122,6 +123,7 @@ export enum EventType {
|
|||||||
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
UPDATE_WEBHOOK_STATUS = "update-webhook-status",
|
||||||
DELETE_WEBHOOK = "delete-webhook",
|
DELETE_WEBHOOK = "delete-webhook",
|
||||||
GET_SECRET_IMPORTS = "get-secret-imports",
|
GET_SECRET_IMPORTS = "get-secret-imports",
|
||||||
|
GET_SECRET_IMPORT = "get-secret-import",
|
||||||
CREATE_SECRET_IMPORT = "create-secret-import",
|
CREATE_SECRET_IMPORT = "create-secret-import",
|
||||||
UPDATE_SECRET_IMPORT = "update-secret-import",
|
UPDATE_SECRET_IMPORT = "update-secret-import",
|
||||||
DELETE_SECRET_IMPORT = "delete-secret-import",
|
DELETE_SECRET_IMPORT = "delete-secret-import",
|
||||||
@@ -182,7 +184,13 @@ export enum EventType {
|
|||||||
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
||||||
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
||||||
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config",
|
||||||
INTEGRATION_SYNCED = "integration-synced"
|
INTEGRATION_SYNCED = "integration-synced",
|
||||||
|
CREATE_CMEK = "create-cmek",
|
||||||
|
UPDATE_CMEK = "update-cmek",
|
||||||
|
DELETE_CMEK = "delete-cmek",
|
||||||
|
GET_CMEKS = "get-cmeks",
|
||||||
|
CMEK_ENCRYPT = "cmek-encrypt",
|
||||||
|
CMEK_DECRYPT = "cmek-decrypt"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -1004,6 +1012,14 @@ interface GetSecretImportsEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetSecretImportEvent {
|
||||||
|
type: EventType.GET_SECRET_IMPORT;
|
||||||
|
metadata: {
|
||||||
|
secretImportId: string;
|
||||||
|
folderId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreateSecretImportEvent {
|
interface CreateSecretImportEvent {
|
||||||
type: EventType.CREATE_SECRET_IMPORT;
|
type: EventType.CREATE_SECRET_IMPORT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1350,7 +1366,7 @@ interface CreateKmsEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
kmsId: string;
|
kmsId: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
slug: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1359,7 +1375,7 @@ interface DeleteKmsEvent {
|
|||||||
type: EventType.DELETE_KMS;
|
type: EventType.DELETE_KMS;
|
||||||
metadata: {
|
metadata: {
|
||||||
kmsId: string;
|
kmsId: string;
|
||||||
slug: string;
|
name: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1368,7 +1384,7 @@ interface UpdateKmsEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
kmsId: string;
|
kmsId: string;
|
||||||
provider: string;
|
provider: string;
|
||||||
slug?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1377,7 +1393,7 @@ interface GetKmsEvent {
|
|||||||
type: EventType.GET_KMS;
|
type: EventType.GET_KMS;
|
||||||
metadata: {
|
metadata: {
|
||||||
kmsId: string;
|
kmsId: string;
|
||||||
slug: string;
|
name: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1386,7 +1402,7 @@ interface UpdateProjectKmsEvent {
|
|||||||
metadata: {
|
metadata: {
|
||||||
secretManagerKmsKey: {
|
secretManagerKmsKey: {
|
||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
name: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1541,6 +1557,53 @@ interface IntegrationSyncedEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateCmekEvent {
|
||||||
|
type: EventType.CREATE_CMEK;
|
||||||
|
metadata: {
|
||||||
|
keyId: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
encryptionAlgorithm: SymmetricEncryption;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteCmekEvent {
|
||||||
|
type: EventType.DELETE_CMEK;
|
||||||
|
metadata: {
|
||||||
|
keyId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateCmekEvent {
|
||||||
|
type: EventType.UPDATE_CMEK;
|
||||||
|
metadata: {
|
||||||
|
keyId: string;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetCmeksEvent {
|
||||||
|
type: EventType.GET_CMEKS;
|
||||||
|
metadata: {
|
||||||
|
keyIds: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CmekEncryptEvent {
|
||||||
|
type: EventType.CMEK_ENCRYPT;
|
||||||
|
metadata: {
|
||||||
|
keyId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CmekDecryptEvent {
|
||||||
|
type: EventType.CMEK_DECRYPT;
|
||||||
|
metadata: {
|
||||||
|
keyId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -1620,6 +1683,7 @@ export type Event =
|
|||||||
| UpdateWebhookStatusEvent
|
| UpdateWebhookStatusEvent
|
||||||
| DeleteWebhookEvent
|
| DeleteWebhookEvent
|
||||||
| GetSecretImportsEvent
|
| GetSecretImportsEvent
|
||||||
|
| GetSecretImportEvent
|
||||||
| CreateSecretImportEvent
|
| CreateSecretImportEvent
|
||||||
| UpdateSecretImportEvent
|
| UpdateSecretImportEvent
|
||||||
| DeleteSecretImportEvent
|
| DeleteSecretImportEvent
|
||||||
@@ -1680,4 +1744,10 @@ export type Event =
|
|||||||
| GetSlackIntegration
|
| GetSlackIntegration
|
||||||
| UpdateProjectSlackConfig
|
| UpdateProjectSlackConfig
|
||||||
| GetProjectSlackConfig
|
| GetProjectSlackConfig
|
||||||
| IntegrationSyncedEvent;
|
| IntegrationSyncedEvent
|
||||||
|
| CreateCmekEvent
|
||||||
|
| UpdateCmekEvent
|
||||||
|
| DeleteCmekEvent
|
||||||
|
| GetCmeksEvent
|
||||||
|
| CmekEncryptEvent
|
||||||
|
| CmekDecryptEvent;
|
||||||
|
@@ -2,10 +2,9 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
// import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -19,7 +18,6 @@ type TCertificateAuthorityCrlServiceFactoryDep = {
|
|||||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
// licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateAuthorityCrlServiceFactory = ReturnType<typeof certificateAuthorityCrlServiceFactory>;
|
export type TCertificateAuthorityCrlServiceFactory = ReturnType<typeof certificateAuthorityCrlServiceFactory>;
|
||||||
@@ -66,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 BadRequestError({ message: "CA not found" });
|
if (!ca) throw new NotFoundError({ message: "CA not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -81,13 +79,6 @@ export const certificateAuthorityCrlServiceFactory = ({
|
|||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
// const plan = await licenseService.getPlan(actorOrgId);
|
|
||||||
// if (!plan.caCrl)
|
|
||||||
// throw new BadRequestError({
|
|
||||||
// message:
|
|
||||||
// "Failed to get CA certificate revocation lists (CRLs) due to plan restriction. Upgrade plan to get the CA CRL."
|
|
||||||
// });
|
|
||||||
|
|
||||||
const caCrls = await certificateAuthorityCrlDAL.find({ caId: ca.id }, { sort: [["createdAt", "desc"]] });
|
const caCrls = await certificateAuthorityCrlDAL.find({ caId: ca.id }, { sort: [["createdAt", "desc"]] });
|
||||||
|
|
||||||
const keyId = await getProjectKmsCertificateKeyId({
|
const keyId = await getProjectKmsCertificateKeyId({
|
||||||
|
@@ -7,7 +7,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, 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 } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
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";
|
||||||
@@ -61,7 +61,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -84,10 +84,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 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 +134,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -157,10 +157,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
|
|
||||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease 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 +208,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -224,10 +224,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
|
|
||||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease 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 +273,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -289,10 +289,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 not found" });
|
||||||
|
|
||||||
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
const dynamicSecretLeases = await dynamicSecretLeaseDAL.find({ dynamicSecretId: dynamicSecretCfg.id });
|
||||||
return dynamicSecretLeases;
|
return dynamicSecretLeases;
|
||||||
@@ -309,7 +309,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -325,10 +325,10 @@ export const dynamicSecretLeaseServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
|
|
||||||
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
const dynamicSecretLease = await dynamicSecretLeaseDAL.findById(leaseId);
|
||||||
if (!dynamicSecretLease) throw new BadRequestError({ message: "Dynamic secret lease not found" });
|
if (!dynamicSecretLease) throw new NotFoundError({ message: "Dynamic secret lease not found" });
|
||||||
|
|
||||||
return dynamicSecretLease;
|
return dynamicSecretLease;
|
||||||
};
|
};
|
||||||
|
@@ -5,7 +5,7 @@ import { TLicenseServiceFactory } from "@app/ee/services/license/license-service
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { OrderByDirection } from "@app/lib/types";
|
import { OrderByDirection } 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";
|
||||||
@@ -66,7 +66,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -89,7 +89,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 +134,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
@@ -158,10 +158,10 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 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 });
|
||||||
@@ -213,7 +213,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
@@ -230,7 +230,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 BadRequestError({ message: "Dynamic secret not found" });
|
||||||
@@ -271,7 +271,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
|
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -287,10 +287,10 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 not found" });
|
||||||
const decryptedStoredInput = JSON.parse(
|
const decryptedStoredInput = JSON.parse(
|
||||||
infisicalSymmetricDecrypt({
|
infisicalSymmetricDecrypt({
|
||||||
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
keyEncoding: dynamicSecretCfg.keyEncoding as SecretKeyEncoding,
|
||||||
@@ -313,26 +313,29 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
path,
|
path,
|
||||||
environmentSlugs,
|
environmentSlugs,
|
||||||
search
|
search,
|
||||||
|
isInternal
|
||||||
}: TListDynamicSecretsMultiEnvDTO) => {
|
}: TListDynamicSecretsMultiEnvDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
if (!isInternal) {
|
||||||
actor,
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actorId,
|
actor,
|
||||||
projectId,
|
actorId,
|
||||||
actorAuthMethod,
|
projectId,
|
||||||
actorOrgId
|
actorAuthMethod,
|
||||||
);
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
// 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,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { 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 BadRequestError({ message: "Folders not found" });
|
if (!folders.length) throw new NotFoundError({ message: "Folders 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 },
|
||||||
@@ -366,7 +369,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 },
|
||||||
@@ -395,7 +398,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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,7 +415,7 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environmentSlug, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder 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 },
|
||||||
@@ -434,26 +437,29 @@ export const dynamicSecretServiceFactory = ({
|
|||||||
path,
|
path,
|
||||||
environmentSlugs,
|
environmentSlugs,
|
||||||
projectId,
|
projectId,
|
||||||
|
isInternal,
|
||||||
...params
|
...params
|
||||||
}: TListDynamicSecretsMultiEnvDTO) => {
|
}: TListDynamicSecretsMultiEnvDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
if (!isInternal) {
|
||||||
actor,
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actorId,
|
actor,
|
||||||
projectId,
|
actorId,
|
||||||
actorAuthMethod,
|
projectId,
|
||||||
actorOrgId
|
actorAuthMethod,
|
||||||
);
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
// 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,
|
ProjectPermissionActions.Read,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment: environmentSlug, secretPath: path })
|
subject(ProjectPermissionSub.Secrets, { 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 BadRequestError({ message: "Folders not found" });
|
if (!folders.length) throw new NotFoundError({ message: "Folders not found" });
|
||||||
|
|
||||||
const dynamicSecretCfg = await dynamicSecretDAL.listDynamicSecretsByFolderIds({
|
const dynamicSecretCfg = await dynamicSecretDAL.listDynamicSecretsByFolderIds({
|
||||||
folderIds: folders.map((folder) => folder.id),
|
folderIds: folders.map((folder) => folder.id),
|
||||||
|
@@ -63,7 +63,7 @@ export type TListDynamicSecretsDTO = {
|
|||||||
export type TListDynamicSecretsMultiEnvDTO = Omit<
|
export type TListDynamicSecretsMultiEnvDTO = Omit<
|
||||||
TListDynamicSecretsDTO,
|
TListDynamicSecretsDTO,
|
||||||
"projectId" | "environmentSlug" | "projectSlug"
|
"projectId" | "environmentSlug" | "projectSlug"
|
||||||
> & { projectId: string; environmentSlugs: string[] };
|
> & { projectId: string; environmentSlugs: string[]; isInternal?: boolean };
|
||||||
|
|
||||||
export type TGetDynamicSecretsCountDTO = Omit<TListDynamicSecretsDTO, "projectSlug" | "projectId"> & {
|
export type TGetDynamicSecretsCountDTO = Omit<TListDynamicSecretsDTO, "projectSlug" | "projectId"> & {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
@@ -3,6 +3,7 @@ import { AwsIamProvider } from "./aws-iam";
|
|||||||
import { AzureEntraIDProvider } from "./azure-entra-id";
|
import { AzureEntraIDProvider } from "./azure-entra-id";
|
||||||
import { CassandraProvider } from "./cassandra";
|
import { CassandraProvider } from "./cassandra";
|
||||||
import { ElasticSearchProvider } from "./elastic-search";
|
import { ElasticSearchProvider } from "./elastic-search";
|
||||||
|
import { LdapProvider } from "./ldap";
|
||||||
import { DynamicSecretProviders } from "./models";
|
import { DynamicSecretProviders } from "./models";
|
||||||
import { MongoAtlasProvider } from "./mongo-atlas";
|
import { MongoAtlasProvider } from "./mongo-atlas";
|
||||||
import { MongoDBProvider } from "./mongo-db";
|
import { MongoDBProvider } from "./mongo-db";
|
||||||
@@ -20,5 +21,6 @@ export const buildDynamicSecretProviders = () => ({
|
|||||||
[DynamicSecretProviders.MongoDB]: MongoDBProvider(),
|
[DynamicSecretProviders.MongoDB]: MongoDBProvider(),
|
||||||
[DynamicSecretProviders.ElasticSearch]: ElasticSearchProvider(),
|
[DynamicSecretProviders.ElasticSearch]: ElasticSearchProvider(),
|
||||||
[DynamicSecretProviders.RabbitMq]: RabbitMqProvider(),
|
[DynamicSecretProviders.RabbitMq]: RabbitMqProvider(),
|
||||||
[DynamicSecretProviders.AzureEntraID]: AzureEntraIDProvider()
|
[DynamicSecretProviders.AzureEntraID]: AzureEntraIDProvider(),
|
||||||
|
[DynamicSecretProviders.Ldap]: LdapProvider()
|
||||||
});
|
});
|
||||||
|
235
backend/src/ee/services/dynamic-secret/providers/ldap.ts
Normal file
235
backend/src/ee/services/dynamic-secret/providers/ldap.ts
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
import handlebars from "handlebars";
|
||||||
|
import ldapjs from "ldapjs";
|
||||||
|
import ldif from "ldif";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { LdapSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
const generatePassword = () => {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||||
|
return customAlphabet(charset, 64)();
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodePassword = (password?: string) => {
|
||||||
|
const quotedPassword = `"${password}"`;
|
||||||
|
const utf16lePassword = Buffer.from(quotedPassword, "utf16le");
|
||||||
|
const base64Password = utf16lePassword.toString("base64");
|
||||||
|
return base64Password;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUsername = () => {
|
||||||
|
return alphaNumericNanoId(20);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateLDIF = ({
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
ldifTemplate
|
||||||
|
}: {
|
||||||
|
username: string;
|
||||||
|
password?: string;
|
||||||
|
ldifTemplate: string;
|
||||||
|
}): string => {
|
||||||
|
const data = {
|
||||||
|
Username: username,
|
||||||
|
Password: password,
|
||||||
|
EncodedPassword: encodePassword(password)
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderTemplate = handlebars.compile(ldifTemplate);
|
||||||
|
const renderedLdif = renderTemplate(data);
|
||||||
|
|
||||||
|
return renderedLdif;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LdapProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await LdapSchema.parseAsync(inputs);
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = async (providerInputs: z.infer<typeof LdapSchema>): Promise<ldapjs.Client> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const client = ldapjs.createClient({
|
||||||
|
url: providerInputs.url,
|
||||||
|
tlsOptions: {
|
||||||
|
ca: providerInputs.ca ? providerInputs.ca : null,
|
||||||
|
rejectUnauthorized: !!providerInputs.ca
|
||||||
|
},
|
||||||
|
reconnect: true,
|
||||||
|
bindDN: providerInputs.binddn,
|
||||||
|
bindCredentials: providerInputs.bindpass
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on("error", (err: Error) => {
|
||||||
|
client.unbind();
|
||||||
|
reject(new BadRequestError({ message: err.message }));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.bind(providerInputs.binddn, providerInputs.bindpass, (err) => {
|
||||||
|
if (err) {
|
||||||
|
client.unbind();
|
||||||
|
reject(new BadRequestError({ message: err.message }));
|
||||||
|
} else {
|
||||||
|
resolve(client);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
return client.connected;
|
||||||
|
};
|
||||||
|
|
||||||
|
const executeLdif = async (client: ldapjs.Client, ldif_file: string) => {
|
||||||
|
type TEntry = {
|
||||||
|
dn: string;
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
changes: {
|
||||||
|
operation?: string;
|
||||||
|
attribute: {
|
||||||
|
attribute: string;
|
||||||
|
};
|
||||||
|
value: {
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
values: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Untyped, can be any for ldapjs.Change.modification.values
|
||||||
|
value: any;
|
||||||
|
}[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
let parsedEntries: TEntry[];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
parsedEntries = ldif.parse(ldif_file).entries as TEntry[];
|
||||||
|
} catch (err) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid LDIF format, refer to the documentation at Dynamic secrets > LDAP > LDIF Entries."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const dnArray: string[] = [];
|
||||||
|
|
||||||
|
for await (const entry of parsedEntries) {
|
||||||
|
const { dn } = entry;
|
||||||
|
let responseDn: string;
|
||||||
|
|
||||||
|
if (entry.type === "add") {
|
||||||
|
const attributes: Record<string, string | string[]> = {};
|
||||||
|
|
||||||
|
entry.changes.forEach((change) => {
|
||||||
|
const attrName = change.attribute.attribute;
|
||||||
|
const attrValue = change.value.value;
|
||||||
|
|
||||||
|
attributes[attrName] = Array.isArray(attrValue) ? attrValue : [attrValue];
|
||||||
|
});
|
||||||
|
|
||||||
|
responseDn = await new Promise((resolve, reject) => {
|
||||||
|
client.add(dn, attributes, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(new BadRequestError({ message: err.message }));
|
||||||
|
} else {
|
||||||
|
resolve(dn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (entry.type === "modify") {
|
||||||
|
const changes: ldapjs.Change[] = [];
|
||||||
|
|
||||||
|
entry.changes.forEach((change) => {
|
||||||
|
changes.push(
|
||||||
|
new ldapjs.Change({
|
||||||
|
operation: change.operation || "replace",
|
||||||
|
modification: {
|
||||||
|
type: change.attribute.attribute,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
||||||
|
values: change.values.map((value) => value.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
responseDn = await new Promise((resolve, reject) => {
|
||||||
|
client.modify(dn, changes, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(new BadRequestError({ message: err.message }));
|
||||||
|
} else {
|
||||||
|
resolve(dn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (entry.type === "delete") {
|
||||||
|
responseDn = await new Promise((resolve, reject) => {
|
||||||
|
client.del(dn, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(new BadRequestError({ message: err.message }));
|
||||||
|
} else {
|
||||||
|
resolve(dn);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
client.unbind();
|
||||||
|
throw new BadRequestError({ message: `Unsupported operation type ${entry.type}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
dnArray.push(responseDn);
|
||||||
|
}
|
||||||
|
client.unbind();
|
||||||
|
return dnArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = generateUsername();
|
||||||
|
const password = generatePassword();
|
||||||
|
const generatedLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.creationLdif });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dnArray = await executeLdif(client, generatedLdif);
|
||||||
|
|
||||||
|
return { entityId: username, data: { DN_ARRAY: dnArray, USERNAME: username, PASSWORD: password } };
|
||||||
|
} catch (err) {
|
||||||
|
if (providerInputs.rollbackLdif) {
|
||||||
|
const rollbackLdif = generateLDIF({ username, password, ldifTemplate: providerInputs.rollbackLdif });
|
||||||
|
await executeLdif(client, rollbackLdif);
|
||||||
|
}
|
||||||
|
throw new BadRequestError({ message: (err as Error).message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (inputs: unknown, entityId: string) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const connection = await getClient(providerInputs);
|
||||||
|
const revocationLdif = generateLDIF({ username: entityId, ldifTemplate: providerInputs.revocationLdif });
|
||||||
|
|
||||||
|
await executeLdif(connection, revocationLdif);
|
||||||
|
|
||||||
|
return { entityId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (inputs: unknown, entityId: string) => {
|
||||||
|
// Do nothing
|
||||||
|
return { entityId };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
@@ -174,6 +174,17 @@ export const AzureEntraIDSchema = z.object({
|
|||||||
clientSecret: z.string().trim().min(1)
|
clientSecret: z.string().trim().min(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const LdapSchema = z.object({
|
||||||
|
url: z.string().trim().min(1),
|
||||||
|
binddn: z.string().trim().min(1),
|
||||||
|
bindpass: z.string().trim().min(1),
|
||||||
|
ca: z.string().optional(),
|
||||||
|
|
||||||
|
creationLdif: z.string().min(1),
|
||||||
|
revocationLdif: z.string().min(1),
|
||||||
|
rollbackLdif: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
export enum DynamicSecretProviders {
|
export enum DynamicSecretProviders {
|
||||||
SqlDatabase = "sql-database",
|
SqlDatabase = "sql-database",
|
||||||
Cassandra = "cassandra",
|
Cassandra = "cassandra",
|
||||||
@@ -184,7 +195,8 @@ export enum DynamicSecretProviders {
|
|||||||
ElasticSearch = "elastic-search",
|
ElasticSearch = "elastic-search",
|
||||||
MongoDB = "mongo-db",
|
MongoDB = "mongo-db",
|
||||||
RabbitMq = "rabbit-mq",
|
RabbitMq = "rabbit-mq",
|
||||||
AzureEntraID = "azure-entra-id"
|
AzureEntraID = "azure-entra-id",
|
||||||
|
Ldap = "ldap"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||||
@@ -197,7 +209,8 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
|||||||
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 })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type TDynamicProviderFns = {
|
export type TDynamicProviderFns = {
|
||||||
|
@@ -30,7 +30,7 @@ export const externalKmsDALFactory = (db: TDbClient) => {
|
|||||||
isDisabled: el.isDisabled,
|
isDisabled: el.isDisabled,
|
||||||
isReserved: el.isReserved,
|
isReserved: el.isReserved,
|
||||||
orgId: el.orgId,
|
orgId: el.orgId,
|
||||||
slug: el.slug,
|
name: el.name,
|
||||||
createdAt: el.createdAt,
|
createdAt: el.createdAt,
|
||||||
updatedAt: el.updatedAt,
|
updatedAt: el.updatedAt,
|
||||||
externalKms: {
|
externalKms: {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
@@ -43,7 +43,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
provider,
|
provider,
|
||||||
description,
|
description,
|
||||||
actor,
|
actor,
|
||||||
slug,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
@@ -64,7 +64,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const kmsSlug = slug ? slugify(slug) : slugify(alphaNumericNanoId(8).toLowerCase());
|
const kmsName = name ? slugify(name) : slugify(alphaNumericNanoId(8).toLowerCase());
|
||||||
|
|
||||||
let sanitizedProviderInput = "";
|
let sanitizedProviderInput = "";
|
||||||
switch (provider.type) {
|
switch (provider.type) {
|
||||||
@@ -96,7 +96,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
{
|
{
|
||||||
isReserved: false,
|
isReserved: false,
|
||||||
description,
|
description,
|
||||||
slug: kmsSlug,
|
name: kmsName,
|
||||||
orgId: actorOrgId
|
orgId: actorOrgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
@@ -120,7 +120,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
description,
|
description,
|
||||||
actor,
|
actor,
|
||||||
id: kmsId,
|
id: kmsId,
|
||||||
slug,
|
name,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
@@ -142,10 +142,10 @@ export const externalKmsServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const kmsSlug = slug ? slugify(slug) : 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 BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
|
||||||
|
|
||||||
let sanitizedProviderInput = "";
|
let sanitizedProviderInput = "";
|
||||||
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
|
const { encryptor: orgDataKeyEncryptor, decryptor: orgDataKeyDecryptor } =
|
||||||
@@ -188,7 +188,7 @@ export const externalKmsServiceFactory = ({
|
|||||||
kmsDoc.id,
|
kmsDoc.id,
|
||||||
{
|
{
|
||||||
description,
|
description,
|
||||||
slug: kmsSlug
|
name: kmsName
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -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 BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms 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 BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
|
||||||
|
|
||||||
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
@@ -280,14 +280,14 @@ export const externalKmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findBySlug = async ({
|
const findByName = async ({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
slug: kmsSlug
|
name: kmsName
|
||||||
}: TGetExternalKmsBySlugDTO) => {
|
}: TGetExternalKmsBySlugDTO) => {
|
||||||
const kmsDoc = await kmsDAL.findOne({ slug: kmsSlug, orgId: actorOrgId });
|
const kmsDoc = await kmsDAL.findOne({ name: kmsName, orgId: actorOrgId });
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -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 BadRequestError({ message: "External kms not found" });
|
if (!externalKmsDoc) throw new NotFoundError({ message: "External kms not found" });
|
||||||
|
|
||||||
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: orgDataKeyDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
@@ -327,6 +327,6 @@ export const externalKmsServiceFactory = ({
|
|||||||
deleteById,
|
deleteById,
|
||||||
list,
|
list,
|
||||||
findById,
|
findById,
|
||||||
findBySlug
|
findByName
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -3,14 +3,14 @@ import { TOrgPermission } from "@app/lib/types";
|
|||||||
import { TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "./providers/model";
|
import { TExternalKmsInputSchema, TExternalKmsInputUpdateSchema } from "./providers/model";
|
||||||
|
|
||||||
export type TCreateExternalKmsDTO = {
|
export type TCreateExternalKmsDTO = {
|
||||||
slug?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
provider: TExternalKmsInputSchema;
|
provider: TExternalKmsInputSchema;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
export type TUpdateExternalKmsDTO = {
|
export type TUpdateExternalKmsDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
slug?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
provider?: TExternalKmsInputUpdateSchema;
|
provider?: TExternalKmsInputUpdateSchema;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
@@ -26,5 +26,5 @@ export type TGetExternalKmsByIdDTO = {
|
|||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
export type TGetExternalKmsBySlugDTO = {
|
export type TGetExternalKmsBySlugDTO = {
|
||||||
slug: string;
|
name: string;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
@@ -60,7 +60,7 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// special query
|
// special query
|
||||||
const findAllGroupMembers = async ({
|
const findAllGroupPossibleMembers = async ({
|
||||||
orgId,
|
orgId,
|
||||||
groupId,
|
groupId,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
@@ -125,7 +125,7 @@ export const groupDALFactory = (db: TDbClient) => {
|
|||||||
return {
|
return {
|
||||||
findGroups,
|
findGroups,
|
||||||
findByOrgId,
|
findByOrgId,
|
||||||
findAllGroupMembers,
|
findAllGroupPossibleMembers,
|
||||||
...groupOrm
|
...groupOrm
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -2,7 +2,7 @@ import { Knex } from "knex";
|
|||||||
|
|
||||||
import { SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
import { SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ScimRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TAddUsersToGroup,
|
TAddUsersToGroup,
|
||||||
@@ -73,24 +73,24 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
|
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
|
||||||
|
|
||||||
if (!ghostUser) {
|
if (!ghostUser) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find sudo user"
|
message: "Failed to find project owner"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, tx);
|
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, tx);
|
||||||
|
|
||||||
if (!ghostUserLatestKey) {
|
if (!ghostUserLatestKey) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find sudo user latest key"
|
message: "Failed to find project owner's latest key"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
||||||
|
|
||||||
if (!bot) {
|
if (!bot) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find bot"
|
message: "Failed to find project bot"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ export const addUsersToGroupByUserIds = async ({
|
|||||||
|
|
||||||
userIds.forEach((userId) => {
|
userIds.forEach((userId) => {
|
||||||
if (!existingUserOrgMembershipsUserIdsSet.has(userId))
|
if (!existingUserOrgMembershipsUserIdsSet.has(userId))
|
||||||
throw new BadRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: `User with id ${userId} is not part of the organization`
|
message: `User with id ${userId} is not part of the organization`
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -303,7 +303,7 @@ export const removeUsersFromGroupByUserIds = async ({
|
|||||||
|
|
||||||
userIds.forEach((userId) => {
|
userIds.forEach((userId) => {
|
||||||
if (!existingUserGroupMembershipsUserIdsSet.has(userId))
|
if (!existingUserGroupMembershipsUserIdsSet.has(userId))
|
||||||
throw new BadRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: `User(s) are not part of the group ${group.slug}`
|
message: `User(s) are not part of the group ${group.slug}`
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -415,7 +415,7 @@ export const convertPendingGroupAdditionsToGroupMemberships = async ({
|
|||||||
const usersUserIdsSet = new Set(users.map((u) => u.id));
|
const usersUserIdsSet = new Set(users.map((u) => u.id));
|
||||||
userIds.forEach((userId) => {
|
userIds.forEach((userId) => {
|
||||||
if (!usersUserIdsSet.has(userId)) {
|
if (!usersUserIdsSet.has(userId)) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: `Failed to find user with id ${userId}`
|
message: `Failed to find user with id ${userId}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@ import slugify from "@sindresorhus/slugify";
|
|||||||
|
|
||||||
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
import { OrgMembershipRole, TOrgRoles } from "@app/db/schemas";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
@@ -30,7 +30,10 @@ import { TUserGroupMembershipDALFactory } from "./user-group-membership-dal";
|
|||||||
|
|
||||||
type TGroupServiceFactoryDep = {
|
type TGroupServiceFactoryDep = {
|
||||||
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
|
userDAL: Pick<TUserDALFactory, "find" | "findUserEncKeyByUserIdsBatch" | "transaction" | "findOne">;
|
||||||
groupDAL: Pick<TGroupDALFactory, "create" | "findOne" | "update" | "delete" | "findAllGroupMembers" | "findById">;
|
groupDAL: Pick<
|
||||||
|
TGroupDALFactory,
|
||||||
|
"create" | "findOne" | "update" | "delete" | "findAllGroupPossibleMembers" | "findById"
|
||||||
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership" | "countAllOrgMembers">;
|
||||||
userGroupMembershipDAL: Pick<
|
userGroupMembershipDAL: Pick<
|
||||||
@@ -59,7 +62,7 @@ export const groupServiceFactory = ({
|
|||||||
licenseService
|
licenseService
|
||||||
}: TGroupServiceFactoryDep) => {
|
}: TGroupServiceFactoryDep) => {
|
||||||
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
const createGroup = async ({ name, slug, role, actor, actorId, actorAuthMethod, actorOrgId }: TCreateGroupDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -82,7 +85,8 @@ export const groupServiceFactory = ({
|
|||||||
);
|
);
|
||||||
const isCustomRole = Boolean(customRole);
|
const isCustomRole = Boolean(customRole);
|
||||||
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
const hasRequiredPriviledges = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged group" });
|
if (!hasRequiredPriviledges)
|
||||||
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
||||||
|
|
||||||
const group = await groupDAL.create({
|
const group = await groupDAL.create({
|
||||||
name,
|
name,
|
||||||
@@ -105,7 +109,7 @@ export const groupServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TUpdateGroupDTO) => {
|
}: TUpdateGroupDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -124,7 +128,7 @@ export const groupServiceFactory = ({
|
|||||||
|
|
||||||
const group = await groupDAL.findOne({ orgId: actorOrgId, id });
|
const group = await groupDAL.findOne({ orgId: actorOrgId, id });
|
||||||
if (!group) {
|
if (!group) {
|
||||||
throw new BadRequestError({ message: `Failed to find group with ID ${id}` });
|
throw new NotFoundError({ message: `Failed to find group with ID ${id}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
let customRole: TOrgRoles | undefined;
|
let customRole: TOrgRoles | undefined;
|
||||||
@@ -137,7 +141,7 @@ export const groupServiceFactory = ({
|
|||||||
const isCustomRole = Boolean(customOrgRole);
|
const isCustomRole = Boolean(customOrgRole);
|
||||||
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
const hasRequiredNewRolePermission = isAtLeastAsPrivileged(permission, rolePermission);
|
||||||
if (!hasRequiredNewRolePermission)
|
if (!hasRequiredNewRolePermission)
|
||||||
throw new BadRequestError({ message: "Failed to create a more privileged group" });
|
throw new ForbiddenRequestError({ message: "Failed to create a more privileged group" });
|
||||||
if (isCustomRole) customRole = customOrgRole;
|
if (isCustomRole) customRole = customOrgRole;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +165,7 @@ export const groupServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const deleteGroup = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
|
const deleteGroup = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TDeleteGroupDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -188,9 +192,7 @@ export const groupServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getGroupById = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TGetGroupByIdDTO) => {
|
const getGroupById = async ({ id, actor, actorId, actorAuthMethod, actorOrgId }: TGetGroupByIdDTO) => {
|
||||||
if (!actorOrgId) {
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
throw new BadRequestError({ message: "Failed to read group without organization" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -221,7 +223,7 @@ export const groupServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TListGroupUsersDTO) => {
|
}: TListGroupUsersDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -238,11 +240,11 @@ export const groupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!group)
|
if (!group)
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
const users = await groupDAL.findAllGroupMembers({
|
const users = await groupDAL.findAllGroupPossibleMembers({
|
||||||
orgId: group.orgId,
|
orgId: group.orgId,
|
||||||
groupId: group.id,
|
groupId: group.id,
|
||||||
offset,
|
offset,
|
||||||
@@ -256,7 +258,7 @@ export const groupServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
|
const addUserToGroup = async ({ id, username, actor, actorId, actorAuthMethod, actorOrgId }: TAddUserToGroupDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -274,7 +276,7 @@ export const groupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!group)
|
if (!group)
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -286,7 +288,7 @@ export const groupServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
|
throw new ForbiddenRequestError({ message: "Failed to add user to more privileged group" });
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const user = await userDAL.findOne({ username });
|
||||||
if (!user) throw new BadRequestError({ message: `Failed to find user with username ${username}` });
|
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
||||||
|
|
||||||
const users = await addUsersToGroupByUserIds({
|
const users = await addUsersToGroupByUserIds({
|
||||||
group,
|
group,
|
||||||
@@ -311,7 +313,7 @@ export const groupServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TRemoveUserFromGroupDTO) => {
|
}: TRemoveUserFromGroupDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Failed to create group without organization" });
|
if (!actorOrgId) throw new UnauthorizedError({ message: "No organization ID provided in request" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -329,7 +331,7 @@ export const groupServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!group)
|
if (!group)
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: `Failed to find group with ID ${id}`
|
message: `Failed to find group with ID ${id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -341,7 +343,7 @@ export const groupServiceFactory = ({
|
|||||||
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
|
throw new ForbiddenRequestError({ message: "Failed to delete user from more privileged group" });
|
||||||
|
|
||||||
const user = await userDAL.findOne({ username });
|
const user = await userDAL.findOne({ username });
|
||||||
if (!user) throw new BadRequestError({ message: `Failed to find user with username ${username}` });
|
if (!user) throw new NotFoundError({ message: `Failed to find user with username ${username}` });
|
||||||
|
|
||||||
const users = await removeUsersFromGroupByUserIds({
|
const users = await removeUsersFromGroupByUserIds({
|
||||||
group,
|
group,
|
||||||
|
@@ -4,7 +4,7 @@ import ms from "ms";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
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";
|
||||||
@@ -34,18 +34,12 @@ export type TIdentityProjectAdditionalPrivilegeServiceFactory = ReturnType<
|
|||||||
|
|
||||||
// TODO(akhilmhdh): move this to more centralized
|
// TODO(akhilmhdh): move this to more centralized
|
||||||
export const UnpackedPermissionSchema = z.object({
|
export const UnpackedPermissionSchema = z.object({
|
||||||
subject: z.union([z.string().min(1), z.string().array()]).optional(),
|
subject: z
|
||||||
action: z.union([z.string().min(1), z.string().array()]),
|
.union([z.string().min(1), z.string().array()])
|
||||||
conditions: z
|
.transform((el) => (typeof el !== "string" ? el[0] : el))
|
||||||
.object({
|
.optional(),
|
||||||
environment: z.string().optional(),
|
action: z.union([z.string().min(1), z.string().array()]).transform((el) => (typeof el === "string" ? [el] : el)),
|
||||||
secretPath: z
|
conditions: z.unknown().optional()
|
||||||
.object({
|
|
||||||
$glob: z.string().min(1)
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const unpackPermissions = (permissions: unknown) =>
|
const unpackPermissions = (permissions: unknown) =>
|
||||||
@@ -71,12 +65,12 @@ 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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -143,12 +137,12 @@ 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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -173,7 +167,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
slug,
|
slug,
|
||||||
projectMembershipId: identityProjectMembership.id
|
projectMembershipId: identityProjectMembership.id
|
||||||
});
|
});
|
||||||
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
||||||
if (data?.slug) {
|
if (data?.slug) {
|
||||||
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
const existingSlug = await identityProjectAdditionalPrivilegeDAL.findOne({
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
@@ -224,12 +218,12 @@ 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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -254,7 +248,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
slug,
|
slug,
|
||||||
projectMembershipId: identityProjectMembership.id
|
projectMembershipId: identityProjectMembership.id
|
||||||
});
|
});
|
||||||
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
||||||
|
|
||||||
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
const deletedPrivilege = await identityProjectAdditionalPrivilegeDAL.deleteById(identityPrivilege.id);
|
||||||
return {
|
return {
|
||||||
@@ -274,12 +268,12 @@ 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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -293,7 +287,7 @@ export const identityProjectAdditionalPrivilegeServiceFactory = ({
|
|||||||
slug,
|
slug,
|
||||||
projectMembershipId: identityProjectMembership.id
|
projectMembershipId: identityProjectMembership.id
|
||||||
});
|
});
|
||||||
if (!identityPrivilege) throw new BadRequestError({ message: "Identity additional privilege not found" });
|
if (!identityPrivilege) throw new NotFoundError({ message: "Identity additional privilege not found" });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...identityPrivilege,
|
...identityPrivilege,
|
||||||
@@ -310,12 +304,12 @@ 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 BadRequestError({ message: "Project not found" });
|
if (!project) throw new NotFoundError({ message: "Project not found" });
|
||||||
const projectId = project.id;
|
const projectId = project.id;
|
||||||
|
|
||||||
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
const identityProjectMembership = await identityProjectDAL.findOne({ identityId, projectId });
|
||||||
if (!identityProjectMembership)
|
if (!identityProjectMembership)
|
||||||
throw new BadRequestError({ message: `Failed to find identity with id ${identityId}` });
|
throw new NotFoundError({ message: `Failed to find identity with id ${identityId}` });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
|
@@ -21,7 +21,7 @@ import {
|
|||||||
infisicalSymmetricDecrypt,
|
infisicalSymmetricDecrypt,
|
||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
@@ -253,7 +253,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId });
|
||||||
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@@ -289,10 +289,10 @@ 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 BadRequestError({ message: "Failed to find organization LDAP data" });
|
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId: ldapConfig.orgId });
|
||||||
if (!orgBot) throw new BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
||||||
|
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
@@ -375,7 +375,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 BadRequestError({ message: "Org not found" });
|
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
||||||
|
|
||||||
const ldapConfig = await getLdapCfg({
|
const ldapConfig = await getLdapCfg({
|
||||||
orgId: organization.id,
|
orgId: organization.id,
|
||||||
@@ -420,7 +420,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.LDAP)) {
|
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.LDAP)) {
|
||||||
throw new BadRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Login with LDAP is disabled by administrator."
|
message: "Login with LDAP is disabled by administrator."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -432,7 +432,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
||||||
|
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
@@ -700,7 +700,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" });
|
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
|
||||||
|
|
||||||
const groupMaps = await ldapGroupMapDAL.findLdapGroupMapsByLdapConfigId(ldapConfigId);
|
const groupMaps = await ldapGroupMapDAL.findLdapGroupMapsByLdapConfigId(ldapConfigId);
|
||||||
|
|
||||||
@@ -741,13 +741,13 @@ export const ldapConfigServiceFactory = ({
|
|||||||
const groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase);
|
const groups = await searchGroups(ldapConfig, groupSearchFilter, ldapConfig.groupSearchBase);
|
||||||
|
|
||||||
if (!groups.some((g) => g.cn === ldapGroupCN)) {
|
if (!groups.some((g) => g.cn === ldapGroupCN)) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find LDAP Group CN"
|
message: "Failed to find LDAP Group CN"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const group = await groupDAL.findOne({ slug: groupSlug, orgId });
|
const group = await groupDAL.findOne({ slug: groupSlug, orgId });
|
||||||
if (!group) throw new BadRequestError({ message: "Failed to find group" });
|
if (!group) throw new NotFoundError({ message: "Failed to find group" });
|
||||||
|
|
||||||
const groupMap = await ldapGroupMapDAL.create({
|
const groupMap = await ldapGroupMapDAL.create({
|
||||||
ldapConfigId,
|
ldapConfigId,
|
||||||
@@ -781,7 +781,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!ldapConfig) throw new BadRequestError({ message: "Failed to find organization LDAP data" });
|
if (!ldapConfig) throw new NotFoundError({ message: "Failed to find organization LDAP data" });
|
||||||
|
|
||||||
const [deletedGroupMap] = await ldapGroupMapDAL.delete({
|
const [deletedGroupMap] = await ldapGroupMapDAL.delete({
|
||||||
ldapConfigId: ldapConfig.id,
|
ldapConfigId: ldapConfig.id,
|
||||||
|
@@ -10,7 +10,7 @@ import { Knex } from "knex";
|
|||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { verifyOfflineLicense } from "@app/lib/crypto";
|
import { verifyOfflineLicense } from "@app/lib/crypto";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
|
||||||
@@ -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 BadRequestError({ message: "Org not found" });
|
if (!org) throw new NotFoundError({ message: "Organization 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 BadRequestError({ message: "Org not found" });
|
if (!org) throw new NotFoundError({ message: "Organization 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);
|
||||||
@@ -266,8 +266,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,8 +294,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,8 +340,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.get(
|
const { data } = await licenseServerCloudApi.request.get(
|
||||||
@@ -357,8 +357,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.get(
|
const { data } = await licenseServerCloudApi.request.get(
|
||||||
@@ -373,8 +373,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,8 +398,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { data } = await licenseServerCloudApi.request.patch(
|
const { data } = await licenseServerCloudApi.request.patch(
|
||||||
@@ -418,8 +418,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,8 +445,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
@@ -474,8 +474,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,8 +491,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
@@ -509,8 +509,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,8 +530,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,8 +547,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -564,8 +564,8 @@ export const licenseServiceFactory = ({
|
|||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TOidcConfigDALFactory = ReturnType<typeof oidcConfigDALFactory>;
|
export type TOidcConfigDALFactory = ReturnType<typeof oidcConfigDALFactory>;
|
||||||
@@ -7,5 +8,22 @@ export type TOidcConfigDALFactory = ReturnType<typeof oidcConfigDALFactory>;
|
|||||||
export const oidcConfigDALFactory = (db: TDbClient) => {
|
export const oidcConfigDALFactory = (db: TDbClient) => {
|
||||||
const oidcCfgOrm = ormify(db, TableName.OidcConfig);
|
const oidcCfgOrm = ormify(db, TableName.OidcConfig);
|
||||||
|
|
||||||
return { ...oidcCfgOrm };
|
const findEnforceableOidcCfg = async (orgId: string) => {
|
||||||
|
try {
|
||||||
|
const oidcCfg = await db
|
||||||
|
.replicaNode()(TableName.OidcConfig)
|
||||||
|
.where({
|
||||||
|
orgId,
|
||||||
|
isActive: true
|
||||||
|
})
|
||||||
|
.whereNotNull("lastUsed")
|
||||||
|
.first();
|
||||||
|
|
||||||
|
return oidcCfg;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "Find org by id" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...oidcCfgOrm, findEnforceableOidcCfg };
|
||||||
};
|
};
|
||||||
|
@@ -17,7 +17,7 @@ import {
|
|||||||
infisicalSymmetricDecrypt,
|
infisicalSymmetricDecrypt,
|
||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
@@ -77,7 +77,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
const getOidc = async (dto: TGetOidcCfgDTO) => {
|
||||||
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
const org = await orgDAL.findOne({ slug: dto.orgSlug });
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found",
|
message: "Organization not found",
|
||||||
name: "OrgNotFound"
|
name: "OrgNotFound"
|
||||||
});
|
});
|
||||||
@@ -98,7 +98,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!oidcCfg) {
|
if (!oidcCfg) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Failed to find organization OIDC configuration"
|
message: "Failed to find organization OIDC configuration"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
// 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 BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
@@ -160,7 +160,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.OIDC)) {
|
||||||
throw new BadRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Login with OIDC is disabled by administrator."
|
message: "Login with OIDC is disabled by administrator."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -173,7 +173,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
||||||
|
|
||||||
let user: TUsers;
|
let user: TUsers;
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
@@ -314,6 +314,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await oidcConfigDAL.update({ orgId }, { lastUsed: new Date() });
|
||||||
|
|
||||||
if (user.email && !user.isEmailVerified) {
|
if (user.email && !user.isEmailVerified) {
|
||||||
const token = await tokenService.createTokenForUser({
|
const token = await tokenService.createTokenForUser({
|
||||||
type: TokenType.TOKEN_EMAIL_VERIFICATION,
|
type: TokenType.TOKEN_EMAIL_VERIFICATION,
|
||||||
@@ -356,7 +358,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -378,7 +380,7 @@ 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 BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@@ -395,7 +397,8 @@ export const oidcConfigServiceFactory = ({
|
|||||||
tokenEndpoint,
|
tokenEndpoint,
|
||||||
userinfoEndpoint,
|
userinfoEndpoint,
|
||||||
jwksUri,
|
jwksUri,
|
||||||
isActive
|
isActive,
|
||||||
|
lastUsed: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (clientId !== undefined) {
|
if (clientId !== undefined) {
|
||||||
@@ -418,6 +421,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [ssoConfig] = await oidcConfigDAL.update({ orgId: org.id }, updateQuery);
|
const [ssoConfig] = await oidcConfigDAL.update({ orgId: org.id }, updateQuery);
|
||||||
|
await orgDAL.updateById(org.id, { authEnforced: false, scimEnabled: false });
|
||||||
return ssoConfig;
|
return ssoConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -443,7 +447,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
slug: orgSlug
|
slug: orgSlug
|
||||||
});
|
});
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found"
|
message: "Organization not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -549,7 +553,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Organization not found."
|
message: "Organization not found."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -560,7 +564,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!oidcCfg || !oidcCfg.isActive) {
|
if (!oidcCfg || !oidcCfg.isActive) {
|
||||||
throw new BadRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Failed to authenticate with OIDC SSO"
|
message: "Failed to authenticate with OIDC SSO"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -617,7 +621,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
if (oidcCfg.allowedEmailDomains) {
|
if (oidcCfg.allowedEmailDomains) {
|
||||||
const allowedDomains = oidcCfg.allowedEmailDomains.split(", ");
|
const allowedDomains = oidcCfg.allowedEmailDomains.split(", ");
|
||||||
if (!allowedDomains.includes(claims.email.split("@")[1])) {
|
if (!allowedDomains.includes(claims.email.split("@")[1])) {
|
||||||
throw new BadRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Email not allowed."
|
message: "Email not allowed."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -50,6 +50,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.OrgMembership),
|
selectAllTableCols(TableName.OrgMembership),
|
||||||
db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"),
|
db.ref("slug").withSchema(TableName.OrgRoles).withSchema(TableName.OrgRoles).as("customRoleSlug"),
|
||||||
|
db.ref("permissions").withSchema(TableName.OrgRoles),
|
||||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
db.ref("groupId").withSchema("userGroups"),
|
db.ref("groupId").withSchema("userGroups"),
|
||||||
db.ref("groupOrgId").withSchema("userGroups"),
|
db.ref("groupOrgId").withSchema("userGroups"),
|
||||||
@@ -167,8 +168,14 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
})
|
})
|
||||||
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
|
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
|
||||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
|
||||||
|
.andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||||
|
db.ref("username").withSchema(TableName.Users).as("username"),
|
||||||
// groups specific
|
// groups specific
|
||||||
db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"),
|
db.ref("id").withSchema(TableName.GroupProjectMembership).as("groupMembershipId"),
|
||||||
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.GroupProjectMembership).as("groupMembershipCreatedAt"),
|
||||||
@@ -256,6 +263,9 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
|
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
|
||||||
// general
|
// general
|
||||||
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
|
||||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
db.ref("orgId").withSchema(TableName.Project),
|
db.ref("orgId").withSchema(TableName.Project),
|
||||||
db.ref("id").withSchema(TableName.Project).as("projectId")
|
db.ref("id").withSchema(TableName.Project).as("projectId")
|
||||||
@@ -266,6 +276,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
key: "projectId",
|
key: "projectId",
|
||||||
parentMapper: ({
|
parentMapper: ({
|
||||||
orgId,
|
orgId,
|
||||||
|
username,
|
||||||
orgAuthEnforced,
|
orgAuthEnforced,
|
||||||
membershipId,
|
membershipId,
|
||||||
groupMembershipId,
|
groupMembershipId,
|
||||||
@@ -278,6 +289,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
orgAuthEnforced,
|
orgAuthEnforced,
|
||||||
userId,
|
userId,
|
||||||
projectId,
|
projectId,
|
||||||
|
username,
|
||||||
id: membershipId || groupMembershipId,
|
id: membershipId || groupMembershipId,
|
||||||
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
|
createdAt: membershipCreatedAt || groupMembershipCreatedAt,
|
||||||
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt
|
updatedAt: membershipUpdatedAt || groupMembershipUpdatedAt
|
||||||
@@ -353,6 +365,15 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
|
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
|
||||||
isTemporary: userAdditionalPrivilegesIsTemporary
|
isTemporary: userAdditionalPrivilegesIsTemporary
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -398,6 +419,7 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
`${TableName.IdentityProjectMembershipRole}.projectMembershipId`,
|
||||||
`${TableName.IdentityProjectMembership}.id`
|
`${TableName.IdentityProjectMembership}.id`
|
||||||
)
|
)
|
||||||
|
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.IdentityProjectMembership}.identityId`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.ProjectRoles,
|
TableName.ProjectRoles,
|
||||||
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
`${TableName.IdentityProjectMembershipRole}.customRoleId`,
|
||||||
@@ -414,11 +436,17 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityProjectMembership}.projectId`,
|
`${TableName.IdentityProjectMembership}.projectId`,
|
||||||
`${TableName.Project}.id`
|
`${TableName.Project}.id`
|
||||||
)
|
)
|
||||||
.where("identityId", identityId)
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
||||||
|
.andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
|
})
|
||||||
|
.where(`${TableName.IdentityProjectMembership}.identityId`, identityId)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
db.ref("id").withSchema(TableName.IdentityProjectMembership).as("membershipId"),
|
||||||
|
db.ref("name").withSchema(TableName.Identity).as("identityName"),
|
||||||
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
|
db.ref("orgId").withSchema(TableName.Project).as("orgId"), // Now you can select orgId from Project
|
||||||
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.IdentityProjectMembership).as("membershipCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.IdentityProjectMembership).as("membershipUpdatedAt"),
|
||||||
@@ -442,15 +470,19 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
db
|
db
|
||||||
.ref("temporaryAccessEndTime")
|
.ref("temporaryAccessEndTime")
|
||||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
.as("identityApTemporaryAccessEndTime")
|
.as("identityApTemporaryAccessEndTime"),
|
||||||
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
||||||
);
|
);
|
||||||
|
|
||||||
const permission = sqlNestRelationships({
|
const permission = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "membershipId",
|
key: "membershipId",
|
||||||
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, orgId }) => ({
|
parentMapper: ({ membershipId, membershipCreatedAt, membershipUpdatedAt, orgId, identityName }) => ({
|
||||||
id: membershipId,
|
id: membershipId,
|
||||||
identityId,
|
identityId,
|
||||||
|
username: identityName,
|
||||||
projectId,
|
projectId,
|
||||||
createdAt: membershipCreatedAt,
|
createdAt: membershipCreatedAt,
|
||||||
updatedAt: membershipUpdatedAt,
|
updatedAt: membershipUpdatedAt,
|
||||||
@@ -488,6 +520,15 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
||||||
isTemporary: identityApIsTemporary
|
isTemporary: identityApIsTemporary
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { TOrganizations } from "@app/db/schemas";
|
import { TOrganizations } from "@app/db/schemas";
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, AuthMethod } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
||||||
@@ -14,14 +14,19 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
|
|||||||
].includes(actorAuthMethod);
|
].includes(actorAuthMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) {
|
function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrganizations["authEnforced"]) {
|
||||||
if (actorAuthMethod === undefined) {
|
if (actorAuthMethod === undefined) {
|
||||||
throw new UnauthorizedError({ name: "No auth method defined" });
|
throw new UnauthorizedError({ name: "No auth method defined" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSamlEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod)) {
|
if (
|
||||||
throw new UnauthorizedError({ name: "Cannot access org-scoped resource" });
|
isOrgSsoEnforced &&
|
||||||
|
actorAuthMethod !== null &&
|
||||||
|
!isAuthMethodSaml(actorAuthMethod) &&
|
||||||
|
actorAuthMethod !== AuthMethod.OIDC
|
||||||
|
) {
|
||||||
|
throw new ForbiddenRequestError({ name: "Org auth enforced. Cannot access org-scoped resource" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isAuthMethodSaml, validateOrgSAML };
|
export { isAuthMethodSaml, validateOrgSSO };
|
||||||
|
@@ -0,0 +1,9 @@
|
|||||||
|
export type TBuildProjectPermissionDTO = {
|
||||||
|
permissions?: unknown;
|
||||||
|
role: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export type TBuildOrgPermissionDTO = {
|
||||||
|
permissions?: unknown;
|
||||||
|
role: string;
|
||||||
|
}[];
|
@@ -1,6 +1,7 @@
|
|||||||
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { PackRule, unpackRules } from "@casl/ability/extra";
|
import { PackRule, unpackRules } from "@casl/ability/extra";
|
||||||
import { MongoQuery } from "@ucast/mongo2js";
|
import { MongoQuery } from "@ucast/mongo2js";
|
||||||
|
import handlebars from "handlebars";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OrgMembershipRole,
|
OrgMembershipRole,
|
||||||
@@ -10,7 +11,8 @@ import {
|
|||||||
TProjectMemberships
|
TProjectMemberships
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { objectify } from "@app/lib/fn";
|
||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -19,8 +21,8 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
|
|||||||
|
|
||||||
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
|
||||||
import { TPermissionDALFactory } from "./permission-dal";
|
import { TPermissionDALFactory } from "./permission-dal";
|
||||||
import { validateOrgSAML } from "./permission-fns";
|
import { validateOrgSSO } from "./permission-fns";
|
||||||
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-types";
|
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-service-types";
|
||||||
import {
|
import {
|
||||||
buildServiceTokenProjectPermission,
|
buildServiceTokenProjectPermission,
|
||||||
projectAdminPermissions,
|
projectAdminPermissions,
|
||||||
@@ -62,7 +64,7 @@ export const permissionServiceFactory = ({
|
|||||||
permissions as PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[]
|
permissions as PackRule<RawRuleOf<MongoAbility<OrgPermissionSet>>>[]
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({ name: "OrgRoleInvalid", message: "Org role not found" });
|
throw new NotFoundError({ name: "OrgRoleInvalid", message: "Organization role not found" });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.reduce((curr, prev) => prev.concat(curr), []);
|
.reduce((curr, prev) => prev.concat(curr), []);
|
||||||
@@ -72,7 +74,7 @@ export const permissionServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildProjectPermission = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
const buildProjectPermissionRules = (projectUserRoles: TBuildProjectPermissionDTO) => {
|
||||||
const rules = projectUserRoles
|
const rules = projectUserRoles
|
||||||
.map(({ role, permissions }) => {
|
.map(({ role, permissions }) => {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
@@ -90,7 +92,7 @@ export const permissionServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
name: "ProjectRoleInvalid",
|
name: "ProjectRoleInvalid",
|
||||||
message: "Project role not found"
|
message: "Project role not found"
|
||||||
});
|
});
|
||||||
@@ -98,9 +100,7 @@ export const permissionServiceFactory = ({
|
|||||||
})
|
})
|
||||||
.reduce((curr, prev) => prev.concat(curr), []);
|
.reduce((curr, prev) => prev.concat(curr), []);
|
||||||
|
|
||||||
return createMongoAbility<ProjectPermissionSet>(rules, {
|
return rules;
|
||||||
conditionsMatcher
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -114,11 +114,11 @@ export const permissionServiceFactory = ({
|
|||||||
) => {
|
) => {
|
||||||
// when token is scoped, ensure the passed org id is same as user org id
|
// when token is scoped, ensure the passed org id is same as user org id
|
||||||
if (userOrgId && userOrgId !== orgId)
|
if (userOrgId && userOrgId !== orgId)
|
||||||
throw new BadRequestError({ message: "Invalid user token. Scoped to different organization." });
|
throw new ForbiddenRequestError({ message: "Invalid user token. Scoped to different organization." });
|
||||||
const membership = await permissionDAL.getOrgPermission(userId, orgId);
|
const membership = await permissionDAL.getOrgPermission(userId, orgId);
|
||||||
if (!membership) throw new UnauthorizedError({ name: "User not in org" });
|
if (!membership) throw new ForbiddenRequestError({ name: "You are not apart of this organization" });
|
||||||
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
|
if (membership.role === OrgMembershipRole.Custom && !membership.permissions) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new BadRequestError({ name: "Custom organization permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the org ID is API_KEY, the request is being made with an API Key.
|
// If the org ID is API_KEY, the request is being made with an API Key.
|
||||||
@@ -127,10 +127,10 @@ export const permissionServiceFactory = ({
|
|||||||
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
||||||
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
||||||
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) {
|
if (userOrgId !== "API_KEY" && membership.orgId !== userOrgId) {
|
||||||
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
validateOrgSAML(authMethod, membership.orgAuthEnforced);
|
validateOrgSSO(authMethod, membership.orgAuthEnforced);
|
||||||
|
|
||||||
const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat(
|
const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat(
|
||||||
membership?.groups?.map(({ role, customRolePermission }) => ({
|
membership?.groups?.map(({ role, customRolePermission }) => ({
|
||||||
@@ -143,9 +143,9 @@ export const permissionServiceFactory = ({
|
|||||||
|
|
||||||
const getIdentityOrgPermission = async (identityId: string, orgId: string) => {
|
const getIdentityOrgPermission = async (identityId: string, orgId: string) => {
|
||||||
const membership = await permissionDAL.getOrgIdentityPermission(identityId, orgId);
|
const membership = await permissionDAL.getOrgIdentityPermission(identityId, orgId);
|
||||||
if (!membership) throw new UnauthorizedError({ name: "Identity not in org" });
|
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 BadRequestError({ name: "Custom permission not found" });
|
throw new NotFoundError({ name: "Custom organization permission not found" });
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
permission: buildOrgPermission([{ role: membership.role, permissions: membership.permissions }]),
|
permission: buildOrgPermission([{ role: membership.role, permissions: membership.permissions }]),
|
||||||
@@ -166,8 +166,8 @@ export const permissionServiceFactory = ({
|
|||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return getIdentityOrgPermission(id, orgId);
|
return getIdentityOrgPermission(id, orgId);
|
||||||
default:
|
default:
|
||||||
throw new UnauthorizedError({
|
throw new BadRequestError({
|
||||||
message: "Permission not defined",
|
message: "Invalid actor provided",
|
||||||
name: "Get org permission"
|
name: "Get org permission"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -179,7 +179,7 @@ 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 BadRequestError({ message: "Role not found" });
|
if (!orgRole) throw new NotFoundError({ message: "Specified role was not found" });
|
||||||
return {
|
return {
|
||||||
permission: buildOrgPermission([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]),
|
permission: buildOrgPermission([{ role: OrgMembershipRole.Custom, permissions: orgRole.permissions }]),
|
||||||
role: orgRole
|
role: orgRole
|
||||||
@@ -196,12 +196,12 @@ export const permissionServiceFactory = ({
|
|||||||
userOrgId?: string
|
userOrgId?: string
|
||||||
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
): Promise<TProjectPermissionRT<ActorType.USER>> => {
|
||||||
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
const userProjectPermission = await permissionDAL.getProjectPermission(userId, projectId);
|
||||||
if (!userProjectPermission) throw new UnauthorizedError({ name: "User not in project" });
|
if (!userProjectPermission) throw new ForbiddenRequestError({ name: "User not a part of the specified project" });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)
|
userProjectPermission.roles.some(({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions)
|
||||||
) {
|
) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new NotFoundError({ name: "The permission was not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the org ID is API_KEY, the request is being made with an API Key.
|
// If the org ID is API_KEY, the request is being made with an API Key.
|
||||||
@@ -210,10 +210,10 @@ export const permissionServiceFactory = ({
|
|||||||
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
// Extra: This means that when users are using API keys to make requests, they can't use slug-based routes.
|
||||||
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
// Slug-based routes depend on the organization ID being present on the request, since project slugs aren't globally unique, and we need a way to filter by organization.
|
||||||
if (userOrgId !== "API_KEY" && userProjectPermission.orgId !== userOrgId) {
|
if (userOrgId !== "API_KEY" && userProjectPermission.orgId !== userOrgId) {
|
||||||
throw new UnauthorizedError({ name: "You are not logged into this organization" });
|
throw new ForbiddenRequestError({ name: "You are not logged into this organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
validateOrgSAML(authMethod, userProjectPermission.orgAuthEnforced);
|
validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
|
||||||
|
|
||||||
// join two permissions and pass to build the final permission set
|
// join two permissions and pass to build the final permission set
|
||||||
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
|
||||||
@@ -223,8 +223,32 @@ export const permissionServiceFactory = ({
|
|||||||
permissions
|
permissions
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
|
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||||
|
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
|
||||||
|
const metadataKeyValuePair = objectify(
|
||||||
|
userProjectPermission.metadata,
|
||||||
|
(i) => i.key,
|
||||||
|
(i) => i.value
|
||||||
|
);
|
||||||
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
id: userProjectPermission.userId,
|
||||||
|
username: userProjectPermission.username,
|
||||||
|
metadata: metadataKeyValuePair
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ data: false }
|
||||||
|
);
|
||||||
|
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||||
|
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
permission,
|
||||||
membership: userProjectPermission,
|
membership: userProjectPermission,
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
userProjectPermission.roles.findIndex(
|
userProjectPermission.roles.findIndex(
|
||||||
@@ -239,18 +263,19 @@ export const permissionServiceFactory = ({
|
|||||||
identityOrgId: string | undefined
|
identityOrgId: string | undefined
|
||||||
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
): Promise<TProjectPermissionRT<ActorType.IDENTITY>> => {
|
||||||
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
const identityProjectPermission = await permissionDAL.getProjectIdentityPermission(identityId, projectId);
|
||||||
if (!identityProjectPermission) throw new UnauthorizedError({ name: "Identity not in project" });
|
if (!identityProjectPermission)
|
||||||
|
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified project" });
|
||||||
|
|
||||||
if (
|
if (
|
||||||
identityProjectPermission.roles.some(
|
identityProjectPermission.roles.some(
|
||||||
({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions
|
({ role, permissions }) => role === ProjectMembershipRole.Custom && !permissions
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new BadRequestError({ name: "Custom permission not found" });
|
throw new NotFoundError({ name: "Custom permission not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (identityProjectPermission.orgId !== identityOrgId) {
|
if (identityProjectPermission.orgId !== identityOrgId) {
|
||||||
throw new UnauthorizedError({ name: "You are not a member of this organization" });
|
throw new ForbiddenRequestError({ name: "Identity is not a member of the specified organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const rolePermissions =
|
const rolePermissions =
|
||||||
@@ -261,8 +286,32 @@ export const permissionServiceFactory = ({
|
|||||||
permissions
|
permissions
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
|
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||||
|
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
|
||||||
|
const metadataKeyValuePair = objectify(
|
||||||
|
identityProjectPermission.metadata,
|
||||||
|
(i) => i.key,
|
||||||
|
(i) => i.value
|
||||||
|
);
|
||||||
|
const interpolateRules = templatedRules(
|
||||||
|
{
|
||||||
|
identity: {
|
||||||
|
id: identityProjectPermission.identityId,
|
||||||
|
username: identityProjectPermission.username,
|
||||||
|
metadata: metadataKeyValuePair
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ data: false }
|
||||||
|
);
|
||||||
|
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||||
|
JSON.parse(interpolateRules) as RawRuleOf<MongoAbility<ProjectPermissionSet>>[],
|
||||||
|
{
|
||||||
|
conditionsMatcher
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission(rolePermissions.concat(additionalPrivileges)),
|
permission,
|
||||||
membership: identityProjectPermission,
|
membership: identityProjectPermission,
|
||||||
hasRole: (role: string) =>
|
hasRole: (role: string) =>
|
||||||
identityProjectPermission.roles.findIndex(
|
identityProjectPermission.roles.findIndex(
|
||||||
@@ -277,25 +326,23 @@ 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 BadRequestError({ message: "Service token not found" });
|
if (!serviceToken) throw new NotFoundError({ message: "Service token not found" });
|
||||||
|
|
||||||
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
|
const serviceTokenProject = await projectDAL.findById(serviceToken.projectId);
|
||||||
|
|
||||||
if (!serviceTokenProject) throw new BadRequestError({ message: "Service token not linked to a project" });
|
if (!serviceTokenProject) throw new BadRequestError({ message: "Service token not linked to a project" });
|
||||||
|
|
||||||
if (serviceTokenProject.orgId !== actorOrgId) {
|
if (serviceTokenProject.orgId !== actorOrgId) {
|
||||||
throw new UnauthorizedError({ message: "Service token not a part of this organization" });
|
throw new ForbiddenRequestError({ message: "Service token not a part of the specified organization" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceToken.projectId !== projectId)
|
if (serviceToken.projectId !== projectId) {
|
||||||
throw new UnauthorizedError({
|
throw new ForbiddenRequestError({ name: "Service token not a part of the specified project" });
|
||||||
message: "Failed to find service authorization for given project"
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (serviceTokenProject.orgId !== actorOrgId)
|
if (serviceTokenProject.orgId !== actorOrgId) {
|
||||||
throw new UnauthorizedError({
|
throw new ForbiddenRequestError({ message: "Service token not a part of the specified organization" });
|
||||||
message: "Failed to find service authorization for given project"
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
const scopes = ServiceTokenScopes.parse(serviceToken.scopes || []);
|
||||||
return {
|
return {
|
||||||
@@ -335,8 +382,8 @@ export const permissionServiceFactory = ({
|
|||||||
case ActorType.IDENTITY:
|
case ActorType.IDENTITY:
|
||||||
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
return getIdentityProjectPermission(id, projectId, actorOrgId) as Promise<TProjectPermissionRT<T>>;
|
||||||
default:
|
default:
|
||||||
throw new UnauthorizedError({
|
throw new BadRequestError({
|
||||||
message: "Permission not defined",
|
message: "Invalid actor provided",
|
||||||
name: "Get project permission"
|
name: "Get project permission"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -346,15 +393,23 @@ export const permissionServiceFactory = ({
|
|||||||
const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole);
|
const isCustomRole = !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole);
|
||||||
if (isCustomRole) {
|
if (isCustomRole) {
|
||||||
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
|
const projectRole = await projectRoleDAL.findOne({ slug: role, projectId });
|
||||||
if (!projectRole) throw new BadRequestError({ message: `Role not found: ${role}` });
|
if (!projectRole) throw new NotFoundError({ message: `Specified role was not found: ${role}` });
|
||||||
|
const rules = buildProjectPermissionRules([
|
||||||
|
{ role: ProjectMembershipRole.Custom, permissions: projectRole.permissions }
|
||||||
|
]);
|
||||||
return {
|
return {
|
||||||
permission: buildProjectPermission([
|
permission: createMongoAbility<ProjectPermissionSet>(rules, {
|
||||||
{ role: ProjectMembershipRole.Custom, permissions: projectRole.permissions }
|
conditionsMatcher
|
||||||
]),
|
}),
|
||||||
role: projectRole
|
role: projectRole
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { permission: buildProjectPermission([{ role, permissions: [] }]) };
|
|
||||||
|
const rules = buildProjectPermissionRules([{ role, permissions: [] }]);
|
||||||
|
const permission = createMongoAbility<ProjectPermissionSet>(rules, {
|
||||||
|
conditionsMatcher
|
||||||
|
});
|
||||||
|
return { permission };
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -365,6 +420,6 @@ export const permissionServiceFactory = ({
|
|||||||
getOrgPermissionByRole,
|
getOrgPermissionByRole,
|
||||||
getProjectPermissionByRole,
|
getProjectPermissionByRole,
|
||||||
buildOrgPermission,
|
buildOrgPermission,
|
||||||
buildProjectPermission
|
buildProjectPermissionRules
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,9 +1,47 @@
|
|||||||
export type TBuildProjectPermissionDTO = {
|
import picomatch from "picomatch";
|
||||||
permissions?: unknown;
|
import { z } from "zod";
|
||||||
role: string;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
export type TBuildOrgPermissionDTO = {
|
export enum PermissionConditionOperators {
|
||||||
permissions?: unknown;
|
$IN = "$in",
|
||||||
role: string;
|
$ALL = "$all",
|
||||||
}[];
|
$REGEX = "$regex",
|
||||||
|
$EQ = "$eq",
|
||||||
|
$NEQ = "$ne",
|
||||||
|
$GLOB = "$glob"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PermissionConditionSchema = {
|
||||||
|
[PermissionConditionOperators.$IN]: z.string().min(1).array(),
|
||||||
|
[PermissionConditionOperators.$ALL]: z.string().min(1).array(),
|
||||||
|
[PermissionConditionOperators.$REGEX]: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.refine(
|
||||||
|
(el) => {
|
||||||
|
try {
|
||||||
|
// eslint-disable-next-line no-new
|
||||||
|
new RegExp(el);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ message: "Invalid regex pattern" }
|
||||||
|
),
|
||||||
|
[PermissionConditionOperators.$EQ]: z.string().min(1),
|
||||||
|
[PermissionConditionOperators.$NEQ]: z.string().min(1),
|
||||||
|
[PermissionConditionOperators.$GLOB]: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.refine(
|
||||||
|
(el) => {
|
||||||
|
try {
|
||||||
|
picomatch.parse([el]);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ message: "Invalid glob pattern" }
|
||||||
|
)
|
||||||
|
};
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
|
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 { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { PermissionConditionOperators, PermissionConditionSchema } from "./permission-types";
|
||||||
|
|
||||||
export enum ProjectPermissionActions {
|
export enum ProjectPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Create = "create",
|
Create = "create",
|
||||||
@@ -10,6 +14,15 @@ export enum ProjectPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ProjectPermissionCmekActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete",
|
||||||
|
Encrypt = "encrypt",
|
||||||
|
Decrypt = "decrypt"
|
||||||
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionSub {
|
export enum ProjectPermissionSub {
|
||||||
Role = "role",
|
Role = "role",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
@@ -34,10 +47,29 @@ export enum ProjectPermissionSub {
|
|||||||
CertificateTemplates = "certificate-templates",
|
CertificateTemplates = "certificate-templates",
|
||||||
PkiAlerts = "pki-alerts",
|
PkiAlerts = "pki-alerts",
|
||||||
PkiCollections = "pki-collections",
|
PkiCollections = "pki-collections",
|
||||||
Kms = "kms"
|
Kms = "kms",
|
||||||
|
Cmek = "cmek"
|
||||||
}
|
}
|
||||||
|
|
||||||
type SubjectFields = {
|
export type SecretSubjectFields = {
|
||||||
|
environment: string;
|
||||||
|
secretPath: string;
|
||||||
|
// secretName: 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 = {
|
||||||
environment: string;
|
environment: string;
|
||||||
secretPath: string;
|
secretPath: string;
|
||||||
};
|
};
|
||||||
@@ -45,11 +77,14 @@ type SubjectFields = {
|
|||||||
export type ProjectPermissionSet =
|
export type ProjectPermissionSet =
|
||||||
| [
|
| [
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SubjectFields)
|
ProjectPermissionSub.Secrets | (ForcedSubject<ProjectPermissionSub.Secrets> & SecretSubjectFields)
|
||||||
]
|
]
|
||||||
| [
|
| [
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSub.SecretFolders | (ForcedSubject<ProjectPermissionSub.SecretFolders> & SubjectFields)
|
(
|
||||||
|
| ProjectPermissionSub.SecretFolders
|
||||||
|
| (ForcedSubject<ProjectPermissionSub.SecretFolders> & SecretFolderSubjectFields)
|
||||||
|
)
|
||||||
]
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||||
@@ -70,134 +105,254 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiAlerts]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
| [ProjectPermissionActions, ProjectPermissionSub.PkiCollections]
|
||||||
|
| [ProjectPermissionCmekActions, ProjectPermissionSub.Cmek]
|
||||||
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Delete, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Project]
|
||||||
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
||||||
|
|
||||||
export const fullProjectPermissionSet: [ProjectPermissionActions, ProjectPermissionSub][] = [
|
const CASL_ACTION_SCHEMA_NATIVE_ENUM = <ACTION extends z.EnumLike>(actions: ACTION) =>
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Secrets],
|
z
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Secrets],
|
.union([z.nativeEnum(actions), z.nativeEnum(actions).array().min(1)])
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets],
|
.transform((el) => (typeof el === "string" ? [el] : el));
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval],
|
const CASL_ACTION_SCHEMA_ENUM = <ACTION extends z.EnumValues>(actions: ACTION) =>
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval],
|
z.union([z.enum(actions), z.enum(actions).array().min(1)]).transform((el) => (typeof el === "string" ? [el] : el));
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval],
|
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval],
|
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation],
|
const SecretConditionSchema = z
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation],
|
.object({
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation],
|
environment: z.union([
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation],
|
z.string(),
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
|
})
|
||||||
|
.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()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback],
|
export const ProjectPermissionSchema = z.discriminatedUnion("subject", [
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback],
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Secrets).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Member],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Member],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Member],
|
),
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Member],
|
conditions: SecretConditionSchema.describe(
|
||||||
|
"When specified, only matching conditions will be allowed to access given resource."
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Groups],
|
).optional()
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Groups],
|
}),
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Groups],
|
z.object({
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Groups],
|
subject: z.literal(ProjectPermissionSub.SecretApproval).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Role],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Role],
|
)
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Role],
|
}),
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Role],
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.SecretRotation).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Integrations],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Integrations],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations],
|
)
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations],
|
}),
|
||||||
|
z.object({
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks],
|
subject: z.literal(ProjectPermissionSub.SecretRollback).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks],
|
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Read, ProjectPermissionActions.Create]).describe(
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks],
|
)
|
||||||
|
}),
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Identity],
|
z.object({
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Identity],
|
subject: z.literal(ProjectPermissionSub.Member).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Identity],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Identity],
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens],
|
}),
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens],
|
z.object({
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens],
|
subject: z.literal(ProjectPermissionSub.Groups).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Settings],
|
)
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Settings],
|
}),
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Settings],
|
z.object({
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Settings],
|
subject: z.literal(ProjectPermissionSub.Role).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Environments],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Environments],
|
)
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Environments],
|
}),
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Environments],
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Integrations).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Tags],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Tags],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Tags],
|
)
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Tags],
|
}),
|
||||||
|
z.object({
|
||||||
// TODO(Daniel): Remove the audit logs permissions from project-level permissions.
|
subject: z.literal(ProjectPermissionSub.Webhooks).describe("The entity this permission pertains to."),
|
||||||
// TODO: We haven't done this yet because it might break existing roles, since those roles will become "invalid" since the audit log permission defined on those roles, no longer exist in the project-level defined permissions.
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs],
|
)
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs],
|
}),
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs],
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Identity).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList],
|
)
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList],
|
}),
|
||||||
|
z.object({
|
||||||
// double check if all CRUD are needed for CA and Certificates
|
subject: z.literal(ProjectPermissionSub.ServiceTokens).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateAuthorities],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateAuthorities],
|
)
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateAuthorities],
|
}),
|
||||||
|
z.object({
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.Certificates],
|
subject: z.literal(ProjectPermissionSub.Settings).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.Certificates],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates],
|
)
|
||||||
|
}),
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates],
|
z.object({
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates],
|
subject: z.literal(ProjectPermissionSub.Environments).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates],
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts],
|
}),
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts],
|
z.object({
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts],
|
subject: z.literal(ProjectPermissionSub.Tags).describe("The entity this permission pertains to."),
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts],
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections],
|
)
|
||||||
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections],
|
}),
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections],
|
z.object({
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections],
|
subject: z.literal(ProjectPermissionSub.AuditLogs).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Project],
|
"Describe what action an entity can take."
|
||||||
[ProjectPermissionActions.Delete, ProjectPermissionSub.Project],
|
)
|
||||||
|
}),
|
||||||
[ProjectPermissionActions.Edit, ProjectPermissionSub.Kms]
|
z.object({
|
||||||
];
|
subject: z.literal(ProjectPermissionSub.IpAllowList).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.CertificateAuthorities).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Certificates).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to. "),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.PkiAlerts).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.PkiCollections).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Project).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete]).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Kms).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_ENUM([ProjectPermissionActions.Edit]).describe(
|
||||||
|
"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({
|
||||||
|
subject: z.literal(ProjectPermissionSub.Cmek).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionCmekActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
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
|
||||||
fullProjectPermissionSet.forEach((permission) => {
|
[
|
||||||
const [action, subject] = permission;
|
ProjectPermissionSub.Secrets,
|
||||||
can(action, subject);
|
ProjectPermissionSub.SecretApproval,
|
||||||
|
ProjectPermissionSub.SecretRotation,
|
||||||
|
ProjectPermissionSub.Member,
|
||||||
|
ProjectPermissionSub.Groups,
|
||||||
|
ProjectPermissionSub.Role,
|
||||||
|
ProjectPermissionSub.Integrations,
|
||||||
|
ProjectPermissionSub.Webhooks,
|
||||||
|
ProjectPermissionSub.Identity,
|
||||||
|
ProjectPermissionSub.ServiceTokens,
|
||||||
|
ProjectPermissionSub.Settings,
|
||||||
|
ProjectPermissionSub.Environments,
|
||||||
|
ProjectPermissionSub.Tags,
|
||||||
|
ProjectPermissionSub.AuditLogs,
|
||||||
|
ProjectPermissionSub.IpAllowList,
|
||||||
|
ProjectPermissionSub.CertificateAuthorities,
|
||||||
|
ProjectPermissionSub.Certificates,
|
||||||
|
ProjectPermissionSub.CertificateTemplates,
|
||||||
|
ProjectPermissionSub.PkiAlerts,
|
||||||
|
ProjectPermissionSub.PkiCollections
|
||||||
|
].forEach((el) => {
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
el as ProjectPermissionSub
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
can([ProjectPermissionActions.Edit, ProjectPermissionActions.Delete], ProjectPermissionSub.Project);
|
||||||
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
||||||
|
can([ProjectPermissionActions.Edit], ProjectPermissionSub.Kms);
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCmekActions.Create,
|
||||||
|
ProjectPermissionCmekActions.Edit,
|
||||||
|
ProjectPermissionCmekActions.Delete,
|
||||||
|
ProjectPermissionCmekActions.Read,
|
||||||
|
ProjectPermissionCmekActions.Encrypt,
|
||||||
|
ProjectPermissionCmekActions.Decrypt
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Cmek
|
||||||
|
);
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,73 +361,128 @@ export const projectAdminPermissions = buildAdminPermissionRules();
|
|||||||
const buildMemberPermissionRules = () => {
|
const buildMemberPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Secrets
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretApproval);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.SecretRotation);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.SecretRollback);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
can([ProjectPermissionActions.Read, ProjectPermissionActions.Create], ProjectPermissionSub.Member);
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.Groups);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Integrations
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Webhooks
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.ServiceTokens
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Settings
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Environments
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Tags
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.Role);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.AuditLogs);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.IpAllowList);
|
||||||
|
|
||||||
// double check if all CRUD are needed for CA and Certificates
|
// double check if all CRUD are needed for CA and Certificates
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateAuthorities);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
can(
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
|
[
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
ProjectPermissionActions.Read,
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
ProjectPermissionActions.Edit,
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionActions.Delete
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Certificates
|
||||||
|
);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.CertificateTemplates);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiAlerts);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
can([ProjectPermissionActions.Read], ProjectPermissionSub.PkiCollections);
|
||||||
|
|
||||||
|
can(
|
||||||
|
[
|
||||||
|
ProjectPermissionCmekActions.Create,
|
||||||
|
ProjectPermissionCmekActions.Edit,
|
||||||
|
ProjectPermissionCmekActions.Delete,
|
||||||
|
ProjectPermissionCmekActions.Read,
|
||||||
|
ProjectPermissionCmekActions.Encrypt,
|
||||||
|
ProjectPermissionCmekActions.Decrypt
|
||||||
|
],
|
||||||
|
ProjectPermissionSub.Cmek
|
||||||
|
);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
@@ -300,6 +510,7 @@ const buildViewerPermissionRules = () => {
|
|||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
||||||
|
can(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
@@ -382,32 +593,19 @@ export const isAtLeastAsPrivilegedWorkspace = (
|
|||||||
|
|
||||||
return set1.size >= set2.size;
|
return set1.size >= set2.size;
|
||||||
};
|
};
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
/*
|
export const SecretV2SubjectFieldMapper = (arg: string) => {
|
||||||
* Case: The user requests to create a role with permissions that are not valid and not supposed to be used ever.
|
switch (arg) {
|
||||||
* If we don't check for this, we can run into issues where functions like the `isAtLeastAsPrivileged` will not work as expected, because we compare the size of each permission set.
|
case "environment":
|
||||||
* If the permission set contains invalid permissions, the size will be different, and result in incorrect results.
|
return null;
|
||||||
*/
|
case "secretPath":
|
||||||
export const validateProjectPermissions = (permissions: unknown) => {
|
return null;
|
||||||
const parsedPermissions =
|
case "secretName":
|
||||||
typeof permissions === "string" ? (JSON.parse(permissions) as string[]) : (permissions as string[]);
|
return `${TableName.SecretV2}.key`;
|
||||||
|
case "secretTags":
|
||||||
const flattenedPermissions = [...parsedPermissions];
|
return `${TableName.SecretTag}.slug`;
|
||||||
|
default:
|
||||||
for (const perm of flattenedPermissions) {
|
throw new BadRequestError({ message: `Invalid dynamic knex operator field: ${arg}` });
|
||||||
const [action, subject] = perm;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!fullProjectPermissionSet.find(
|
|
||||||
(currentPermission) => currentPermission[0] === action && currentPermission[1] === subject
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Permission action ${action} on subject ${subject} is not valid`,
|
|
||||||
name: "Create Role"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* eslint-enable */
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
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";
|
||||||
@@ -42,7 +42,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
...dto
|
...dto
|
||||||
}: TCreateUserPrivilegeDTO) => {
|
}: TCreateUserPrivilegeDTO) => {
|
||||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -94,14 +94,14 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
...dto
|
...dto
|
||||||
}: TUpdateUserPrivilegeDTO) => {
|
}: TUpdateUserPrivilegeDTO) => {
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege 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 BadRequestError({ message: "Project membership not found" });
|
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -147,13 +147,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
|
|
||||||
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 BadRequestError({ message: "User additional privilege not found" });
|
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege 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 BadRequestError({ message: "Project membership not found" });
|
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -176,13 +176,13 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TGetUserPrivilegeDetailsDTO) => {
|
}: TGetUserPrivilegeDetailsDTO) => {
|
||||||
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
const userPrivilege = await projectUserAdditionalPrivilegeDAL.findById(privilegeId);
|
||||||
if (!userPrivilege) throw new BadRequestError({ message: "User additional privilege not found" });
|
if (!userPrivilege) throw new NotFoundError({ message: "User additional privilege 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 BadRequestError({ message: "Project membership not found" });
|
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -204,7 +204,7 @@ export const projectUserAdditionalPrivilegeServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TListUserPrivilegesDTO) => {
|
}: TListUserPrivilegesDTO) => {
|
||||||
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
const projectMembership = await projectMembershipDAL.findById(projectMembershipId);
|
||||||
if (!projectMembership) throw new BadRequestError({ message: "Project membership not found" });
|
if (!projectMembership) throw new NotFoundError({ message: "Project membership not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
|
@@ -19,10 +19,11 @@ import {
|
|||||||
infisicalSymmetricDecrypt,
|
infisicalSymmetricDecrypt,
|
||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
|
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
@@ -51,6 +52,8 @@ type TSamlConfigServiceFactoryDep = {
|
|||||||
TOrgDALFactory,
|
TOrgDALFactory,
|
||||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
identityMetadataDAL: Pick<TIdentityMetadataDALFactory, "delete" | "insertMany" | "transaction">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
||||||
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
@@ -71,7 +74,8 @@ export const samlConfigServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService,
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService
|
smtpService,
|
||||||
|
identityMetadataDAL
|
||||||
}: TSamlConfigServiceFactoryDep) => {
|
}: TSamlConfigServiceFactoryDep) => {
|
||||||
const createSamlCfg = async ({
|
const createSamlCfg = async ({
|
||||||
cert,
|
cert,
|
||||||
@@ -187,7 +191,7 @@ 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 BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@@ -253,7 +257,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
|
|
||||||
ssoConfig = await samlConfigDAL.findById(id);
|
ssoConfig = await samlConfigDAL.findById(id);
|
||||||
}
|
}
|
||||||
if (!ssoConfig) throw new BadRequestError({ message: "Failed to find organization SSO data" });
|
if (!ssoConfig) throw new NotFoundError({ message: "Failed to find organization 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") {
|
||||||
@@ -279,7 +283,7 @@ 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 BadRequestError({ message: "Org bot not found", name: "OrgBotNotFound" });
|
if (!orgBot) throw new NotFoundError({ message: "Organization bot not found", name: "OrgBotNotFound" });
|
||||||
const key = infisicalSymmetricDecrypt({
|
const key = infisicalSymmetricDecrypt({
|
||||||
ciphertext: orgBot.encryptedSymmetricKey,
|
ciphertext: orgBot.encryptedSymmetricKey,
|
||||||
iv: orgBot.symmetricKeyIV,
|
iv: orgBot.symmetricKeyIV,
|
||||||
@@ -332,13 +336,14 @@ export const samlConfigServiceFactory = ({
|
|||||||
lastName,
|
lastName,
|
||||||
authProvider,
|
authProvider,
|
||||||
orgId,
|
orgId,
|
||||||
relayState
|
relayState,
|
||||||
|
metadata
|
||||||
}: TSamlLoginDTO) => {
|
}: TSamlLoginDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.SAML)) {
|
if (serverCfg.enabledLoginMethods && !serverCfg.enabledLoginMethods.includes(LoginMethod.SAML)) {
|
||||||
throw new BadRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Login with SAML is disabled by administrator."
|
message: "Login with SAML is disabled by administrator."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -350,7 +355,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const organization = await orgDAL.findOrgById(orgId);
|
const organization = await orgDAL.findOrgById(orgId);
|
||||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
if (!organization) throw new NotFoundError({ message: "Organization not found" });
|
||||||
|
|
||||||
let user: TUsers;
|
let user: TUsers;
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
@@ -386,6 +391,21 @@ export const samlConfigServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (metadata && foundUser.id) {
|
||||||
|
await identityMetadataDAL.delete({ userId: foundUser.id, orgId }, tx);
|
||||||
|
if (metadata.length) {
|
||||||
|
await identityMetadataDAL.insertMany(
|
||||||
|
metadata.map(({ key, value }) => ({
|
||||||
|
userId: foundUser.id,
|
||||||
|
orgId,
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return foundUser;
|
return foundUser;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -474,6 +494,20 @@ export const samlConfigServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (metadata && newUser.id) {
|
||||||
|
await identityMetadataDAL.delete({ userId: newUser.id, orgId }, tx);
|
||||||
|
if (metadata.length) {
|
||||||
|
await identityMetadataDAL.insertMany(
|
||||||
|
metadata.map(({ key, value }) => ({
|
||||||
|
userId: newUser?.id,
|
||||||
|
orgId,
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return newUser;
|
return newUser;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -53,4 +53,5 @@ export type TSamlLoginDTO = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
// saml thingy
|
// saml thingy
|
||||||
relayState?: string;
|
relayState?: string;
|
||||||
|
metadata?: { key: string; value: string }[];
|
||||||
};
|
};
|
||||||
|
@@ -9,7 +9,7 @@ import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee
|
|||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
import { TScimDALFactory } from "@app/ee/services/scim/scim-dal";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, ScimRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
@@ -75,7 +75,14 @@ type TScimServiceFactoryDep = {
|
|||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
"create" | "findOne" | "findAllGroupMembers" | "delete" | "findGroups" | "transaction" | "updateById" | "update"
|
| "create"
|
||||||
|
| "findOne"
|
||||||
|
| "findAllGroupPossibleMembers"
|
||||||
|
| "delete"
|
||||||
|
| "findGroups"
|
||||||
|
| "transaction"
|
||||||
|
| "updateById"
|
||||||
|
| "update"
|
||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
userGroupMembershipDAL: Pick<
|
userGroupMembershipDAL: Pick<
|
||||||
@@ -169,7 +176,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 BadRequestError({ message: "Failed to find SCIM token to delete" });
|
if (!scimToken) throw new NotFoundError({ message: "Failed to find SCIM token to delete" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -775,7 +782,7 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = await groupDAL.findAllGroupMembers({
|
const users = await groupDAL.findAllGroupPossibleMembers({
|
||||||
orgId: group.orgId,
|
orgId: group.orgId,
|
||||||
groupId: group.id
|
groupId: group.id
|
||||||
});
|
});
|
||||||
|
@@ -12,10 +12,21 @@ export type TSecretApprovalPolicyDALFactory = ReturnType<typeof secretApprovalPo
|
|||||||
export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
||||||
const secretApprovalPolicyOrm = ormify(db, TableName.SecretApprovalPolicy);
|
const secretApprovalPolicyOrm = ormify(db, TableName.SecretApprovalPolicy);
|
||||||
|
|
||||||
const secretApprovalPolicyFindQuery = (tx: Knex, filter: TFindFilter<TSecretApprovalPolicies>) =>
|
const secretApprovalPolicyFindQuery = (
|
||||||
|
tx: Knex,
|
||||||
|
filter: TFindFilter<TSecretApprovalPolicies>,
|
||||||
|
customFilter?: {
|
||||||
|
sapId?: string;
|
||||||
|
}
|
||||||
|
) =>
|
||||||
tx(TableName.SecretApprovalPolicy)
|
tx(TableName.SecretApprovalPolicy)
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
.where(buildFindFilter(filter))
|
.where(buildFindFilter(filter))
|
||||||
|
.where((qb) => {
|
||||||
|
if (customFilter?.sapId) {
|
||||||
|
void qb.where(`${TableName.SecretApprovalPolicy}.id`, "=", customFilter.sapId);
|
||||||
|
}
|
||||||
|
})
|
||||||
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
.join(TableName.Environment, `${TableName.SecretApprovalPolicy}.envId`, `${TableName.Environment}.id`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretApprovalPolicyApprover,
|
TableName.SecretApprovalPolicyApprover,
|
||||||
@@ -37,6 +48,7 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
tx.ref("id").withSchema("secretApprovalPolicyApproverUser").as("approverUserId"),
|
tx.ref("id").withSchema("secretApprovalPolicyApproverUser").as("approverUserId"),
|
||||||
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
|
tx.ref("email").withSchema("secretApprovalPolicyApproverUser").as("approverEmail"),
|
||||||
tx.ref("firstName").withSchema("secretApprovalPolicyApproverUser").as("approverFirstName"),
|
tx.ref("firstName").withSchema("secretApprovalPolicyApproverUser").as("approverFirstName"),
|
||||||
|
tx.ref("username").withSchema("secretApprovalPolicyApproverUser").as("approverUsername"),
|
||||||
tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName")
|
tx.ref("lastName").withSchema("secretApprovalPolicyApproverUser").as("approverLastName")
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
@@ -108,9 +120,15 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const find = async (filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>, tx?: Knex) => {
|
const find = async (
|
||||||
|
filter: TFindFilter<TSecretApprovalPolicies & { projectId: string }>,
|
||||||
|
customFilter?: {
|
||||||
|
sapId?: string;
|
||||||
|
},
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const docs = await secretApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
|
const docs = await secretApprovalPolicyFindQuery(tx || db.replicaNode(), filter, customFilter);
|
||||||
const formatedDoc = sqlNestRelationships({
|
const formatedDoc = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
key: "id",
|
key: "id",
|
||||||
@@ -123,8 +141,9 @@ export const secretApprovalPolicyDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "approverUserId",
|
key: "approverUserId",
|
||||||
label: "approvers" as const,
|
label: "approvers" as const,
|
||||||
mapper: ({ approverUserId: id }) => ({
|
mapper: ({ approverUserId: id, approverUsername }) => ({
|
||||||
type: ApproverType.User,
|
type: ApproverType.User,
|
||||||
|
name: approverUsername,
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@@ -3,10 +3,11 @@ import picomatch from "picomatch";
|
|||||||
|
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { containsGlobPatterns } from "@app/lib/picomatch";
|
import { containsGlobPatterns } from "@app/lib/picomatch";
|
||||||
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "@app/services/project-env/project-env-dal";
|
||||||
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import { ApproverType } from "../access-approval-policy/access-approval-policy-types";
|
import { ApproverType } from "../access-approval-policy/access-approval-policy-types";
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
@@ -16,6 +17,7 @@ import {
|
|||||||
TCreateSapDTO,
|
TCreateSapDTO,
|
||||||
TDeleteSapDTO,
|
TDeleteSapDTO,
|
||||||
TGetBoardSapDTO,
|
TGetBoardSapDTO,
|
||||||
|
TGetSapByIdDTO,
|
||||||
TListSapDTO,
|
TListSapDTO,
|
||||||
TUpdateSapDTO
|
TUpdateSapDTO
|
||||||
} from "./secret-approval-policy-types";
|
} from "./secret-approval-policy-types";
|
||||||
@@ -29,6 +31,7 @@ type TSecretApprovalPolicyServiceFactoryDep = {
|
|||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
secretApprovalPolicyDAL: TSecretApprovalPolicyDALFactory;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
|
userDAL: Pick<TUserDALFactory, "find">;
|
||||||
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
secretApprovalPolicyApproverDAL: TSecretApprovalPolicyApproverDALFactory;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
@@ -40,6 +43,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyApproverDAL,
|
secretApprovalPolicyApproverDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
|
userDAL,
|
||||||
licenseService
|
licenseService
|
||||||
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
}: TSecretApprovalPolicyServiceFactoryDep) => {
|
||||||
const createSecretApprovalPolicy = async ({
|
const createSecretApprovalPolicy = async ({
|
||||||
@@ -60,9 +64,14 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id);
|
||||||
const userApprovers = approvers
|
const userApprovers = approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.User)
|
?.filter((approver) => approver.type === ApproverType.User)
|
||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
if (!groupApprovers && approvals > approvers.length)
|
const userApproverNames = approvers
|
||||||
|
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
if (!groupApprovers.length && approvals > approvers.length)
|
||||||
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -86,7 +95,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
const env = await projectEnvDAL.findOne({ slug: environment, projectId });
|
||||||
if (!env) throw new BadRequestError({ message: "Environment not found" });
|
if (!env) throw new NotFoundError({ message: "Environment not found" });
|
||||||
|
|
||||||
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
const secretApproval = await secretApprovalPolicyDAL.transaction(async (tx) => {
|
||||||
const doc = await secretApprovalPolicyDAL.create(
|
const doc = await secretApprovalPolicyDAL.create(
|
||||||
@@ -100,8 +109,31 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let userApproverIds = userApprovers;
|
||||||
|
if (userApproverNames.length) {
|
||||||
|
const approverUsers = await userDAL.find(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
username: userApproverNames
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
const approverNamesFromDb = approverUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userApproverNames?.filter((username) => !approverNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames?.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid approver user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
||||||
|
}
|
||||||
|
|
||||||
await secretApprovalPolicyApproverDAL.insertMany(
|
await secretApprovalPolicyApproverDAL.insertMany(
|
||||||
userApprovers.map((approverUserId) => ({
|
userApproverIds.map((approverUserId) => ({
|
||||||
approverUserId,
|
approverUserId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
@@ -117,6 +149,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
);
|
);
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ...secretApproval, environment: env, projectId };
|
return { ...secretApproval, environment: env, projectId };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -137,10 +170,15 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id);
|
||||||
const userApprovers = approvers
|
const userApprovers = approvers
|
||||||
?.filter((approver) => approver.type === ApproverType.User)
|
?.filter((approver) => approver.type === ApproverType.User)
|
||||||
.map((approver) => approver.id);
|
.map((approver) => approver.id)
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
|
const userApproverNames = approvers
|
||||||
|
.map((approver) => (approver.type === ApproverType.User ? approver.name : undefined))
|
||||||
|
.filter(Boolean) as string[];
|
||||||
|
|
||||||
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
const secretApprovalPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||||
if (!secretApprovalPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
if (!secretApprovalPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -174,8 +212,31 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
await secretApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
|
||||||
|
|
||||||
if (approvers) {
|
if (approvers) {
|
||||||
|
let userApproverIds = userApprovers;
|
||||||
|
if (userApproverNames) {
|
||||||
|
const approverUsers = await userDAL.find(
|
||||||
|
{
|
||||||
|
$in: {
|
||||||
|
username: userApproverNames
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
const approverNamesFromDb = approverUsers.map((user) => user.username);
|
||||||
|
const invalidUsernames = userApproverNames?.filter((username) => !approverNamesFromDb.includes(username));
|
||||||
|
|
||||||
|
if (invalidUsernames?.length) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Invalid approver user: ${invalidUsernames.join(", ")}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
userApproverIds = userApproverIds.concat(approverUsers.map((user) => user.id));
|
||||||
|
}
|
||||||
|
|
||||||
await secretApprovalPolicyApproverDAL.insertMany(
|
await secretApprovalPolicyApproverDAL.insertMany(
|
||||||
userApprovers.map((approverUserId) => ({
|
userApproverIds.map((approverUserId) => ({
|
||||||
approverUserId,
|
approverUserId,
|
||||||
policyId: doc.id
|
policyId: doc.id
|
||||||
})),
|
})),
|
||||||
@@ -192,6 +253,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@@ -209,7 +271,7 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TDeleteSapDTO) => {
|
}: TDeleteSapDTO) => {
|
||||||
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
const sapPolicy = await secretApprovalPolicyDAL.findById(secretPolicyId);
|
||||||
if (!sapPolicy) throw new BadRequestError({ message: "Secret approval policy not found" });
|
if (!sapPolicy) throw new NotFoundError({ message: "Secret approval policy not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -258,7 +320,7 @@ 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 BadRequestError({ message: "Environment not found" });
|
if (!env) throw new NotFoundError({ message: "Environment not found" });
|
||||||
|
|
||||||
const policies = await secretApprovalPolicyDAL.find({ envId: env.id });
|
const policies = await secretApprovalPolicyDAL.find({ envId: env.id });
|
||||||
if (!policies.length) return;
|
if (!policies.length) return;
|
||||||
@@ -296,12 +358,41 @@ export const secretApprovalPolicyServiceFactory = ({
|
|||||||
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
return getSecretApprovalPolicy(projectId, environment, secretPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSecretApprovalPolicyById = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
sapId
|
||||||
|
}: TGetSapByIdDTO) => {
|
||||||
|
const [sapPolicy] = await secretApprovalPolicyDAL.find({}, { sapId });
|
||||||
|
|
||||||
|
if (!sapPolicy) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Cannot find secret approval policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
sapPolicy.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
||||||
|
|
||||||
|
return sapPolicy;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createSecretApprovalPolicy,
|
createSecretApprovalPolicy,
|
||||||
updateSecretApprovalPolicy,
|
updateSecretApprovalPolicy,
|
||||||
deleteSecretApprovalPolicy,
|
deleteSecretApprovalPolicy,
|
||||||
getSecretApprovalPolicy,
|
getSecretApprovalPolicy,
|
||||||
getSecretApprovalPolicyByProjectId,
|
getSecretApprovalPolicyByProjectId,
|
||||||
getSecretApprovalPolicyOfFolder
|
getSecretApprovalPolicyOfFolder,
|
||||||
|
getSecretApprovalPolicyById
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -6,7 +6,7 @@ export type TCreateSapDTO = {
|
|||||||
approvals: number;
|
approvals: number;
|
||||||
secretPath?: string | null;
|
secretPath?: string | null;
|
||||||
environment: string;
|
environment: string;
|
||||||
approvers: { type: ApproverType; id: string }[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
name: string;
|
name: string;
|
||||||
enforcementLevel: EnforcementLevel;
|
enforcementLevel: EnforcementLevel;
|
||||||
@@ -16,7 +16,7 @@ export type TUpdateSapDTO = {
|
|||||||
secretPolicyId: string;
|
secretPolicyId: string;
|
||||||
approvals?: number;
|
approvals?: number;
|
||||||
secretPath?: string | null;
|
secretPath?: string | null;
|
||||||
approvers: { type: ApproverType; id: string }[];
|
approvers: ({ type: ApproverType.Group; id: string } | { type: ApproverType.User; id?: string; name?: string })[];
|
||||||
name?: string;
|
name?: string;
|
||||||
enforcementLevel?: EnforcementLevel;
|
enforcementLevel?: EnforcementLevel;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
@@ -27,6 +27,8 @@ export type TDeleteSapDTO = {
|
|||||||
|
|
||||||
export type TListSapDTO = TProjectPermission;
|
export type TListSapDTO = TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetSapByIdDTO = Omit<TProjectPermission, "projectId"> & { sapId: string };
|
||||||
|
|
||||||
export type TGetBoardSapDTO = {
|
export type TGetBoardSapDTO = {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
environment: string;
|
environment: string;
|
||||||
|
@@ -8,7 +8,7 @@ import {
|
|||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
TSecretTags
|
TSecretTags
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { BadRequestError, DatabaseError } from "@app/lib/errors";
|
import { DatabaseError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
|
||||||
export type TSecretApprovalRequestSecretDALFactory = ReturnType<typeof secretApprovalRequestSecretDALFactory>;
|
export type TSecretApprovalRequestSecretDALFactory = ReturnType<typeof secretApprovalRequestSecretDALFactory>;
|
||||||
@@ -31,7 +31,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (existingApprovalSecrets.length !== data.length) {
|
if (existingApprovalSecrets.length !== data.length) {
|
||||||
throw new BadRequestError({ message: "Some of the secret approvals do not exist" });
|
throw new NotFoundError({ message: "Some of the secret approvals do not exist" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.length === 0) return [];
|
if (data.length === 0) return [];
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy, pick, unique } from "@app/lib/fn";
|
import { groupBy, pick, unique } from "@app/lib/fn";
|
||||||
import { setKnexStringValue } from "@app/lib/knex";
|
import { setKnexStringValue } from "@app/lib/knex";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
@@ -204,7 +204,7 @@ 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 BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
||||||
|
|
||||||
const { projectId } = secretApprovalRequest;
|
const { projectId } = secretApprovalRequest;
|
||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
@@ -222,7 +222,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find(({ userId }) => userId === actorId)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
||||||
}
|
}
|
||||||
|
|
||||||
let secrets;
|
let secrets;
|
||||||
@@ -271,7 +271,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
if (!botKey) throw new BadRequestError({ message: "Bot key not found" });
|
if (!botKey) throw new NotFoundError({ message: "Project bot key not found" });
|
||||||
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 +307,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TReviewRequestDTO) => {
|
}: TReviewRequestDTO) => {
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -331,7 +331,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find(({ userId }) => userId === actorId)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
||||||
}
|
}
|
||||||
const reviewStatus = await secretApprovalRequestReviewerDAL.transaction(async (tx) => {
|
const reviewStatus = await secretApprovalRequestReviewerDAL.transaction(async (tx) => {
|
||||||
const review = await secretApprovalRequestReviewerDAL.findOne(
|
const review = await secretApprovalRequestReviewerDAL.findOne(
|
||||||
@@ -365,7 +365,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
actorAuthMethod
|
actorAuthMethod
|
||||||
}: TStatusChangeDTO) => {
|
}: TStatusChangeDTO) => {
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -389,7 +389,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find(({ userId }) => userId === actorId)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (secretApprovalRequest.hasMerged) throw new BadRequestError({ message: "Approval request has been merged" });
|
if (secretApprovalRequest.hasMerged) throw new BadRequestError({ message: "Approval request has been merged" });
|
||||||
@@ -414,7 +414,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
bypassReason
|
bypassReason
|
||||||
}: TMergeSecretApprovalRequestDTO) => {
|
}: TMergeSecretApprovalRequestDTO) => {
|
||||||
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
const secretApprovalRequest = await secretApprovalRequestDAL.findById(approvalId);
|
||||||
if (!secretApprovalRequest) throw new BadRequestError({ message: "Secret approval request not found" });
|
if (!secretApprovalRequest) throw new NotFoundError({ message: "Secret approval request not found" });
|
||||||
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
if (actor !== ActorType.USER) throw new BadRequestError({ message: "Must be a user" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(actorOrgId);
|
const plan = await licenseService.getPlan(actorOrgId);
|
||||||
@@ -439,7 +439,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretApprovalRequest.committerUserId !== actorId &&
|
secretApprovalRequest.committerUserId !== actorId &&
|
||||||
!policy.approvers.find(({ userId }) => userId === actorId)
|
!policy.approvers.find(({ userId }) => userId === actorId)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError({ message: "User has no access" });
|
throw new ForbiddenRequestError({ message: "User has insufficient privileges" });
|
||||||
}
|
}
|
||||||
const reviewers = secretApprovalRequest.reviewers.reduce<Record<string, ApprovalStatus>>(
|
const reviewers = secretApprovalRequest.reviewers.reduce<Record<string, ApprovalStatus>>(
|
||||||
(prev, curr) => ({ ...prev, [curr.userId.toString()]: curr.status as ApprovalStatus }),
|
(prev, curr) => ({ ...prev, [curr.userId.toString()]: curr.status as ApprovalStatus }),
|
||||||
@@ -462,7 +462,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestIdBridgeSecretV2(
|
||||||
secretApprovalRequest.id
|
secretApprovalRequest.id
|
||||||
);
|
);
|
||||||
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
if (!secretApprovalSecrets) throw new NotFoundError({ message: "No secrets found" });
|
||||||
|
|
||||||
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
const { decryptor: secretManagerDecryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.SecretManager,
|
type: KmsDataKey.SecretManager,
|
||||||
@@ -602,7 +602,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id);
|
||||||
if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" });
|
if (!secretApprovalSecrets) throw new NotFoundError({ message: "No secrets found" });
|
||||||
|
|
||||||
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);
|
||||||
@@ -612,8 +612,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
|
inputSecrets: secretCreationCommits.map(({ secretBlindIndex }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Missing secret blind index"
|
message: "Secret blind index not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { secretBlindIndex };
|
return { secretBlindIndex };
|
||||||
@@ -639,8 +639,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
.filter(({ secretBlindIndex, secret }) => secret && secret.secretBlindIndex !== secretBlindIndex)
|
||||||
.map(({ secretBlindIndex }) => {
|
.map(({ secretBlindIndex }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Missing secret blind index"
|
message: "Secret blind index not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { secretBlindIndex };
|
return { secretBlindIndex };
|
||||||
@@ -762,8 +762,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretQueueService,
|
secretQueueService,
|
||||||
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
|
inputSecrets: secretDeletionCommits.map(({ secretBlindIndex }) => {
|
||||||
if (!secretBlindIndex) {
|
if (!secretBlindIndex) {
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Missing secret blind index"
|
message: "Secret blind index not found"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return { secretBlindIndex, type: SecretType.Shared };
|
return { secretBlindIndex, type: SecretType.Shared };
|
||||||
@@ -789,7 +789,7 @@ 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 BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
await secretQueueService.syncSecrets({
|
await secretQueueService.syncSecrets({
|
||||||
projectId,
|
projectId,
|
||||||
secretPath: folder.path,
|
secretPath: folder.path,
|
||||||
@@ -860,14 +860,14 @@ 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 BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Folder not found for the given environment slug & secret path",
|
message: "Folder not found for the given environment slug & secret path",
|
||||||
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 BadRequestError({ message: "Blind index not found", name: "Update secret" });
|
if (!blindIndexCfg) throw new NotFoundError({ message: "Blind index not found", name: "Update secret" });
|
||||||
|
|
||||||
const commits: Omit<TSecretApprovalRequestsSecretsInsert, "requestId">[] = [];
|
const commits: Omit<TSecretApprovalRequestsSecretsInsert, "requestId">[] = [];
|
||||||
const commitTagIds: Record<string, string[]> = {};
|
const commitTagIds: Record<string, string[]> = {};
|
||||||
@@ -961,7 +961,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretDAL
|
secretDAL
|
||||||
});
|
});
|
||||||
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
|
const secretsGroupedByBlindIndex = groupBy(secrets, (i) => {
|
||||||
if (!i.secretBlindIndex) throw new BadRequestError({ message: "Missing secret blind index" });
|
if (!i.secretBlindIndex) throw new NotFoundError({ message: "Secret blind index not found" });
|
||||||
return i.secretBlindIndex;
|
return i.secretBlindIndex;
|
||||||
});
|
});
|
||||||
const deletedSecretIds = deletedSecrets.map(
|
const deletedSecretIds = deletedSecrets.map(
|
||||||
@@ -972,7 +972,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 BadRequestError({ message: "Failed to find secret blind index" });
|
throw new NotFoundError({ message: "Secret blind index not found" });
|
||||||
return {
|
return {
|
||||||
op: SecretOperations.Delete as const,
|
op: SecretOperations.Delete as const,
|
||||||
...latestSecretVersions[secretId],
|
...latestSecretVersions[secretId],
|
||||||
@@ -988,7 +988,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 BadRequestError({ message: "Tag not found" });
|
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "Tag 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 +1054,7 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
|
|
||||||
const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => {
|
const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => {
|
||||||
if (!i.secretBlindIndex) {
|
if (!i.secretBlindIndex) {
|
||||||
throw new BadRequestError({ message: "Missing secret blind index" });
|
throw new NotFoundError({ message: "Secret blind index not found" });
|
||||||
}
|
}
|
||||||
return i.secretBlindIndex;
|
return i.secretBlindIndex;
|
||||||
});
|
});
|
||||||
@@ -1132,7 +1132,7 @@ 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 BadRequestError({
|
throw new NotFoundError({
|
||||||
message: "Folder not found for the given environment slug & secret path",
|
message: "Folder not found for the given environment slug & secret path",
|
||||||
name: "GenSecretApproval"
|
name: "GenSecretApproval"
|
||||||
});
|
});
|
||||||
@@ -1191,8 +1191,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
if (secretsToUpdateStoredInDB.length !== secretsToUpdate.length)
|
if (secretsToUpdateStoredInDB.length !== secretsToUpdate.length)
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: `Secret not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
message: `Secret does not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
||||||
});
|
});
|
||||||
|
|
||||||
// now find any secret that needs to update its name
|
// now find any secret that needs to update its name
|
||||||
@@ -1207,8 +1207,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
if (secrets.length)
|
if (secrets.length)
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: `Secret not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
message: `Secret does not exist: ${secretsToUpdateStoredInDB.map((el) => el.key).join(",")}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1267,8 +1267,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
if (secretsToDeleteInDB.length !== deletedSecrets.length)
|
if (secretsToDeleteInDB.length !== deletedSecrets.length)
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
message: `Secret not exist: ${secretsToDeleteInDB.map((el) => el.key).join(",")}`
|
message: `Secret does not exist: ${secretsToDeleteInDB.map((el) => el.key).join(",")}`
|
||||||
});
|
});
|
||||||
const secretsGroupedByKey = groupBy(secretsToDeleteInDB, (i) => i.key);
|
const secretsGroupedByKey = groupBy(secretsToDeleteInDB, (i) => i.key);
|
||||||
const deletedSecretIds = deletedSecrets.map((el) => secretsGroupedByKey[el.secretKey][0].id);
|
const deletedSecretIds = deletedSecrets.map((el) => secretsGroupedByKey[el.secretKey][0].id);
|
||||||
@@ -1291,7 +1291,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 BadRequestError({ message: "Tag not found" });
|
if (tagIds.length !== tags.length) throw new NotFoundError({ message: "Tag 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(
|
||||||
|
@@ -4,7 +4,7 @@ import { TSecretApprovalRequestDALFactory } from "@app/ee/services/secret-approv
|
|||||||
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
import { TSecretApprovalRequestSecretDALFactory } from "@app/ee/services/secret-approval-request/secret-approval-request-secret-dal";
|
||||||
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
import { KeyStorePrefixes, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy, unique } from "@app/lib/fn";
|
import { groupBy, unique } 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";
|
||||||
@@ -295,7 +295,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
||||||
destinationSecretImport.folderId
|
destinationSecretImport.folderId
|
||||||
]);
|
]);
|
||||||
if (!destinationFolder) throw new BadRequestError({ message: "Imported folder not found" });
|
if (!destinationFolder) throw new NotFoundError({ message: "Imported folder not found" });
|
||||||
|
|
||||||
let destinationReplicationFolder = await folderDAL.findOne({
|
let destinationReplicationFolder = await folderDAL.findOne({
|
||||||
parentId: destinationFolder.id,
|
parentId: destinationFolder.id,
|
||||||
@@ -506,7 +506,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey) throw new BadRequestError({ message: "Bot not found" });
|
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
||||||
// 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 +545,7 @@ export const secretReplicationServiceFactory = ({
|
|||||||
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
const [destinationFolder] = await folderDAL.findSecretPathByFolderIds(projectId, [
|
||||||
destinationSecretImport.folderId
|
destinationSecretImport.folderId
|
||||||
]);
|
]);
|
||||||
if (!destinationFolder) throw new BadRequestError({ message: "Imported folder not found" });
|
if (!destinationFolder) throw new NotFoundError({ message: "Imported folder not found" });
|
||||||
|
|
||||||
let destinationReplicationFolder = await folderDAL.findOne({
|
let destinationReplicationFolder = await folderDAL.findOne({
|
||||||
parentId: destinationFolder.id,
|
parentId: destinationFolder.id,
|
||||||
|
@@ -13,7 +13,7 @@ import {
|
|||||||
infisicalSymmetricEncypt
|
infisicalSymmetricEncypt
|
||||||
} from "@app/lib/crypto/encryption";
|
} from "@app/lib/crypto/encryption";
|
||||||
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||||
@@ -332,7 +332,7 @@ export const secretRotationQueueFactory = ({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!botKey) throw new BadRequestError({ message: "Bot not found" });
|
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
||||||
const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({
|
const encryptedSecrets = rotationOutputs.map(({ key: outputKey, secretId }) => ({
|
||||||
secretId,
|
secretId,
|
||||||
value: encryptSymmetric128BitHexKeyUTF8(
|
value: encryptSymmetric128BitHexKeyUTF8(
|
||||||
@@ -372,7 +372,7 @@ 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 BadRequestError({ message: "Missing blind index" });
|
if (!el.secretBlindIndex) throw new NotFoundError({ message: "Secret blind index not found" });
|
||||||
return {
|
return {
|
||||||
...el,
|
...el,
|
||||||
secretId: id,
|
secretId: id,
|
||||||
|
@@ -3,7 +3,7 @@ import Ajv from "ajv";
|
|||||||
|
|
||||||
import { ProjectVersion } from "@app/db/schemas";
|
import { ProjectVersion } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { decryptSymmetric128BitHexKeyUTF8, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "@app/services/project-bot/project-bot-service";
|
||||||
@@ -94,7 +94,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath);
|
||||||
if (!folder) throw new BadRequestError({ message: "Secret path not found" });
|
if (!folder) throw new NotFoundError({ message: "Secret path not found" });
|
||||||
ForbiddenError.from(permission).throwUnlessCan(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ProjectPermissionActions.Edit,
|
ProjectPermissionActions.Edit,
|
||||||
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
@@ -108,14 +108,14 @@ export const secretRotationServiceFactory = ({
|
|||||||
$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 BadRequestError({ message: "Secrets not found" });
|
throw new NotFoundError({ message: "Secrets not found" });
|
||||||
} 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 BadRequestError({ message: "Secrets not found" });
|
throw new NotFoundError({ message: "Secrets not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(project.orgId);
|
const plan = await licenseService.getPlan(project.orgId);
|
||||||
@@ -125,7 +125,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
|
const selectedTemplate = rotationTemplates.find(({ name }) => name === provider);
|
||||||
if (!selectedTemplate) throw new BadRequestError({ message: "Provider not found" });
|
if (!selectedTemplate) throw new NotFoundError({ message: "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 +198,7 @@ export const secretRotationServiceFactory = ({
|
|||||||
return docs;
|
return docs;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey) throw new BadRequestError({ message: "bot not found" });
|
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
||||||
const docs = await secretRotationDAL.find({ projectId });
|
const docs = await secretRotationDAL.find({ projectId });
|
||||||
return docs.map((el) => ({
|
return docs.map((el) => ({
|
||||||
...el,
|
...el,
|
||||||
@@ -220,7 +220,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 BadRequestError({ message: "Rotation not found" });
|
if (!doc) throw new NotFoundError({ message: "Rotation 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 +244,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 BadRequestError({ message: "Rotation not found" });
|
if (!doc) throw new NotFoundError({ message: "Rotation not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
|
@@ -7,7 +7,7 @@ import { ProbotOctokit } from "probot";
|
|||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TGitAppDALFactory } from "./git-app-dal";
|
import { TGitAppDALFactory } from "./git-app-dal";
|
||||||
import { TGitAppInstallSessionDALFactory } from "./git-app-install-session-dal";
|
import { TGitAppInstallSessionDALFactory } from "./git-app-install-session-dal";
|
||||||
@@ -63,7 +63,7 @@ export const secretScanningServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TLinkInstallSessionDTO) => {
|
}: TLinkInstallSessionDTO) => {
|
||||||
const session = await gitAppInstallSessionDAL.findOne({ sessionId });
|
const session = await gitAppInstallSessionDAL.findOne({ sessionId });
|
||||||
if (!session) throw new UnauthorizedError({ message: "Session not found" });
|
if (!session) throw new NotFoundError({ message: "Session was not found" });
|
||||||
|
|
||||||
const { permission } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
|
@@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
|
|||||||
|
|
||||||
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
import { TableName, TSecretTagJunctionInsert, TSecretV2TagJunctionInsert } from "@app/db/schemas";
|
||||||
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
import { decryptSymmetric128BitHexKeyUTF8 } from "@app/lib/crypto";
|
||||||
import { BadRequestError, InternalServerError } from "@app/lib/errors";
|
import { InternalServerError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
@@ -99,7 +99,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
|
|
||||||
return snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
return snapshotDAL.countOfSnapshotsByFolderId(folder.id);
|
||||||
};
|
};
|
||||||
@@ -131,7 +131,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
const folder = await folderDAL.findBySecretPath(projectId, environment, path);
|
||||||
if (!folder) throw new BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
|
|
||||||
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 +139,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 BadRequestError({ message: "Snapshot not found" });
|
if (!snapshot) throw new NotFoundError({ message: "Snapshot not found" });
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -173,7 +173,7 @@ 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 BadRequestError({ message: "bot not found" });
|
if (!botKey) throw new NotFoundError({ message: "Project bot not found" });
|
||||||
snapshotDetails = {
|
snapshotDetails = {
|
||||||
...encryptedSnapshotDetails,
|
...encryptedSnapshotDetails,
|
||||||
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
secretVersions: encryptedSnapshotDetails.secretVersions.map((el) => ({
|
||||||
@@ -225,7 +225,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 BadRequestError({ message: "Folder not found" });
|
if (!folder) throw new NotFoundError({ message: "Folder not found" });
|
||||||
const shouldUseSecretV2Bridge = folder.projectVersion === 3;
|
const shouldUseSecretV2Bridge = folder.projectVersion === 3;
|
||||||
|
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@@ -309,7 +309,7 @@ export const secretSnapshotServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
}: TRollbackSnapshotDTO) => {
|
}: TRollbackSnapshotDTO) => {
|
||||||
const snapshot = await snapshotDAL.findById(snapshotId);
|
const snapshot = await snapshotDAL.findById(snapshotId);
|
||||||
if (!snapshot) throw new BadRequestError({ message: "Snapshot not found" });
|
if (!snapshot) throw new NotFoundError({ message: "Snapshot not found" });
|
||||||
const shouldUseBridge = snapshot.projectVersion === 3;
|
const shouldUseBridge = snapshot.projectVersion === 3;
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
@@ -16,6 +16,9 @@ export const KeyStorePrefixes = {
|
|||||||
WaitUntilReadyKmsOrgKeyCreation: "wait-until-ready-kms-org-key-creation-",
|
WaitUntilReadyKmsOrgKeyCreation: "wait-until-ready-kms-org-key-creation-",
|
||||||
WaitUntilReadyKmsOrgDataKeyCreation: "wait-until-ready-kms-org-data-key-creation-",
|
WaitUntilReadyKmsOrgDataKeyCreation: "wait-until-ready-kms-org-data-key-creation-",
|
||||||
|
|
||||||
|
WaitUntilReadyProjectEnvironmentOperation: (projectId: string) =>
|
||||||
|
`wait-until-ready-project-environments-operation-${projectId}`,
|
||||||
|
ProjectEnvironmentLock: (projectId: string) => `project-environment-lock-${projectId}` as const,
|
||||||
SyncSecretIntegrationLock: (projectId: string, environmentSlug: string, secretPath: string) =>
|
SyncSecretIntegrationLock: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
`sync-integration-mutex-${projectId}-${environmentSlug}-${secretPath}` as const,
|
||||||
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
SyncSecretIntegrationLastRunTimestamp: (projectId: string, environmentSlug: string, secretPath: string) =>
|
||||||
|
@@ -360,7 +360,11 @@ export const ORGANIZATIONS = {
|
|||||||
organizationId: "The ID of the organization to update the membership for.",
|
organizationId: "The ID of the organization to update the membership for.",
|
||||||
membershipId: "The ID of the membership to update.",
|
membershipId: "The ID of the membership to update.",
|
||||||
role: "The new role of the membership.",
|
role: "The new role of the membership.",
|
||||||
isActive: "The active status of the membership"
|
isActive: "The active status of the membership",
|
||||||
|
metadata: {
|
||||||
|
key: "The key for user metadata tag.",
|
||||||
|
value: "The value for user metadata tag."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
DELETE_USER_MEMBERSHIP: {
|
DELETE_USER_MEMBERSHIP: {
|
||||||
organizationId: "The ID of the organization to delete the membership from.",
|
organizationId: "The ID of the organization to delete the membership from.",
|
||||||
@@ -671,6 +675,9 @@ export const SECRET_IMPORTS = {
|
|||||||
environment: "The slug of the environment to list secret imports from.",
|
environment: "The slug of the environment to list secret imports from.",
|
||||||
path: "The path to list secret imports from."
|
path: "The path to list secret imports from."
|
||||||
},
|
},
|
||||||
|
GET: {
|
||||||
|
secretImportId: "The ID of the secret import to fetch."
|
||||||
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
environment: "The slug of the environment to import into.",
|
environment: "The slug of the environment to import into.",
|
||||||
path: "The path to import into.",
|
path: "The path to import into.",
|
||||||
@@ -1343,3 +1350,37 @@ export const PROJECT_ROLE = {
|
|||||||
projectSlug: "The slug of the project to list the roles of."
|
projectSlug: "The slug of the project to list the roles of."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const KMS = {
|
||||||
|
CREATE_KEY: {
|
||||||
|
projectId: "The ID of the project to create the key in.",
|
||||||
|
name: "The name of the key to be created. Must be slug-friendly.",
|
||||||
|
description: "An optional description of the key.",
|
||||||
|
encryptionAlgorithm: "The algorithm to use when performing cryptographic operations with the key."
|
||||||
|
},
|
||||||
|
UPDATE_KEY: {
|
||||||
|
keyId: "The ID of the key to be updated.",
|
||||||
|
name: "The updated name of this key. Must be slug-friendly.",
|
||||||
|
description: "The updated description of this key.",
|
||||||
|
isDisabled: "The flag to enable or disable this key."
|
||||||
|
},
|
||||||
|
DELETE_KEY: {
|
||||||
|
keyId: "The ID of the key to be deleted."
|
||||||
|
},
|
||||||
|
LIST_KEYS: {
|
||||||
|
projectId: "The ID of the project to list keys from.",
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th key.",
|
||||||
|
limit: "The number of keys to return.",
|
||||||
|
orderBy: "The column to order keys by.",
|
||||||
|
orderDirection: "The direction to order keys in.",
|
||||||
|
search: "The text string to filter key names by."
|
||||||
|
},
|
||||||
|
ENCRYPT: {
|
||||||
|
keyId: "The ID of the key to encrypt the data with.",
|
||||||
|
plaintext: "The plaintext to be encrypted (base64 encoded)."
|
||||||
|
},
|
||||||
|
DECRYPT: {
|
||||||
|
keyId: "The ID of the key to decrypt the data with.",
|
||||||
|
ciphertext: "The ciphertext to be decrypted (base64 encoded)."
|
||||||
|
}
|
||||||
|
};
|
||||||
|
28
backend/src/lib/base64/index.ts
Normal file
28
backend/src/lib/base64/index.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// Credit: https://github.com/miguelmota/is-base64
|
||||||
|
export const isBase64 = (
|
||||||
|
v: string,
|
||||||
|
opts = { allowEmpty: false, mimeRequired: false, allowMime: true, paddingRequired: false }
|
||||||
|
) => {
|
||||||
|
if (opts.allowEmpty === false && v === "") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let regex = "(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+/]{3}=)?";
|
||||||
|
const mimeRegex = "(data:\\w+\\/[a-zA-Z\\+\\-\\.]+;base64,)";
|
||||||
|
|
||||||
|
if (opts.mimeRequired === true) {
|
||||||
|
regex = mimeRegex + regex;
|
||||||
|
} else if (opts.allowMime === true) {
|
||||||
|
regex = `${mimeRegex}?${regex}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.paddingRequired === false) {
|
||||||
|
regex = "(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}(==)?|[A-Za-z0-9+\\/]{3}=?)?";
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegExp(`^${regex}$`, "gi").test(v);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBase64SizeInBytes = (base64String: string) => {
|
||||||
|
return Buffer.from(base64String, "base64").length;
|
||||||
|
};
|
@@ -23,8 +23,19 @@ export const conditionsMatcher = buildMongoQueryMatcher({ $glob }, { glob });
|
|||||||
/**
|
/**
|
||||||
* Extracts and formats permissions from a CASL Ability object or a raw permission set.
|
* Extracts and formats permissions from a CASL Ability object or a raw permission set.
|
||||||
*/
|
*/
|
||||||
const extractPermissions = (ability: MongoAbility) =>
|
const extractPermissions = (ability: MongoAbility) => {
|
||||||
ability.rules.map((permission) => `${permission.action as string}_${permission.subject as string}`);
|
const permissions: string[] = [];
|
||||||
|
ability.rules.forEach((permission) => {
|
||||||
|
if (typeof permission.action === "string") {
|
||||||
|
permissions.push(`${permission.action}_${permission.subject as string}`);
|
||||||
|
} else {
|
||||||
|
permission.action.forEach((permissionAction) => {
|
||||||
|
permissions.push(`${permissionAction}_${permission.subject as string}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return permissions;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two sets of permissions to determine if the first set is at least as privileged as the second set.
|
* Compares two sets of permissions to determine if the first set is at least as privileged as the second set.
|
||||||
|
111
backend/src/lib/casl/knex.ts
Normal file
111
backend/src/lib/casl/knex.ts
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
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
|
||||||
|
});
|
||||||
|
};
|
@@ -40,9 +40,9 @@ export class ForbiddenRequestError extends Error {
|
|||||||
|
|
||||||
error: unknown;
|
error: unknown;
|
||||||
|
|
||||||
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown }) {
|
constructor({ name, error, message }: { message?: string; name?: string; error?: unknown } = {}) {
|
||||||
super(message ?? "You are not allowed to access this resource");
|
super(message ?? "You are not allowed to access this resource");
|
||||||
this.name = name || "ForbideenError";
|
this.name = name || "ForbiddenError";
|
||||||
this.error = error;
|
this.error = error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -52,3 +52,21 @@ export const unique = <T, K extends string | number | symbol>(array: readonly T[
|
|||||||
);
|
);
|
||||||
return Object.values(valueMap);
|
return Object.values(valueMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array to a dictionary by mapping each item
|
||||||
|
* into a dictionary key & value
|
||||||
|
*/
|
||||||
|
export const objectify = <T, Key extends string | number | symbol, Value = T>(
|
||||||
|
array: readonly T[],
|
||||||
|
getKey: (item: T) => Key,
|
||||||
|
getValue: (item: T) => Value = (item) => item as unknown as Value
|
||||||
|
): Record<Key, Value> => {
|
||||||
|
return array.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[getKey(item)] = getValue(item);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<Key, Value>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import net from "node:net";
|
import net from "node:net";
|
||||||
|
|
||||||
import { UnauthorizedError } from "../errors";
|
import { ForbiddenRequestError } from "../errors";
|
||||||
|
|
||||||
export enum IPType {
|
export enum IPType {
|
||||||
IPV4 = "ipv4",
|
IPV4 = "ipv4",
|
||||||
@@ -126,7 +126,7 @@ export const checkIPAgainstBlocklist = ({ ipAddress, trustedIps }: { ipAddress:
|
|||||||
const check = blockList.check(ipAddress, type);
|
const check = blockList.check(ipAddress, type);
|
||||||
|
|
||||||
if (!check)
|
if (!check)
|
||||||
throw new UnauthorizedError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Failed to authenticate"
|
message: "You are not allowed to access this resource from the current IP address"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
89
backend/src/lib/knex/dynamic.ts
Normal file
89
backend/src/lib/knex/dynamic.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { UnauthorizedError } from "../errors";
|
||||||
|
|
||||||
|
type TKnexDynamicPrimitiveOperator = {
|
||||||
|
operator: "eq" | "ne" | "startsWith" | "endsWith";
|
||||||
|
value: string;
|
||||||
|
field: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TKnexDynamicInOperator = {
|
||||||
|
operator: "in";
|
||||||
|
value: string[] | number[];
|
||||||
|
field: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TKnexNonGroupOperator = TKnexDynamicInOperator | TKnexDynamicPrimitiveOperator;
|
||||||
|
|
||||||
|
type TKnexGroupOperator = {
|
||||||
|
operator: "and" | "or" | "not";
|
||||||
|
value: (TKnexNonGroupOperator | TKnexGroupOperator)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// akhilmhdh: This is still in pending state and not yet ready. If you want to use it ping me.
|
||||||
|
// 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) => {
|
||||||
|
const stack = [{ filterAst: dynamicQuery, queryBuilder: rootQueryBuild }];
|
||||||
|
|
||||||
|
while (stack.length) {
|
||||||
|
const { filterAst, queryBuilder } = stack.pop()!;
|
||||||
|
switch (filterAst.operator) {
|
||||||
|
case "eq": {
|
||||||
|
void queryBuilder.where(filterAst.field, "=", filterAst.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "ne": {
|
||||||
|
void queryBuilder.whereNot(filterAst.field, filterAst.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "startsWith": {
|
||||||
|
void queryBuilder.whereILike(filterAst.field, `${filterAst.value}%`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "endsWith": {
|
||||||
|
void queryBuilder.whereILike(filterAst.field, `%${filterAst.value}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "and": {
|
||||||
|
void queryBuilder.andWhere((subQueryBuilder) => {
|
||||||
|
filterAst.value.forEach((el) => {
|
||||||
|
stack.push({
|
||||||
|
queryBuilder: subQueryBuilder,
|
||||||
|
filterAst: el
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "or": {
|
||||||
|
void queryBuilder.orWhere((subQueryBuilder) => {
|
||||||
|
filterAst.value.forEach((el) => {
|
||||||
|
stack.push({
|
||||||
|
queryBuilder: subQueryBuilder,
|
||||||
|
filterAst: el
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "not": {
|
||||||
|
void queryBuilder.whereNot((subQueryBuilder) => {
|
||||||
|
filterAst.value.forEach((el) => {
|
||||||
|
stack.push({
|
||||||
|
queryBuilder: subQueryBuilder,
|
||||||
|
filterAst: el
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new UnauthorizedError({ message: `Invalid knex dynamic operator: ${filterAst.operator}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -20,6 +20,7 @@ import { TQueueServiceFactory } from "@app/queue";
|
|||||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
|
|
||||||
import { globalRateLimiterCfg } from "./config/rateLimiter";
|
import { globalRateLimiterCfg } from "./config/rateLimiter";
|
||||||
|
import { addErrorsToResponseSchemas } from "./plugins/add-errors-to-response-schemas";
|
||||||
import { fastifyErrHandler } from "./plugins/error-handler";
|
import { fastifyErrHandler } from "./plugins/error-handler";
|
||||||
import { registerExternalNextjs } from "./plugins/external-nextjs";
|
import { registerExternalNextjs } from "./plugins/external-nextjs";
|
||||||
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
|
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "./plugins/fastify-zod";
|
||||||
@@ -75,6 +76,8 @@ export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
|
|||||||
credentials: true,
|
credentials: true,
|
||||||
origin: appCfg.SITE_URL || true
|
origin: appCfg.SITE_URL || true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await server.register(addErrorsToResponseSchemas);
|
||||||
// pull ip based on various proxy headers
|
// pull ip based on various proxy headers
|
||||||
await server.register(fastifyIp);
|
await server.register(fastifyIp);
|
||||||
|
|
||||||
|
15
backend/src/server/plugins/add-errors-to-response-schemas.ts
Normal file
15
backend/src/server/plugins/add-errors-to-response-schemas.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import fp from "fastify-plugin";
|
||||||
|
|
||||||
|
import { DefaultResponseErrorsSchema } from "../routes/sanitizedSchemas";
|
||||||
|
|
||||||
|
export const addErrorsToResponseSchemas = fp(async (server) => {
|
||||||
|
server.addHook("onRoute", (routeOptions) => {
|
||||||
|
if (routeOptions.schema && routeOptions.schema.response) {
|
||||||
|
routeOptions.schema.response = {
|
||||||
|
...DefaultResponseErrorsSchema,
|
||||||
|
...routeOptions.schema.response
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@@ -70,7 +70,7 @@ export const injectAuditLogInfo = fp(async (server: FastifyZodProvider) => {
|
|||||||
metadata: {}
|
metadata: {}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new BadRequestError({ message: "Missing logic for other actor" });
|
throw new BadRequestError({ message: "Invalid actor type provided" });
|
||||||
}
|
}
|
||||||
req.auditLogInfo = payload;
|
req.auditLogInfo = payload;
|
||||||
});
|
});
|
||||||
|
@@ -5,7 +5,7 @@ import jwt, { JwtPayload } from "jsonwebtoken";
|
|||||||
import { TServiceTokens, TUsers } from "@app/db/schemas";
|
import { TServiceTokens, TUsers } from "@app/db/schemas";
|
||||||
import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
|
import { TScimTokenJwtPayload } from "@app/ee/services/scim/scim-types";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMethod, AuthMode, AuthModeJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "@app/services/identity-access-token/identity-access-token-types";
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new UnauthorizedError({ name: "Unknown token strategy" });
|
throw new BadRequestError({ message: "Invalid token strategy provided" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
||||||
|
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { ForbiddenRequestError } from "@app/lib/errors";
|
||||||
import { ActorType } from "@app/services/auth/auth-type";
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
export const verifySuperAdmin = <T extends FastifyRequest>(
|
export const verifySuperAdmin = <T extends FastifyRequest>(
|
||||||
@@ -9,9 +9,8 @@ export const verifySuperAdmin = <T extends FastifyRequest>(
|
|||||||
done: HookHandlerDoneFunction
|
done: HookHandlerDoneFunction
|
||||||
) => {
|
) => {
|
||||||
if (req.auth.actor !== ActorType.USER || !req.auth.user.superAdmin)
|
if (req.auth.actor !== ActorType.USER || !req.auth.user.superAdmin)
|
||||||
throw new UnauthorizedError({
|
throw new ForbiddenRequestError({
|
||||||
name: "Unauthorized access",
|
message: "Requires elevated super admin privileges"
|
||||||
message: "Requires superadmin access"
|
|
||||||
});
|
});
|
||||||
done();
|
done();
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
import { FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
|
||||||
|
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
interface TAuthOptions {
|
interface TAuthOptions {
|
||||||
@@ -11,11 +11,11 @@ export const verifyAuth =
|
|||||||
<T extends FastifyRequest>(authStrategies: AuthMode[], options: TAuthOptions = { requireOrg: true }) =>
|
<T extends FastifyRequest>(authStrategies: AuthMode[], options: TAuthOptions = { requireOrg: true }) =>
|
||||||
(req: T, _res: FastifyReply, done: HookHandlerDoneFunction) => {
|
(req: T, _res: FastifyReply, done: HookHandlerDoneFunction) => {
|
||||||
if (!Array.isArray(authStrategies)) throw new Error("Auth strategy must be array");
|
if (!Array.isArray(authStrategies)) throw new Error("Auth strategy must be array");
|
||||||
if (!req.auth) throw new UnauthorizedError({ name: "Unauthorized access", message: "Token missing" });
|
if (!req.auth) throw new UnauthorizedError({ message: "Token missing" });
|
||||||
|
|
||||||
const isAccessAllowed = authStrategies.some((strategy) => strategy === req.auth.authMode);
|
const isAccessAllowed = authStrategies.some((strategy) => strategy === req.auth.authMode);
|
||||||
if (!isAccessAllowed) {
|
if (!isAccessAllowed) {
|
||||||
throw new UnauthorizedError({ name: `${req.url} Unauthorized Access` });
|
throw new ForbiddenRequestError({ name: `Forbidden access to ${req.url}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
// New optional option. There are some routes which do not require an organization ID to be present on the request.
|
// New optional option. There are some routes which do not require an organization ID to be present on the request.
|
||||||
|
@@ -6,6 +6,7 @@ import { ZodError } from "zod";
|
|||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
DatabaseError,
|
DatabaseError,
|
||||||
|
ForbiddenRequestError,
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
ScimRequestError,
|
ScimRequestError,
|
||||||
@@ -18,25 +19,50 @@ enum JWTErrors {
|
|||||||
InvalidAlgorithm = "invalid algorithm"
|
InvalidAlgorithm = "invalid algorithm"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum HttpStatusCodes {
|
||||||
|
BadRequest = 400,
|
||||||
|
NotFound = 404,
|
||||||
|
Unauthorized = 401,
|
||||||
|
Forbidden = 403,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
|
InternalServerError = 500
|
||||||
|
}
|
||||||
|
|
||||||
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider) => {
|
||||||
server.setErrorHandler((error, req, res) => {
|
server.setErrorHandler((error, req, res) => {
|
||||||
req.log.error(error);
|
req.log.error(error);
|
||||||
if (error instanceof BadRequestError) {
|
if (error instanceof BadRequestError) {
|
||||||
void res.status(400).send({ statusCode: 400, message: error.message, error: error.name });
|
void res
|
||||||
|
.status(HttpStatusCodes.BadRequest)
|
||||||
|
.send({ statusCode: HttpStatusCodes.BadRequest, message: error.message, error: error.name });
|
||||||
} else if (error instanceof NotFoundError) {
|
} else if (error instanceof NotFoundError) {
|
||||||
void res.status(404).send({ statusCode: 404, message: error.message, error: error.name });
|
void res
|
||||||
|
.status(HttpStatusCodes.NotFound)
|
||||||
|
.send({ statusCode: HttpStatusCodes.NotFound, message: error.message, error: error.name });
|
||||||
} else if (error instanceof UnauthorizedError) {
|
} else if (error instanceof UnauthorizedError) {
|
||||||
void res.status(403).send({ statusCode: 403, message: error.message, error: error.name });
|
void res
|
||||||
|
.status(HttpStatusCodes.Unauthorized)
|
||||||
|
.send({ statusCode: HttpStatusCodes.Unauthorized, message: error.message, error: error.name });
|
||||||
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
|
} else if (error instanceof DatabaseError || error instanceof InternalServerError) {
|
||||||
void res.status(500).send({ statusCode: 500, message: "Something went wrong", error: error.name });
|
void res
|
||||||
|
.status(HttpStatusCodes.InternalServerError)
|
||||||
|
.send({ statusCode: HttpStatusCodes.InternalServerError, message: "Something went wrong", error: error.name });
|
||||||
} else if (error instanceof ZodError) {
|
} else if (error instanceof ZodError) {
|
||||||
void res.status(403).send({ statusCode: 403, error: "ValidationFailure", message: error.issues });
|
void res
|
||||||
|
.status(HttpStatusCodes.Unauthorized)
|
||||||
|
.send({ statusCode: HttpStatusCodes.Unauthorized, error: "ValidationFailure", message: error.issues });
|
||||||
} else if (error instanceof ForbiddenError) {
|
} else if (error instanceof ForbiddenError) {
|
||||||
void res.status(401).send({
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
statusCode: 401,
|
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}`
|
||||||
});
|
});
|
||||||
|
} else if (error instanceof ForbiddenRequestError) {
|
||||||
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
|
statusCode: HttpStatusCodes.Forbidden,
|
||||||
|
message: error.message,
|
||||||
|
error: error.name
|
||||||
|
});
|
||||||
} else if (error instanceof ScimRequestError) {
|
} else if (error instanceof ScimRequestError) {
|
||||||
void res.status(error.status).send({
|
void res.status(error.status).send({
|
||||||
schemas: error.schemas,
|
schemas: error.schemas,
|
||||||
@@ -59,8 +85,8 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
return error.message;
|
return error.message;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
void res.status(401).send({
|
void res.status(HttpStatusCodes.Forbidden).send({
|
||||||
statusCode: 401,
|
statusCode: HttpStatusCodes.Forbidden,
|
||||||
error: "TokenError",
|
error: "TokenError",
|
||||||
message
|
message
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
import { Redis } from "ioredis";
|
// import { Redis } from "ioredis";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
@@ -74,7 +74,6 @@ import { trustedIpDALFactory } from "@app/ee/services/trusted-ip/trusted-ip-dal"
|
|||||||
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
import { trustedIpServiceFactory } from "@app/ee/services/trusted-ip/trusted-ip-service";
|
||||||
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
import { TKeyStoreFactory } from "@app/keystore/keystore";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
|
||||||
import { TQueueServiceFactory } from "@app/queue";
|
import { TQueueServiceFactory } from "@app/queue";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue";
|
import { accessTokenQueueServiceFactory } from "@app/services/access-token-queue/access-token-queue";
|
||||||
@@ -97,10 +96,13 @@ import { certificateAuthorityServiceFactory } from "@app/services/certificate-au
|
|||||||
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
|
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
|
||||||
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
|
||||||
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
|
||||||
|
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
|
||||||
|
import { externalMigrationServiceFactory } from "@app/services/external-migration/external-migration-service";
|
||||||
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
|
||||||
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
|
||||||
import { identityDALFactory } from "@app/services/identity/identity-dal";
|
import { identityDALFactory } from "@app/services/identity/identity-dal";
|
||||||
|
import { identityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
||||||
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
import { identityOrgDALFactory } from "@app/services/identity/identity-org-dal";
|
||||||
import { identityServiceFactory } from "@app/services/identity/identity-service";
|
import { identityServiceFactory } from "@app/services/identity/identity-service";
|
||||||
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
import { identityAccessTokenDALFactory } from "@app/services/identity-access-token/identity-access-token-dal";
|
||||||
@@ -265,6 +267,7 @@ export const registerRoutes = async (
|
|||||||
const serviceTokenDAL = serviceTokenDALFactory(db);
|
const serviceTokenDAL = serviceTokenDALFactory(db);
|
||||||
|
|
||||||
const identityDAL = identityDALFactory(db);
|
const identityDAL = identityDALFactory(db);
|
||||||
|
const identityMetadataDAL = identityMetadataDALFactory(db);
|
||||||
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
|
const identityAccessTokenDAL = identityAccessTokenDALFactory(db);
|
||||||
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
const identityOrgMembershipDAL = identityOrgDALFactory(db);
|
||||||
const identityProjectDAL = identityProjectDALFactory(db);
|
const identityProjectDAL = identityProjectDALFactory(db);
|
||||||
@@ -380,11 +383,13 @@ export const registerRoutes = async (
|
|||||||
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
secretApprovalPolicyApproverDAL: sapApproverDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
userDAL
|
||||||
});
|
});
|
||||||
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
||||||
|
|
||||||
const samlService = samlConfigServiceFactory({
|
const samlService = samlConfigServiceFactory({
|
||||||
|
identityMetadataDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
@@ -488,6 +493,7 @@ export const registerRoutes = async (
|
|||||||
});
|
});
|
||||||
const orgService = orgServiceFactory({
|
const orgService = orgServiceFactory({
|
||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
|
identityMetadataDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
samlConfigDAL,
|
samlConfigDAL,
|
||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
@@ -506,7 +512,8 @@ export const registerRoutes = async (
|
|||||||
smtpService,
|
smtpService,
|
||||||
userDAL,
|
userDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
orgBotDAL
|
orgBotDAL,
|
||||||
|
oidcConfigDAL
|
||||||
});
|
});
|
||||||
const signupService = authSignupServiceFactory({
|
const signupService = authSignupServiceFactory({
|
||||||
tokenService,
|
tokenService,
|
||||||
@@ -742,6 +749,7 @@ export const registerRoutes = async (
|
|||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
|
keyStore,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
folderDAL
|
folderDAL
|
||||||
@@ -917,7 +925,8 @@ export const registerRoutes = async (
|
|||||||
const secretSharingService = secretSharingServiceFactory({
|
const secretSharingService = secretSharingServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
secretSharingDAL,
|
secretSharingDAL,
|
||||||
orgDAL
|
orgDAL,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
|
const accessApprovalPolicyService = accessApprovalPolicyServiceFactory({
|
||||||
@@ -927,7 +936,8 @@ export const registerRoutes = async (
|
|||||||
permissionService,
|
permissionService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
userDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
const accessApprovalRequestService = accessApprovalRequestServiceFactory({
|
||||||
@@ -1025,7 +1035,8 @@ export const registerRoutes = async (
|
|||||||
identityDAL,
|
identityDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
identityProjectDAL,
|
identityProjectDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
identityMetadataDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const identityAccessTokenService = identityAccessTokenServiceFactory({
|
const identityAccessTokenService = identityAccessTokenServiceFactory({
|
||||||
@@ -1184,6 +1195,20 @@ export const registerRoutes = async (
|
|||||||
workflowIntegrationDAL
|
workflowIntegrationDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cmekService = cmekServiceFactory({
|
||||||
|
kmsDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
|
const migrationService = externalMigrationServiceFactory({
|
||||||
|
projectService,
|
||||||
|
orgService,
|
||||||
|
projectEnvService,
|
||||||
|
permissionService,
|
||||||
|
secretService
|
||||||
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
//
|
//
|
||||||
// setup the communication with license key server
|
// setup the communication with license key server
|
||||||
@@ -1265,9 +1290,11 @@ export const registerRoutes = async (
|
|||||||
secretSharing: secretSharingService,
|
secretSharing: secretSharingService,
|
||||||
userEngagement: userEngagementService,
|
userEngagement: userEngagementService,
|
||||||
externalKms: externalKmsService,
|
externalKms: externalKmsService,
|
||||||
|
cmek: cmekService,
|
||||||
orgAdmin: orgAdminService,
|
orgAdmin: orgAdminService,
|
||||||
slack: slackService,
|
slack: slackService,
|
||||||
workflowIntegration: workflowIntegrationService
|
workflowIntegration: workflowIntegrationService,
|
||||||
|
migration: migrationService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
@@ -1306,33 +1333,33 @@ export const registerRoutes = async (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (request, reply) => {
|
handler: async () => {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
await db.raw("SELECT NOW()");
|
// await db.raw("SELECT NOW()");
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
logger.error("Health check: database connection failed", err);
|
// logger.error("Health check: database connection failed", err);
|
||||||
return reply.code(503).send({
|
// return reply.code(503).send({
|
||||||
date: new Date(),
|
// date: new Date(),
|
||||||
message: "Service unavailable"
|
// message: "Service unavailable"
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (cfg.isRedisConfigured) {
|
// if (cfg.isRedisConfigured) {
|
||||||
const redis = new Redis(cfg.REDIS_URL);
|
// const redis = new Redis(cfg.REDIS_URL);
|
||||||
try {
|
// try {
|
||||||
await redis.ping();
|
// await redis.ping();
|
||||||
redis.disconnect();
|
// redis.disconnect();
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
logger.error("Health check: redis connection failed", err);
|
// logger.error("Health check: redis connection failed", err);
|
||||||
return reply.code(503).send({
|
// return reply.code(503).send({
|
||||||
date: new Date(),
|
// date: new Date(),
|
||||||
message: "Service unavailable"
|
// message: "Service unavailable"
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
|
@@ -27,6 +27,34 @@ export const integrationAuthPubSchema = IntegrationAuthsSchema.pick({
|
|||||||
updatedAt: true
|
updatedAt: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DefaultResponseErrorsSchema = {
|
||||||
|
400: z.object({
|
||||||
|
statusCode: z.literal(400),
|
||||||
|
message: z.string(),
|
||||||
|
error: z.string()
|
||||||
|
}),
|
||||||
|
404: z.object({
|
||||||
|
statusCode: z.literal(404),
|
||||||
|
message: z.string(),
|
||||||
|
error: z.string()
|
||||||
|
}),
|
||||||
|
401: z.object({
|
||||||
|
statusCode: z.literal(401),
|
||||||
|
message: z.any(),
|
||||||
|
error: z.string()
|
||||||
|
}),
|
||||||
|
403: z.object({
|
||||||
|
statusCode: z.literal(403),
|
||||||
|
message: z.string(),
|
||||||
|
error: z.string()
|
||||||
|
}),
|
||||||
|
500: z.object({
|
||||||
|
statusCode: z.literal(500),
|
||||||
|
message: z.string(),
|
||||||
|
error: z.string()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
|
export const sapPubSchema = SecretApprovalPoliciesSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
environment: z.object({
|
environment: z.object({
|
||||||
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -227,8 +227,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
if (serverCfg.initialized)
|
if (serverCfg.initialized) throw new BadRequestError({ message: "Admin account has already been set up" });
|
||||||
throw new UnauthorizedError({ name: "Admin sign up", message: "Admin has been created" });
|
|
||||||
const { user, token, organization } = await server.services.superAdmin.adminSignUp({
|
const { user, token, organization } = await server.services.superAdmin.adminSignUp({
|
||||||
...req.body,
|
...req.body,
|
||||||
ip: req.realIp,
|
ip: req.realIp,
|
||||||
|
@@ -2,7 +2,7 @@ import jwt from "jsonwebtoken";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { authRateLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthMode, AuthModeRefreshJwtTokenPayload, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
@@ -71,23 +71,34 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
|||||||
const refreshToken = req.cookies.jid;
|
const refreshToken = req.cookies.jid;
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
if (!refreshToken)
|
if (!refreshToken)
|
||||||
throw new BadRequestError({
|
throw new NotFoundError({
|
||||||
name: "Auth token route",
|
name: "AuthTokenNotFound",
|
||||||
message: "Failed to find refresh token"
|
message: "Failed to find refresh token"
|
||||||
});
|
});
|
||||||
|
|
||||||
const decodedToken = jwt.verify(refreshToken, appCfg.AUTH_SECRET) as AuthModeRefreshJwtTokenPayload;
|
const decodedToken = jwt.verify(refreshToken, appCfg.AUTH_SECRET) as AuthModeRefreshJwtTokenPayload;
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN)
|
if (decodedToken.authTokenType !== AuthTokenType.REFRESH_TOKEN)
|
||||||
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
|
throw new UnauthorizedError({
|
||||||
|
message: "The token provided is not a refresh token",
|
||||||
|
name: "InvalidToken"
|
||||||
|
});
|
||||||
|
|
||||||
const tokenVersion = await server.services.authToken.getUserTokenSessionById(
|
const tokenVersion = await server.services.authToken.getUserTokenSessionById(
|
||||||
decodedToken.tokenVersionId,
|
decodedToken.tokenVersionId,
|
||||||
decodedToken.userId
|
decodedToken.userId
|
||||||
);
|
);
|
||||||
if (!tokenVersion) throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
|
if (!tokenVersion)
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "Valid token version not found",
|
||||||
|
name: "InvalidToken"
|
||||||
|
});
|
||||||
|
|
||||||
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion)
|
if (decodedToken.refreshVersion !== tokenVersion.refreshVersion) {
|
||||||
throw new UnauthorizedError({ message: "Invalid token", name: "Auth token route" });
|
throw new UnauthorizedError({
|
||||||
|
message: "Token version mismatch",
|
||||||
|
name: "InvalidToken"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{
|
{
|
||||||
|
331
backend/src/server/routes/v1/cmek-router.ts
Normal file
331
backend/src/server/routes/v1/cmek-router.ts
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { InternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { KMS } from "@app/lib/api-docs";
|
||||||
|
import { getBase64SizeInBytes, isBase64 } from "@app/lib/base64";
|
||||||
|
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { CmekOrderBy } from "@app/services/cmek/cmek-types";
|
||||||
|
|
||||||
|
const keyNameSchema = z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.max(32)
|
||||||
|
.toLowerCase()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Name must be slug friendly"
|
||||||
|
});
|
||||||
|
const keyDescriptionSchema = z.string().trim().max(500).optional();
|
||||||
|
|
||||||
|
const base64Schema = z.string().superRefine((val, ctx) => {
|
||||||
|
if (!isBase64(val)) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "plaintext must be base64 encoded"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getBase64SizeInBytes(val) > 4096) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
message: "data cannot exceed 4096 bytes"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||||
|
// create encryption key
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/keys",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Create KMS key",
|
||||||
|
body: z.object({
|
||||||
|
projectId: z.string().describe(KMS.CREATE_KEY.projectId),
|
||||||
|
name: keyNameSchema.describe(KMS.CREATE_KEY.name),
|
||||||
|
description: keyDescriptionSchema.describe(KMS.CREATE_KEY.description),
|
||||||
|
encryptionAlgorithm: z
|
||||||
|
.nativeEnum(SymmetricEncryption)
|
||||||
|
.optional()
|
||||||
|
.default(SymmetricEncryption.AES_GCM_256)
|
||||||
|
.describe(KMS.CREATE_KEY.encryptionAlgorithm) // eventually will support others
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
key: KmsKeysSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
body: { projectId, name, description, encryptionAlgorithm },
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const cmek = await server.services.cmek.createCmek(
|
||||||
|
{ orgId: permission.orgId, projectId, name, description, encryptionAlgorithm },
|
||||||
|
permission
|
||||||
|
);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CREATE_CMEK,
|
||||||
|
metadata: {
|
||||||
|
keyId: cmek.id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
encryptionAlgorithm
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { key: cmek };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// update KMS key
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/keys/:keyId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Update KMS key",
|
||||||
|
params: z.object({
|
||||||
|
keyId: z.string().uuid().describe(KMS.UPDATE_KEY.keyId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: keyNameSchema.optional().describe(KMS.UPDATE_KEY.name),
|
||||||
|
isDisabled: z.boolean().optional().describe(KMS.UPDATE_KEY.isDisabled),
|
||||||
|
description: keyDescriptionSchema.describe(KMS.UPDATE_KEY.description)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
key: KmsKeysSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
params: { keyId },
|
||||||
|
body,
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const cmek = await server.services.cmek.updateCmekById({ keyId, ...body }, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_CMEK,
|
||||||
|
metadata: {
|
||||||
|
keyId,
|
||||||
|
...body
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { key: cmek };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete KMS key
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/keys/:keyId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Delete KMS key",
|
||||||
|
params: z.object({
|
||||||
|
keyId: z.string().uuid().describe(KMS.DELETE_KEY.keyId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
key: KmsKeysSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
params: { keyId },
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const cmek = await server.services.cmek.deleteCmekById(keyId, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_CMEK,
|
||||||
|
metadata: {
|
||||||
|
keyId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { key: cmek };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// list KMS keys
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/keys",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "List KMS keys",
|
||||||
|
querystring: z.object({
|
||||||
|
projectId: z.string().describe(KMS.LIST_KEYS.projectId),
|
||||||
|
offset: z.coerce.number().min(0).optional().default(0).describe(KMS.LIST_KEYS.offset),
|
||||||
|
limit: z.coerce.number().min(1).max(100).optional().default(100).describe(KMS.LIST_KEYS.limit),
|
||||||
|
orderBy: z.nativeEnum(CmekOrderBy).optional().default(CmekOrderBy.Name).describe(KMS.LIST_KEYS.orderBy),
|
||||||
|
orderDirection: z
|
||||||
|
.nativeEnum(OrderByDirection)
|
||||||
|
.optional()
|
||||||
|
.default(OrderByDirection.ASC)
|
||||||
|
.describe(KMS.LIST_KEYS.orderDirection),
|
||||||
|
search: z.string().trim().optional().describe(KMS.LIST_KEYS.search)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
keys: KmsKeysSchema.merge(InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true })).array(),
|
||||||
|
totalCount: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
query: { projectId, ...dto },
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const { cmeks, totalCount } = await server.services.cmek.listCmeksByProjectId({ projectId, ...dto }, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_CMEKS,
|
||||||
|
metadata: {
|
||||||
|
keyIds: cmeks.map((key) => key.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { keys: cmeks, totalCount };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// encrypt data
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/keys/:keyId/encrypt",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Encrypt data with KMS key",
|
||||||
|
params: z.object({
|
||||||
|
keyId: z.string().uuid().describe(KMS.ENCRYPT.keyId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
plaintext: base64Schema.describe(KMS.ENCRYPT.plaintext)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
ciphertext: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
params: { keyId },
|
||||||
|
body: { plaintext },
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const ciphertext = await server.services.cmek.cmekEncrypt({ keyId, plaintext }, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CMEK_ENCRYPT,
|
||||||
|
metadata: {
|
||||||
|
keyId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ciphertext };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/keys/:keyId/decrypt",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Decrypt data with KMS key",
|
||||||
|
params: z.object({
|
||||||
|
keyId: z.string().uuid().describe(KMS.ENCRYPT.keyId)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
ciphertext: base64Schema.describe(KMS.ENCRYPT.plaintext)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
plaintext: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const {
|
||||||
|
params: { keyId },
|
||||||
|
body: { ciphertext },
|
||||||
|
permission
|
||||||
|
} = req;
|
||||||
|
|
||||||
|
const plaintext = await server.services.cmek.cmekDecrypt({ keyId, ciphertext }, permission);
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.CMEK_DECRYPT,
|
||||||
|
metadata: {
|
||||||
|
keyId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { plaintext };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -1,7 +1,9 @@
|
|||||||
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
|
import { SecretFoldersSchema, SecretImportsSchema, SecretTagsSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { DASHBOARD } from "@app/lib/api-docs";
|
import { DASHBOARD } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { removeTrailingSlash } from "@app/lib/fn";
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
@@ -15,6 +17,20 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
|||||||
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
import { SecretsOrderBy } from "@app/services/secret/secret-types";
|
||||||
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
|
||||||
|
|
||||||
|
// handle querystring boolean values
|
||||||
|
const booleanSchema = z
|
||||||
|
.union([z.boolean(), z.string().trim()])
|
||||||
|
.transform((value) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
// ie if not empty, 0 or false, return true
|
||||||
|
return Boolean(value) && Number(value) !== 0 && value.toLowerCase() !== "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
.default(true);
|
||||||
|
|
||||||
export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -55,21 +71,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.describe(DASHBOARD.SECRET_OVERVIEW_LIST.orderDirection)
|
.describe(DASHBOARD.SECRET_OVERVIEW_LIST.orderDirection)
|
||||||
.optional(),
|
.optional(),
|
||||||
search: z.string().trim().describe(DASHBOARD.SECRET_OVERVIEW_LIST.search).optional(),
|
search: z.string().trim().describe(DASHBOARD.SECRET_OVERVIEW_LIST.search).optional(),
|
||||||
includeSecrets: z.coerce
|
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecrets),
|
||||||
.boolean()
|
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeFolders),
|
||||||
.optional()
|
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeDynamicSecrets)
|
||||||
.default(true)
|
|
||||||
.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeSecrets),
|
|
||||||
includeFolders: z.coerce
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.default(true)
|
|
||||||
.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeFolders),
|
|
||||||
includeDynamicSecrets: z.coerce
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.default(true)
|
|
||||||
.describe(DASHBOARD.SECRET_OVERVIEW_LIST.includeDynamicSecrets)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -173,7 +177,30 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeDynamicSecrets) {
|
if (!includeDynamicSecrets && !includeSecrets)
|
||||||
|
return {
|
||||||
|
folders,
|
||||||
|
totalFolderCount,
|
||||||
|
totalCount: totalFolderCount ?? 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const { permission } = await server.services.permission.getProjectPermission(
|
||||||
|
req.permission.type,
|
||||||
|
req.permission.id,
|
||||||
|
projectId,
|
||||||
|
req.permission.authMethod,
|
||||||
|
req.permission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
const permissiveEnvs = // filter envs user has access to
|
||||||
|
environments.filter((environment) =>
|
||||||
|
permission.can(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
subject(ProjectPermissionSub.Secrets, { environment, secretPath })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (includeDynamicSecrets && permissiveEnvs.length) {
|
||||||
// this is the unique count, ie duplicate secrets across envs only count as 1
|
// this is the unique count, ie duplicate secrets across envs only count as 1
|
||||||
totalDynamicSecretCount = await server.services.dynamicSecret.getCountMultiEnv({
|
totalDynamicSecretCount = await server.services.dynamicSecret.getCountMultiEnv({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
@@ -182,8 +209,9 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId,
|
projectId,
|
||||||
search,
|
search,
|
||||||
environmentSlugs: environments,
|
environmentSlugs: permissiveEnvs,
|
||||||
path: secretPath
|
path: secretPath,
|
||||||
|
isInternal: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (remainingLimit > 0 && totalDynamicSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalDynamicSecretCount > adjustedOffset) {
|
||||||
@@ -196,10 +224,11 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
search,
|
search,
|
||||||
orderBy,
|
orderBy,
|
||||||
orderDirection,
|
orderDirection,
|
||||||
environmentSlugs: environments,
|
environmentSlugs: permissiveEnvs,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
limit: remainingLimit,
|
limit: remainingLimit,
|
||||||
offset: adjustedOffset
|
offset: adjustedOffset,
|
||||||
|
isInternal: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// get the count of unique dynamic secret names to properly adjust remaining limit
|
// get the count of unique dynamic secret names to properly adjust remaining limit
|
||||||
@@ -212,17 +241,18 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeSecrets) {
|
if (includeSecrets && permissiveEnvs.length) {
|
||||||
// this is the unique count, ie duplicate secrets across envs only count as 1
|
// this is the unique count, ie duplicate secrets across envs only count as 1
|
||||||
totalSecretCount = await server.services.secret.getSecretsCountMultiEnv({
|
totalSecretCount = await server.services.secret.getSecretsCountMultiEnv({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
environments,
|
environments: permissiveEnvs,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId,
|
projectId,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
search
|
search,
|
||||||
|
isInternal: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
@@ -230,7 +260,7 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
environments,
|
environments: permissiveEnvs,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId,
|
projectId,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
@@ -238,10 +268,11 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
orderDirection,
|
orderDirection,
|
||||||
search,
|
search,
|
||||||
limit: remainingLimit,
|
limit: remainingLimit,
|
||||||
offset: adjustedOffset
|
offset: adjustedOffset,
|
||||||
|
isInternal: true
|
||||||
});
|
});
|
||||||
|
|
||||||
for await (const environment of environments) {
|
for await (const environment of permissiveEnvs) {
|
||||||
const secretCountFromEnv = secrets.filter((secret) => secret.environment === environment).length;
|
const secretCountFromEnv = secrets.filter((secret) => secret.environment === environment).length;
|
||||||
|
|
||||||
if (secretCountFromEnv) {
|
if (secretCountFromEnv) {
|
||||||
@@ -325,26 +356,10 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
search: z.string().trim().describe(DASHBOARD.SECRET_DETAILS_LIST.search).optional(),
|
||||||
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
tags: z.string().trim().transform(decodeURIComponent).describe(DASHBOARD.SECRET_DETAILS_LIST.tags).optional(),
|
||||||
includeSecrets: z.coerce
|
includeSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
||||||
.boolean()
|
includeFolders: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
||||||
.optional()
|
includeDynamicSecrets: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
||||||
.default(true)
|
includeImports: booleanSchema.describe(DASHBOARD.SECRET_DETAILS_LIST.includeImports)
|
||||||
.describe(DASHBOARD.SECRET_DETAILS_LIST.includeSecrets),
|
|
||||||
includeFolders: z.coerce
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.default(true)
|
|
||||||
.describe(DASHBOARD.SECRET_DETAILS_LIST.includeFolders),
|
|
||||||
includeDynamicSecrets: z.coerce
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.default(true)
|
|
||||||
.describe(DASHBOARD.SECRET_DETAILS_LIST.includeDynamicSecrets),
|
|
||||||
includeImports: z.coerce
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.default(true)
|
|
||||||
.describe(DASHBOARD.SECRET_DETAILS_LIST.includeImports)
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -498,56 +513,44 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeDynamicSecrets) {
|
try {
|
||||||
totalDynamicSecretCount = await server.services.dynamicSecret.getDynamicSecretCount({
|
if (includeDynamicSecrets) {
|
||||||
actor: req.permission.type,
|
totalDynamicSecretCount = await server.services.dynamicSecret.getDynamicSecretCount({
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId,
|
|
||||||
search,
|
|
||||||
environmentSlug: environment,
|
|
||||||
path: secretPath
|
|
||||||
});
|
|
||||||
|
|
||||||
if (remainingLimit > 0 && totalDynamicSecretCount > adjustedOffset) {
|
|
||||||
dynamicSecrets = await server.services.dynamicSecret.listDynamicSecretsByEnv({
|
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
projectId,
|
projectId,
|
||||||
search,
|
search,
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
environmentSlug: environment,
|
environmentSlug: environment,
|
||||||
path: secretPath,
|
path: secretPath
|
||||||
limit: remainingLimit,
|
|
||||||
offset: adjustedOffset
|
|
||||||
});
|
});
|
||||||
|
|
||||||
remainingLimit -= dynamicSecrets.length;
|
if (remainingLimit > 0 && totalDynamicSecretCount > adjustedOffset) {
|
||||||
adjustedOffset = 0;
|
dynamicSecrets = await server.services.dynamicSecret.listDynamicSecretsByEnv({
|
||||||
} else {
|
actor: req.permission.type,
|
||||||
adjustedOffset = Math.max(0, adjustedOffset - totalDynamicSecretCount);
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId,
|
||||||
|
search,
|
||||||
|
orderBy,
|
||||||
|
orderDirection,
|
||||||
|
environmentSlug: environment,
|
||||||
|
path: secretPath,
|
||||||
|
limit: remainingLimit,
|
||||||
|
offset: adjustedOffset
|
||||||
|
});
|
||||||
|
|
||||||
|
remainingLimit -= dynamicSecrets.length;
|
||||||
|
adjustedOffset = 0;
|
||||||
|
} else {
|
||||||
|
adjustedOffset = Math.max(0, adjustedOffset - totalDynamicSecretCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (includeSecrets) {
|
if (includeSecrets) {
|
||||||
totalSecretCount = await server.services.secret.getSecretsCount({
|
totalSecretCount = await server.services.secret.getSecretsCount({
|
||||||
actorId: req.permission.id,
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
environment,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
projectId,
|
|
||||||
path: secretPath,
|
|
||||||
search,
|
|
||||||
tagSlugs: tags
|
|
||||||
});
|
|
||||||
|
|
||||||
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
|
||||||
const secretsRaw = await server.services.secret.getSecretsRaw({
|
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
@@ -555,44 +558,62 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
projectId,
|
projectId,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
orderBy,
|
|
||||||
orderDirection,
|
|
||||||
search,
|
search,
|
||||||
limit: remainingLimit,
|
|
||||||
offset: adjustedOffset,
|
|
||||||
tagSlugs: tags
|
tagSlugs: tags
|
||||||
});
|
});
|
||||||
|
|
||||||
secrets = secretsRaw.secrets;
|
if (remainingLimit > 0 && totalSecretCount > adjustedOffset) {
|
||||||
|
const secretsRaw = await server.services.secret.getSecretsRaw({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
environment,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
projectId,
|
||||||
|
path: secretPath,
|
||||||
|
orderBy,
|
||||||
|
orderDirection,
|
||||||
|
search,
|
||||||
|
limit: remainingLimit,
|
||||||
|
offset: adjustedOffset,
|
||||||
|
tagSlugs: tags
|
||||||
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
secrets = secretsRaw.secrets;
|
||||||
projectId,
|
|
||||||
...req.auditLogInfo,
|
|
||||||
event: {
|
|
||||||
type: EventType.GET_SECRETS,
|
|
||||||
metadata: {
|
|
||||||
environment,
|
|
||||||
secretPath,
|
|
||||||
numberOfSecrets: secrets.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) {
|
await server.services.auditLog.createAuditLog({
|
||||||
await server.services.telemetry.sendPostHogEvents({
|
projectId,
|
||||||
event: PostHogEventTypes.SecretPulled,
|
...req.auditLogInfo,
|
||||||
distinctId: getTelemetryDistinctId(req),
|
event: {
|
||||||
properties: {
|
type: EventType.GET_SECRETS,
|
||||||
numberOfSecrets: secrets.length,
|
metadata: {
|
||||||
workspaceId: projectId,
|
environment,
|
||||||
environment,
|
secretPath,
|
||||||
secretPath,
|
numberOfSecrets: secrets.length
|
||||||
channel: getUserAgentType(req.headers["user-agent"]),
|
}
|
||||||
...req.auditLogInfo
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (getUserAgentType(req.headers["user-agent"]) !== UserAgentType.K8_OPERATOR) {
|
||||||
|
await server.services.telemetry.sendPostHogEvents({
|
||||||
|
event: PostHogEventTypes.SecretPulled,
|
||||||
|
distinctId: getTelemetryDistinctId(req),
|
||||||
|
properties: {
|
||||||
|
numberOfSecrets: secrets.length,
|
||||||
|
workspaceId: projectId,
|
||||||
|
environment,
|
||||||
|
secretPath,
|
||||||
|
channel: getUserAgentType(req.headers["user-agent"]),
|
||||||
|
...req.auditLogInfo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!(error instanceof ForbiddenError)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
@@ -22,7 +22,7 @@ export const registerIdentityAwsAuthRouter = async (server: FastifyZodProvider)
|
|||||||
schema: {
|
schema: {
|
||||||
description: "Login with AWS Auth",
|
description: "Login with AWS Auth",
|
||||||
body: z.object({
|
body: z.object({
|
||||||
identityId: z.string().describe(AWS_AUTH.LOGIN.identityId),
|
identityId: z.string().trim().describe(AWS_AUTH.LOGIN.identityId),
|
||||||
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
|
iamHttpRequestMethod: z.string().default("POST").describe(AWS_AUTH.LOGIN.iamHttpRequestMethod),
|
||||||
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
|
iamRequestBody: z.string().describe(AWS_AUTH.LOGIN.iamRequestBody),
|
||||||
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
|
iamRequestHeaders: z.string().describe(AWS_AUTH.LOGIN.iamRequestHeaders)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user