mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-29 22:37:44 +00:00
Compare commits
239 Commits
daniel/fix
...
daniel/bet
Author | SHA1 | Date | |
---|---|---|---|
|
cee982754b | ||
|
a6497b844a | ||
|
788dcf2c73 | ||
|
7f055450df | ||
|
9234213c62 | ||
|
e7278c4cd9 | ||
|
3e79dbb3f5 | ||
|
9b2565e387 | ||
|
1c5a8cabe9 | ||
|
10e7999334 | ||
|
8c458588ab | ||
|
2381a2e4ba | ||
|
9ef8812205 | ||
|
37a204e49e | ||
|
11927f341a | ||
|
6fc17a4964 | ||
|
eb00232db6 | ||
|
4fd245e493 | ||
|
d92c57d051 | ||
|
beaef1feb0 | ||
|
033fd5e7a4 | ||
|
f49f3c926c | ||
|
280d44f1e5 | ||
|
4eea0dc544 | ||
|
8a33f1a591 | ||
|
56ff11d63f | ||
|
1ecce285f0 | ||
|
b5c9b6a1bd | ||
|
e12ac6c07e | ||
|
ea480c222b | ||
|
1fb644af4a | ||
|
a6f4a95821 | ||
|
8578208f2d | ||
|
fc4189ba0f | ||
|
b9ecf42fb6 | ||
|
008e18638f | ||
|
ac3b9c25dd | ||
|
f4997dec12 | ||
|
fcf405c630 | ||
|
efc6876260 | ||
|
8bab6d87bb | ||
|
39a49f12f5 | ||
|
cfd841ea08 | ||
|
4d67c03e3e | ||
|
8826bc5d60 | ||
|
03fdce67f1 | ||
|
72f3f7980e | ||
|
f1aa2fbd84 | ||
|
217de6250f | ||
|
f742bd01d9 | ||
|
3fe53d5183 | ||
|
a5f5f803df | ||
|
c37e3ba635 | ||
|
55279e5e41 | ||
|
88fb37e8c6 | ||
|
6271dcc25d | ||
|
0f7faa6bfe | ||
|
4ace339d5b | ||
|
e8c0d1ece9 | ||
|
bb1977976c | ||
|
bb3da75870 | ||
|
088e888560 | ||
|
180241fdf0 | ||
|
93f27a7ee8 | ||
|
ed3bc8dd27 | ||
|
8dc4809ec8 | ||
|
a55d64e430 | ||
|
02d54da74a | ||
|
d660168700 | ||
|
1c75fc84f0 | ||
|
f63da87c7f | ||
|
53b9fe2dec | ||
|
87dc0eed7e | ||
|
f2dd6f94a4 | ||
|
ac26ae3893 | ||
|
4c65e9910a | ||
|
5150c102e6 | ||
|
41c29d41e1 | ||
|
4de33190a9 | ||
|
7cfecb39e4 | ||
|
7524b83c29 | ||
|
7a41cdf51b | ||
|
17d99cb2cf | ||
|
bd0da0ff74 | ||
|
d2a54234f4 | ||
|
626262461a | ||
|
93ba29e57f | ||
|
1581aa088d | ||
|
ceab951bca | ||
|
2e3dcc50ae | ||
|
a79087670e | ||
|
7b04c08fc7 | ||
|
70842b8e5e | ||
|
36e3e4c1b5 | ||
|
ce9b66ef14 | ||
|
1384c8e855 | ||
|
f213c75ede | ||
|
6ade708e19 | ||
|
ce3af41ebc | ||
|
e442f10fa5 | ||
|
2e8ad18285 | ||
|
f03ca7f916 | ||
|
bfa533e9d2 | ||
|
a8759e7410 | ||
|
af1905a39e | ||
|
16182a9d1d | ||
|
1321aa712f | ||
|
c1f61f2db4 | ||
|
5ad00130ea | ||
|
ea5e8e29e6 | ||
|
e7f89bdfef | ||
|
d23a7e41f3 | ||
|
52a885716d | ||
|
3fc907f076 | ||
|
eaf10483c0 | ||
|
dcd0234fb5 | ||
|
4dda270e8e | ||
|
4e6b289e1b | ||
|
c1cb85b49f | ||
|
ed71e651f6 | ||
|
6fab7d9507 | ||
|
1a11dd954b | ||
|
5d3574d3f6 | ||
|
aa42aa05aa | ||
|
7a36badb23 | ||
|
9ce6fd3f8e | ||
|
a549c8b9e3 | ||
|
1c749c84f2 | ||
|
1bc1feb843 | ||
|
80ca115ccd | ||
|
5a6bb90870 | ||
|
de7a693a6a | ||
|
096417281e | ||
|
763a96faf8 | ||
|
870eaf9301 | ||
|
10abf192a1 | ||
|
508f697bdd | ||
|
8ea8a6f72e | ||
|
54e6f4b607 | ||
|
ea3b3c5cec | ||
|
a8fd83652d | ||
|
45f3675337 | ||
|
87a9a87dcd | ||
|
0b882ece8c | ||
|
e005e94165 | ||
|
0e07eaaa01 | ||
|
e10e313af3 | ||
|
e6c0bbb25b | ||
|
2b39d9e6c4 | ||
|
cf42279e5b | ||
|
fbc4b47198 | ||
|
4baa6b1d3d | ||
|
74ee77f41e | ||
|
ee1b12173a | ||
|
1bfbc7047c | ||
|
a410d560a7 | ||
|
99e150cc1d | ||
|
e7191c2f71 | ||
|
f6deb0969a | ||
|
1163e41e64 | ||
|
a0f93f995e | ||
|
50fcf97a36 | ||
|
8e68d21115 | ||
|
372b6cbaea | ||
|
26add7bfd1 | ||
|
364302a691 | ||
|
c8dc29d59b | ||
|
f3d207ab5c | ||
|
e1cd632546 | ||
|
655ee4f118 | ||
|
34a2452bf5 | ||
|
7846a81636 | ||
|
6bdf3455f5 | ||
|
556ae168dd | ||
|
7b19d2aa6a | ||
|
bda9bb3d61 | ||
|
4b66a9343c | ||
|
4930d7fc02 | ||
|
ad644db512 | ||
|
3707b75349 | ||
|
ffaf145317 | ||
|
17b0d0081d | ||
|
ecf177fecc | ||
|
6112bc9356 | ||
|
6c3156273c | ||
|
eb7c804bb9 | ||
|
9d7bfae519 | ||
|
1292b5bf56 | ||
|
f09e18a706 | ||
|
5d9a43a3fd | ||
|
12154c869f | ||
|
8d66272ab2 | ||
|
0e44e630cb | ||
|
49c4929c9c | ||
|
da561e37c5 | ||
|
ebc584d36f | ||
|
656d979d7d | ||
|
a29fb613b9 | ||
|
5382f3de2d | ||
|
b2b858f7e8 | ||
|
dbc5b5a3d1 | ||
|
8f3d328b9a | ||
|
b7d683ee1b | ||
|
9bd6ec19c4 | ||
|
03fd0a1eb9 | ||
|
97023e7714 | ||
|
1d23ed0680 | ||
|
1bd66a614b | ||
|
802a9cf83c | ||
|
9e95fdbb58 | ||
|
803f56cfe5 | ||
|
b163a6c5ad | ||
|
ddc119ceb6 | ||
|
302e068c74 | ||
|
95b92caff3 | ||
|
5d894b6d43 | ||
|
09e621539e | ||
|
5e0b78b104 | ||
|
27852607d1 | ||
|
956719f797 | ||
|
71b8c59050 | ||
|
15c5fe4095 | ||
|
91ebcca0fd | ||
|
0826b40e2a | ||
|
911b62c63a | ||
|
5343c7af00 | ||
|
8c03c160a9 | ||
|
604b0467f9 | ||
|
a2b555dd81 | ||
|
23c362f9cd | ||
|
9120367562 | ||
|
f509464947 | ||
|
07fd489982 | ||
|
f6d3831d6d | ||
|
d604ef2480 | ||
|
fe096772e0 | ||
|
35a63b8cc6 | ||
|
2a4596d415 | ||
|
35e476d916 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -63,6 +63,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Editor specific
|
# Editor specific
|
||||||
.vscode/*
|
.vscode/*
|
||||||
|
.idea/*
|
||||||
|
|
||||||
frontend-build
|
frontend-build
|
||||||
|
|
||||||
|
329
backend/package-lock.json
generated
329
backend/package-lock.json
generated
@@ -34,6 +34,8 @@
|
|||||||
"@peculiar/x509": "^1.12.1",
|
"@peculiar/x509": "^1.12.1",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
|
"@slack/oauth": "^3.0.1",
|
||||||
|
"@slack/web-api": "^7.3.4",
|
||||||
"@team-plain/typescript-sdk": "^4.6.1",
|
"@team-plain/typescript-sdk": "^4.6.1",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
@@ -61,6 +63,7 @@
|
|||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"mongodb": "^6.8.1",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"mysql2": "^3.9.8",
|
"mysql2": "^3.9.8",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
@@ -4778,6 +4781,14 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
|
"version": "1.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
|
||||||
|
"integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
|
||||||
|
"dependencies": {
|
||||||
|
"sparse-bitfield": "^3.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz",
|
||||||
@@ -5972,6 +5983,78 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@slack/logger": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18",
|
||||||
|
"npm": ">= 8.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@slack/oauth": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-TuR9PI6bYKX6qHC7FQI4keMnhj45TNfSNQtTU3mtnHUX4XLM2dYLvRkUNADyiLTle2qu2rsOQtCIsZJw6H0sDA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@slack/logger": "^4",
|
||||||
|
"@slack/web-api": "^7.3.4",
|
||||||
|
"@types/jsonwebtoken": "^9",
|
||||||
|
"@types/node": ">=18",
|
||||||
|
"jsonwebtoken": "^9",
|
||||||
|
"lodash.isstring": "^4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18",
|
||||||
|
"npm": ">=8.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@slack/types": {
|
||||||
|
"version": "2.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@slack/types/-/types-2.12.0.tgz",
|
||||||
|
"integrity": "sha512-yFewzUomYZ2BYaGJidPuIgjoYj5wqPDmi7DLSaGIkf+rCi4YZ2Z3DaiYIbz7qb/PL2NmamWjCvB7e9ArI5HkKg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.13.0",
|
||||||
|
"npm": ">= 6.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@slack/web-api": {
|
||||||
|
"version": "7.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.3.4.tgz",
|
||||||
|
"integrity": "sha512-KwLK8dlz2lhr3NO7kbYQ7zgPTXPKrhq1JfQc0etJ0K8LSJhYYnf8GbVznvgDT/Uz1/pBXfFQnoXjrQIOKAdSuw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@slack/logger": "^4.0.0",
|
||||||
|
"@slack/types": "^2.9.0",
|
||||||
|
"@types/node": ">=18.0.0",
|
||||||
|
"@types/retry": "0.12.0",
|
||||||
|
"axios": "^1.7.4",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"is-electron": "2.2.2",
|
||||||
|
"is-stream": "^2",
|
||||||
|
"p-queue": "^6",
|
||||||
|
"p-retry": "^4",
|
||||||
|
"retry": "^0.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18",
|
||||||
|
"npm": ">= 8.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@slack/web-api/node_modules/is-stream": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@smithy/abort-controller": {
|
"node_modules/@smithy/abort-controller": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz",
|
||||||
@@ -7177,6 +7260,11 @@
|
|||||||
"integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==",
|
"integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/retry": {
|
||||||
|
"version": "0.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||||
|
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
|
||||||
|
},
|
||||||
"node_modules/@types/safe-regex": {
|
"node_modules/@types/safe-regex": {
|
||||||
"version": "1.1.6",
|
"version": "1.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/safe-regex/-/safe-regex-1.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/safe-regex/-/safe-regex-1.1.6.tgz",
|
||||||
@@ -7214,6 +7302,19 @@
|
|||||||
"integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==",
|
"integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/webidl-conversions": {
|
||||||
|
"version": "7.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
|
||||||
|
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
|
||||||
|
},
|
||||||
|
"node_modules/@types/whatwg-url": {
|
||||||
|
"version": "11.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
|
||||||
|
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/webidl-conversions": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/xml-crypto": {
|
"node_modules/@types/xml-crypto": {
|
||||||
"version": "1.4.6",
|
"version": "1.4.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.6.tgz",
|
||||||
@@ -8822,6 +8923,14 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bson": {
|
||||||
|
"version": "6.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz",
|
||||||
|
"integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/btoa-lite": {
|
"node_modules/btoa-lite": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz",
|
||||||
@@ -10355,6 +10464,11 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
|
||||||
|
},
|
||||||
"node_modules/events": {
|
"node_modules/events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
@@ -11174,15 +11288,46 @@
|
|||||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
},
|
},
|
||||||
"node_modules/gcp-metadata": {
|
"node_modules/gcp-metadata": {
|
||||||
"version": "6.1.0",
|
"version": "5.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
|
||||||
"integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
|
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"gaxios": "^6.0.0",
|
"gaxios": "^5.0.0",
|
||||||
"json-bigint": "^1.0.0"
|
"json-bigint": "^1.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gcp-metadata/node_modules/gaxios": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"extend": "^3.0.2",
|
||||||
|
"https-proxy-agent": "^5.0.0",
|
||||||
|
"is-stream": "^2.0.0",
|
||||||
|
"node-fetch": "^2.6.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gcp-metadata/node_modules/is-stream": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/generate-function": {
|
"node_modules/generate-function": {
|
||||||
@@ -11407,6 +11552,18 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/google-auth-library/node_modules/gcp-metadata": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
|
||||||
|
"dependencies": {
|
||||||
|
"gaxios": "^6.0.0",
|
||||||
|
"json-bigint": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/google-auth-library/node_modules/jwa": {
|
"node_modules/google-auth-library/node_modules/jwa": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
|
||||||
@@ -12105,6 +12262,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-electron": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg=="
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
@@ -13118,6 +13280,11 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/memory-pager": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
|
||||||
|
},
|
||||||
"node_modules/merge-descriptors": {
|
"node_modules/merge-descriptors": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||||
@@ -13308,6 +13475,91 @@
|
|||||||
"obliterator": "^2.0.1"
|
"obliterator": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mongodb": {
|
||||||
|
"version": "6.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.1.tgz",
|
||||||
|
"integrity": "sha512-qsS+gl5EJb+VzJqUjXSZ5Y5rbuM/GZlZUEJ2OIVYP10L9rO9DQ0DGp+ceTzsmoADh6QYMWd9MSdG9IxRyYUkEA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@mongodb-js/saslprep": "^1.1.5",
|
||||||
|
"bson": "^6.7.0",
|
||||||
|
"mongodb-connection-string-url": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@aws-sdk/credential-providers": "^3.188.0",
|
||||||
|
"@mongodb-js/zstd": "^1.1.0",
|
||||||
|
"gcp-metadata": "^5.2.0",
|
||||||
|
"kerberos": "^2.0.1",
|
||||||
|
"mongodb-client-encryption": ">=6.0.0 <7",
|
||||||
|
"snappy": "^7.2.2",
|
||||||
|
"socks": "^2.7.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@aws-sdk/credential-providers": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@mongodb-js/zstd": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"gcp-metadata": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"kerberos": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"mongodb-client-encryption": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"snappy": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"socks": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/whatwg-url": "^11.0.2",
|
||||||
|
"whatwg-url": "^13.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
|
||||||
|
"dependencies": {
|
||||||
|
"punycode": "^2.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
|
||||||
|
"version": "13.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
|
||||||
|
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "^4.1.1",
|
||||||
|
"webidl-conversions": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mri": {
|
"node_modules/mri": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz",
|
||||||
@@ -13968,6 +14220,14 @@
|
|||||||
"node": ">=14.6"
|
"node": ">=14.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-finally": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-is-promise": {
|
"node_modules/p-is-promise": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz",
|
||||||
@@ -14006,6 +14266,38 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-queue": {
|
||||||
|
"version": "6.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
|
||||||
|
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^4.0.4",
|
||||||
|
"p-timeout": "^3.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-queue/node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||||
|
},
|
||||||
|
"node_modules/p-retry": {
|
||||||
|
"version": "4.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||||
|
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/retry": "0.12.0",
|
||||||
|
"retry": "^0.13.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-throttle": {
|
"node_modules/p-throttle": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.1.0.tgz",
|
||||||
@@ -14017,6 +14309,17 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-timeout": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
|
||||||
|
"dependencies": {
|
||||||
|
"p-finally": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/p-try": {
|
"node_modules/p-try": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
@@ -15367,6 +15670,14 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/retry": {
|
||||||
|
"version": "0.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||||
|
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reusify": {
|
"node_modules/reusify": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||||
@@ -15927,6 +16238,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sparse-bitfield": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"memory-pager": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/split2": {
|
"node_modules/split2": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
|
@@ -131,6 +131,8 @@
|
|||||||
"@peculiar/x509": "^1.12.1",
|
"@peculiar/x509": "^1.12.1",
|
||||||
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
"@serdnam/pino-cloudwatch-transport": "^1.0.4",
|
||||||
"@sindresorhus/slugify": "1.1.0",
|
"@sindresorhus/slugify": "1.1.0",
|
||||||
|
"@slack/oauth": "^3.0.1",
|
||||||
|
"@slack/web-api": "^7.3.4",
|
||||||
"@team-plain/typescript-sdk": "^4.6.1",
|
"@team-plain/typescript-sdk": "^4.6.1",
|
||||||
"@ucast/mongo2js": "^1.3.4",
|
"@ucast/mongo2js": "^1.3.4",
|
||||||
"ajv": "^8.12.0",
|
"ajv": "^8.12.0",
|
||||||
@@ -158,6 +160,7 @@
|
|||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
|
"mongodb": "^6.8.1",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"mysql2": "^3.9.8",
|
"mysql2": "^3.9.8",
|
||||||
"nanoid": "^3.3.4",
|
"nanoid": "^3.3.4",
|
||||||
|
4
backend/src/@types/fastify.d.ts
vendored
4
backend/src/@types/fastify.d.ts
vendored
@@ -70,12 +70,14 @@ import { TSecretReplicationServiceFactory } from "@app/services/secret-replicati
|
|||||||
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { TSecretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service";
|
||||||
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
import { TServiceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
||||||
|
import { TSlackServiceFactory } from "@app/services/slack/slack-service";
|
||||||
import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
|
import { TSuperAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
|
||||||
import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
import { TTelemetryServiceFactory } from "@app/services/telemetry/telemetry-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
import { TUserServiceFactory } from "@app/services/user/user-service";
|
import { TUserServiceFactory } from "@app/services/user/user-service";
|
||||||
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { TUserEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { TWebhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
@@ -177,6 +179,8 @@ declare module "fastify" {
|
|||||||
userEngagement: TUserEngagementServiceFactory;
|
userEngagement: TUserEngagementServiceFactory;
|
||||||
externalKms: TExternalKmsServiceFactory;
|
externalKms: TExternalKmsServiceFactory;
|
||||||
orgAdmin: TOrgAdminServiceFactory;
|
orgAdmin: TOrgAdminServiceFactory;
|
||||||
|
slack: TSlackServiceFactory;
|
||||||
|
workflowIntegration: TWorkflowIntegrationServiceFactory;
|
||||||
};
|
};
|
||||||
// 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
|
||||||
|
26
backend/src/@types/knex.d.ts
vendored
26
backend/src/@types/knex.d.ts
vendored
@@ -193,6 +193,9 @@ import {
|
|||||||
TProjectRolesUpdate,
|
TProjectRolesUpdate,
|
||||||
TProjects,
|
TProjects,
|
||||||
TProjectsInsert,
|
TProjectsInsert,
|
||||||
|
TProjectSlackConfigs,
|
||||||
|
TProjectSlackConfigsInsert,
|
||||||
|
TProjectSlackConfigsUpdate,
|
||||||
TProjectsUpdate,
|
TProjectsUpdate,
|
||||||
TProjectUserAdditionalPrivilege,
|
TProjectUserAdditionalPrivilege,
|
||||||
TProjectUserAdditionalPrivilegeInsert,
|
TProjectUserAdditionalPrivilegeInsert,
|
||||||
@@ -299,6 +302,9 @@ import {
|
|||||||
TServiceTokens,
|
TServiceTokens,
|
||||||
TServiceTokensInsert,
|
TServiceTokensInsert,
|
||||||
TServiceTokensUpdate,
|
TServiceTokensUpdate,
|
||||||
|
TSlackIntegrations,
|
||||||
|
TSlackIntegrationsInsert,
|
||||||
|
TSlackIntegrationsUpdate,
|
||||||
TSuperAdmin,
|
TSuperAdmin,
|
||||||
TSuperAdminInsert,
|
TSuperAdminInsert,
|
||||||
TSuperAdminUpdate,
|
TSuperAdminUpdate,
|
||||||
@@ -322,7 +328,10 @@ import {
|
|||||||
TUsersUpdate,
|
TUsersUpdate,
|
||||||
TWebhooks,
|
TWebhooks,
|
||||||
TWebhooksInsert,
|
TWebhooksInsert,
|
||||||
TWebhooksUpdate
|
TWebhooksUpdate,
|
||||||
|
TWorkflowIntegrations,
|
||||||
|
TWorkflowIntegrationsInsert,
|
||||||
|
TWorkflowIntegrationsUpdate
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import {
|
import {
|
||||||
TSecretV2TagJunction,
|
TSecretV2TagJunction,
|
||||||
@@ -776,5 +785,20 @@ declare module "knex/types/tables" {
|
|||||||
TKmsKeyVersionsInsert,
|
TKmsKeyVersionsInsert,
|
||||||
TKmsKeyVersionsUpdate
|
TKmsKeyVersionsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SlackIntegrations]: KnexOriginal.CompositeTableType<
|
||||||
|
TSlackIntegrations,
|
||||||
|
TSlackIntegrationsInsert,
|
||||||
|
TSlackIntegrationsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.ProjectSlackConfigs]: KnexOriginal.CompositeTableType<
|
||||||
|
TProjectSlackConfigs,
|
||||||
|
TProjectSlackConfigsInsert,
|
||||||
|
TProjectSlackConfigsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.WorkflowIntegrations]: KnexOriginal.CompositeTableType<
|
||||||
|
TWorkflowIntegrations,
|
||||||
|
TWorkflowIntegrationsInsert,
|
||||||
|
TWorkflowIntegrationsUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,96 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasTable(TableName.WorkflowIntegrations))) {
|
||||||
|
await knex.schema.createTable(TableName.WorkflowIntegrations, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("integration").notNullable();
|
||||||
|
tb.string("slug").notNullable();
|
||||||
|
tb.uuid("orgId").notNullable();
|
||||||
|
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
tb.string("description");
|
||||||
|
tb.unique(["orgId", "slug"]);
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.SlackIntegrations))) {
|
||||||
|
await knex.schema.createTable(TableName.SlackIntegrations, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).notNullable();
|
||||||
|
tb.foreign("id").references("id").inTable(TableName.WorkflowIntegrations).onDelete("CASCADE");
|
||||||
|
tb.string("teamId").notNullable();
|
||||||
|
tb.string("teamName").notNullable();
|
||||||
|
tb.string("slackUserId").notNullable();
|
||||||
|
tb.string("slackAppId").notNullable();
|
||||||
|
tb.binary("encryptedBotAccessToken").notNullable();
|
||||||
|
tb.string("slackBotId").notNullable();
|
||||||
|
tb.string("slackBotUserId").notNullable();
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await knex.schema.hasTable(TableName.ProjectSlackConfigs))) {
|
||||||
|
await knex.schema.createTable(TableName.ProjectSlackConfigs, (tb) => {
|
||||||
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
tb.string("projectId").notNullable().unique();
|
||||||
|
tb.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
tb.uuid("slackIntegrationId").notNullable();
|
||||||
|
tb.foreign("slackIntegrationId").references("id").inTable(TableName.SlackIntegrations).onDelete("CASCADE");
|
||||||
|
tb.boolean("isAccessRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
tb.string("accessRequestChannels").notNullable().defaultTo("");
|
||||||
|
tb.boolean("isSecretRequestNotificationEnabled").notNullable().defaultTo(false);
|
||||||
|
tb.string("secretRequestChannels").notNullable().defaultTo("");
|
||||||
|
tb.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.ProjectSlackConfigs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doesSuperAdminHaveSlackClientId = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedSlackClientId");
|
||||||
|
const doesSuperAdminHaveSlackClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedSlackClientSecret"
|
||||||
|
);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (tb) => {
|
||||||
|
if (!doesSuperAdminHaveSlackClientId) {
|
||||||
|
tb.binary("encryptedSlackClientId");
|
||||||
|
}
|
||||||
|
if (!doesSuperAdminHaveSlackClientSecret) {
|
||||||
|
tb.binary("encryptedSlackClientSecret");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.ProjectSlackConfigs);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.ProjectSlackConfigs);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SlackIntegrations);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.SlackIntegrations);
|
||||||
|
|
||||||
|
await knex.schema.dropTableIfExists(TableName.WorkflowIntegrations);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.WorkflowIntegrations);
|
||||||
|
|
||||||
|
const doesSuperAdminHaveSlackClientId = await knex.schema.hasColumn(TableName.SuperAdmin, "encryptedSlackClientId");
|
||||||
|
const doesSuperAdminHaveSlackClientSecret = await knex.schema.hasColumn(
|
||||||
|
TableName.SuperAdmin,
|
||||||
|
"encryptedSlackClientSecret"
|
||||||
|
);
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.SuperAdmin, (tb) => {
|
||||||
|
if (doesSuperAdminHaveSlackClientId) {
|
||||||
|
tb.dropColumn("encryptedSlackClientId");
|
||||||
|
}
|
||||||
|
if (doesSuperAdminHaveSlackClientSecret) {
|
||||||
|
tb.dropColumn("encryptedSlackClientSecret");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||||
|
const hasRequireTemplateForIssuanceColumn = await knex.schema.hasColumn(
|
||||||
|
TableName.CertificateAuthority,
|
||||||
|
"requireTemplateForIssuance"
|
||||||
|
);
|
||||||
|
if (!hasRequireTemplateForIssuanceColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||||
|
t.boolean("requireTemplateForIssuance").notNullable().defaultTo(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasTable(TableName.CertificateAuthority)) {
|
||||||
|
await knex.schema.alterTable(TableName.CertificateAuthority, (t) => {
|
||||||
|
t.dropColumn("requireTemplateForIssuance");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,85 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
// Certificate template
|
||||||
|
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
||||||
|
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.CertificateTemplate, (tb) => {
|
||||||
|
if (!hasKeyUsagesCol) {
|
||||||
|
tb.specificType("keyUsages", "text[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasExtendedKeyUsagesCol) {
|
||||||
|
tb.specificType("extendedKeyUsages", "text[]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasKeyUsagesCol) {
|
||||||
|
await knex(TableName.CertificateTemplate).update({
|
||||||
|
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasExtendedKeyUsagesCol) {
|
||||||
|
await knex(TableName.CertificateTemplate).update({
|
||||||
|
extendedKeyUsages: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certificate
|
||||||
|
const doesCertTableHaveKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "keyUsages");
|
||||||
|
const doesCertTableHaveExtendedKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "extendedKeyUsages");
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (tb) => {
|
||||||
|
if (!doesCertTableHaveKeyUsages) {
|
||||||
|
tb.specificType("keyUsages", "text[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doesCertTableHaveExtendedKeyUsages) {
|
||||||
|
tb.specificType("extendedKeyUsages", "text[]");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!doesCertTableHaveKeyUsages) {
|
||||||
|
await knex(TableName.Certificate).update({
|
||||||
|
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doesCertTableHaveExtendedKeyUsages) {
|
||||||
|
await knex(TableName.Certificate).update({
|
||||||
|
extendedKeyUsages: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
// Certificate Template
|
||||||
|
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
||||||
|
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.CertificateTemplate, (t) => {
|
||||||
|
if (hasKeyUsagesCol) {
|
||||||
|
t.dropColumn("keyUsages");
|
||||||
|
}
|
||||||
|
if (hasExtendedKeyUsagesCol) {
|
||||||
|
t.dropColumn("extendedKeyUsages");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Certificate
|
||||||
|
const doesCertTableHaveKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "keyUsages");
|
||||||
|
const doesCertTableHaveExtendedKeyUsages = await knex.schema.hasColumn(TableName.Certificate, "extendedKeyUsages");
|
||||||
|
await knex.schema.alterTable(TableName.Certificate, (t) => {
|
||||||
|
if (doesCertTableHaveKeyUsages) {
|
||||||
|
t.dropColumn("keyUsages");
|
||||||
|
}
|
||||||
|
if (doesCertTableHaveExtendedKeyUsages) {
|
||||||
|
t.dropColumn("extendedKeyUsages");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -28,7 +28,8 @@ export const CertificateAuthoritiesSchema = z.object({
|
|||||||
keyAlgorithm: z.string(),
|
keyAlgorithm: z.string(),
|
||||||
notBefore: z.date().nullable().optional(),
|
notBefore: z.date().nullable().optional(),
|
||||||
notAfter: z.date().nullable().optional(),
|
notAfter: z.date().nullable().optional(),
|
||||||
activeCaCertId: z.string().uuid().nullable().optional()
|
activeCaCertId: z.string().uuid().nullable().optional(),
|
||||||
|
requireTemplateForIssuance: z.boolean().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
export type TCertificateAuthorities = z.infer<typeof CertificateAuthoritiesSchema>;
|
||||||
|
@@ -16,7 +16,9 @@ export const CertificateTemplatesSchema = z.object({
|
|||||||
subjectAlternativeName: z.string(),
|
subjectAlternativeName: z.string(),
|
||||||
ttl: z.string(),
|
ttl: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
|
extendedKeyUsages: z.string().array().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
||||||
|
@@ -22,7 +22,9 @@ export const CertificatesSchema = z.object({
|
|||||||
revocationReason: z.number().nullable().optional(),
|
revocationReason: z.number().nullable().optional(),
|
||||||
altNames: z.string().default("").nullable().optional(),
|
altNames: z.string().default("").nullable().optional(),
|
||||||
caCertId: z.string().uuid(),
|
caCertId: z.string().uuid(),
|
||||||
certificateTemplateId: z.string().uuid().nullable().optional()
|
certificateTemplateId: z.string().uuid().nullable().optional(),
|
||||||
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
|
extendedKeyUsages: z.string().array().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
export type TCertificates = z.infer<typeof CertificatesSchema>;
|
||||||
|
@@ -62,6 +62,7 @@ export * from "./project-environments";
|
|||||||
export * from "./project-keys";
|
export * from "./project-keys";
|
||||||
export * from "./project-memberships";
|
export * from "./project-memberships";
|
||||||
export * from "./project-roles";
|
export * from "./project-roles";
|
||||||
|
export * from "./project-slack-configs";
|
||||||
export * from "./project-user-additional-privilege";
|
export * from "./project-user-additional-privilege";
|
||||||
export * from "./project-user-membership-roles";
|
export * from "./project-user-membership-roles";
|
||||||
export * from "./projects";
|
export * from "./projects";
|
||||||
@@ -101,6 +102,7 @@ export * from "./secret-versions-v2";
|
|||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
export * from "./secrets-v2";
|
export * from "./secrets-v2";
|
||||||
export * from "./service-tokens";
|
export * from "./service-tokens";
|
||||||
|
export * from "./slack-integrations";
|
||||||
export * from "./super-admin";
|
export * from "./super-admin";
|
||||||
export * from "./trusted-ips";
|
export * from "./trusted-ips";
|
||||||
export * from "./user-actions";
|
export * from "./user-actions";
|
||||||
@@ -109,3 +111,4 @@ export * from "./user-encryption-keys";
|
|||||||
export * from "./user-group-membership";
|
export * from "./user-group-membership";
|
||||||
export * from "./users";
|
export * from "./users";
|
||||||
export * from "./webhooks";
|
export * from "./webhooks";
|
||||||
|
export * from "./workflow-integrations";
|
||||||
|
@@ -114,7 +114,10 @@ export enum TableName {
|
|||||||
InternalKms = "internal_kms",
|
InternalKms = "internal_kms",
|
||||||
InternalKmsKeyVersion = "internal_kms_key_version",
|
InternalKmsKeyVersion = "internal_kms_key_version",
|
||||||
// @depreciated
|
// @depreciated
|
||||||
KmsKeyVersion = "kms_key_versions"
|
KmsKeyVersion = "kms_key_versions",
|
||||||
|
WorkflowIntegrations = "workflow_integrations",
|
||||||
|
SlackIntegrations = "slack_integrations",
|
||||||
|
ProjectSlackConfigs = "project_slack_configs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
24
backend/src/db/schemas/project-slack-configs.ts
Normal file
24
backend/src/db/schemas/project-slack-configs.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// 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 ProjectSlackConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
projectId: z.string(),
|
||||||
|
slackIntegrationId: z.string().uuid(),
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
accessRequestChannels: z.string().default(""),
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean().default(false),
|
||||||
|
secretRequestChannels: z.string().default(""),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TProjectSlackConfigs = z.infer<typeof ProjectSlackConfigsSchema>;
|
||||||
|
export type TProjectSlackConfigsInsert = Omit<z.input<typeof ProjectSlackConfigsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TProjectSlackConfigsUpdate = Partial<Omit<z.input<typeof ProjectSlackConfigsSchema>, TImmutableDBKeys>>;
|
@@ -21,8 +21,8 @@ export const SecretSharingSchema = z.object({
|
|||||||
expiresAfterViews: z.number().nullable().optional(),
|
expiresAfterViews: z.number().nullable().optional(),
|
||||||
accessType: z.string().default("anyone"),
|
accessType: z.string().default("anyone"),
|
||||||
name: z.string().nullable().optional(),
|
name: z.string().nullable().optional(),
|
||||||
password: z.string().nullable().optional(),
|
lastViewedAt: z.date().nullable().optional(),
|
||||||
lastViewedAt: z.date().nullable().optional()
|
password: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
export type TSecretSharing = z.infer<typeof SecretSharingSchema>;
|
||||||
|
27
backend/src/db/schemas/slack-integrations.ts
Normal file
27
backend/src/db/schemas/slack-integrations.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// 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 { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const SlackIntegrationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
teamId: z.string(),
|
||||||
|
teamName: z.string(),
|
||||||
|
slackUserId: z.string(),
|
||||||
|
slackAppId: z.string(),
|
||||||
|
encryptedBotAccessToken: zodBuffer,
|
||||||
|
slackBotId: z.string(),
|
||||||
|
slackBotUserId: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TSlackIntegrations = z.infer<typeof SlackIntegrationsSchema>;
|
||||||
|
export type TSlackIntegrationsInsert = Omit<z.input<typeof SlackIntegrationsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TSlackIntegrationsUpdate = Partial<Omit<z.input<typeof SlackIntegrationsSchema>, TImmutableDBKeys>>;
|
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
import { TImmutableDBKeys } from "./models";
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
export const SuperAdminSchema = z.object({
|
export const SuperAdminSchema = z.object({
|
||||||
@@ -19,7 +21,9 @@ export const SuperAdminSchema = z.object({
|
|||||||
trustLdapEmails: z.boolean().default(false).nullable().optional(),
|
trustLdapEmails: z.boolean().default(false).nullable().optional(),
|
||||||
trustOidcEmails: z.boolean().default(false).nullable().optional(),
|
trustOidcEmails: z.boolean().default(false).nullable().optional(),
|
||||||
defaultAuthOrgId: z.string().uuid().nullable().optional(),
|
defaultAuthOrgId: z.string().uuid().nullable().optional(),
|
||||||
enabledLoginMethods: z.string().array().nullable().optional()
|
enabledLoginMethods: z.string().array().nullable().optional(),
|
||||||
|
encryptedSlackClientId: zodBuffer.nullable().optional(),
|
||||||
|
encryptedSlackClientSecret: zodBuffer.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
export type TSuperAdmin = z.infer<typeof SuperAdminSchema>;
|
||||||
|
22
backend/src/db/schemas/workflow-integrations.ts
Normal file
22
backend/src/db/schemas/workflow-integrations.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// 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 WorkflowIntegrationsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
integration: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
description: z.string().nullable().optional(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TWorkflowIntegrations = z.infer<typeof WorkflowIntegrationsSchema>;
|
||||||
|
export type TWorkflowIntegrationsInsert = Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TWorkflowIntegrationsUpdate = Partial<Omit<z.input<typeof WorkflowIntegrationsSchema>, TImmutableDBKeys>>;
|
@@ -11,6 +11,30 @@ export const registerCaCrlRouter = async (server: FastifyZodProvider) => {
|
|||||||
config: {
|
config: {
|
||||||
rateLimit: readLimit
|
rateLimit: readLimit
|
||||||
},
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get CRL in DER format (deprecated)",
|
||||||
|
params: z.object({
|
||||||
|
crlId: z.string().trim().describe(CA_CRLS.GET.crlId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.instanceof(Buffer)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const { crl } = await server.services.certificateAuthorityCrl.getCrlById(req.params.crlId);
|
||||||
|
|
||||||
|
res.header("Content-Type", "application/pkix-crl");
|
||||||
|
|
||||||
|
return Buffer.from(crl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:crlId/der",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
schema: {
|
schema: {
|
||||||
description: "Get CRL in DER format",
|
description: "Get CRL in DER format",
|
||||||
params: z.object({
|
params: z.object({
|
||||||
|
@@ -101,6 +101,7 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
message: "Slug must be a valid"
|
message: "Slug must be a valid"
|
||||||
}),
|
}),
|
||||||
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
name: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.name),
|
||||||
|
description: z.string().trim().optional().describe(PROJECT_ROLE.UPDATE.description),
|
||||||
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
permissions: ProjectPermissionSchema.array().describe(PROJECT_ROLE.UPDATE.permissions).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
@@ -122,6 +122,10 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
|
project: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
}),
|
||||||
event: z.object({
|
event: z.object({
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
metadata: z.any()
|
metadata: z.any()
|
||||||
@@ -138,7 +142,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const auditLogs = await server.services.auditLog.listProjectAuditLogs({
|
const auditLogs = await server.services.auditLog.listAuditLogs({
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
@@ -100,17 +100,34 @@ 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 = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
const email =
|
||||||
|
profile?.email ??
|
||||||
|
// entra sends data in this format
|
||||||
|
(profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email"] as string) ??
|
||||||
|
(profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved\
|
||||||
|
|
||||||
if (!email || !profile.firstName) {
|
const firstName = (profile.firstName ??
|
||||||
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
|
// entra sends data in this format
|
||||||
|
profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstName"]) as string;
|
||||||
|
|
||||||
|
const lastName =
|
||||||
|
profile.lastName ?? profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/lastName"];
|
||||||
|
|
||||||
|
if (!email || !firstName) {
|
||||||
|
logger.info(
|
||||||
|
{
|
||||||
|
err: new Error("Invalid saml request. Missing email or first name"),
|
||||||
|
profile
|
||||||
|
},
|
||||||
|
`email: ${email} firstName: ${profile.firstName as string}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
||||||
externalId: profile.nameID,
|
externalId: profile.nameID,
|
||||||
email,
|
email,
|
||||||
firstName: profile.firstName as string,
|
firstName,
|
||||||
lastName: profile.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
|
||||||
@@ -118,7 +135,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
cb(null, {});
|
cb(error as Error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {}
|
() => {}
|
||||||
|
@@ -5,9 +5,13 @@ 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, UnauthorizedError } 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 { 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 { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
|
import { triggerSlackNotification } from "@app/services/slack/slack-fns";
|
||||||
|
import { SlackTriggerFeature } from "@app/services/slack/slack-types";
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -33,7 +37,10 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
accessApprovalPolicyApproverDAL: Pick<TAccessApprovalPolicyApproverDALFactory, "find">;
|
accessApprovalPolicyApproverDAL: Pick<TAccessApprovalPolicyApproverDALFactory, "find">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "checkProjectUpgradeStatus" | "findProjectBySlug">;
|
projectDAL: Pick<
|
||||||
|
TProjectDALFactory,
|
||||||
|
"checkProjectUpgradeStatus" | "findProjectBySlug" | "findProjectWithOrg" | "findById"
|
||||||
|
>;
|
||||||
accessApprovalRequestDAL: Pick<
|
accessApprovalRequestDAL: Pick<
|
||||||
TAccessApprovalRequestDALFactory,
|
TAccessApprovalRequestDALFactory,
|
||||||
| "create"
|
| "create"
|
||||||
@@ -56,6 +63,8 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
TUserDALFactory,
|
TUserDALFactory,
|
||||||
"findUserByProjectMembershipId" | "findUsersByProjectMembershipIds" | "find" | "findById"
|
"findUserByProjectMembershipId" | "findUsersByProjectMembershipIds" | "find" | "findById"
|
||||||
>;
|
>;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
|
||||||
@@ -71,7 +80,9 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
accessApprovalPolicyApproverDAL,
|
accessApprovalPolicyApproverDAL,
|
||||||
additionalPrivilegeDAL,
|
additionalPrivilegeDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
userDAL
|
userDAL,
|
||||||
|
kmsService,
|
||||||
|
projectSlackConfigDAL
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const createAccessApprovalRequest = async ({
|
const createAccessApprovalRequest = async ({
|
||||||
isTemporary,
|
isTemporary,
|
||||||
@@ -166,13 +177,36 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const requesterFullName = `${requestedByUser.firstName} ${requestedByUser.lastName}`;
|
||||||
|
const approvalUrl = `${cfg.SITE_URL}/project/${project.id}/approval`;
|
||||||
|
|
||||||
|
await triggerSlackNotification({
|
||||||
|
projectId: project.id,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
notification: {
|
||||||
|
type: SlackTriggerFeature.ACCESS_REQUEST,
|
||||||
|
payload: {
|
||||||
|
projectName: project.name,
|
||||||
|
requesterFullName,
|
||||||
|
isTemporary,
|
||||||
|
requesterEmail: requestedByUser.email as string,
|
||||||
|
secretPath,
|
||||||
|
environment: envSlug,
|
||||||
|
permissions: accessTypes,
|
||||||
|
approvalUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
recipients: approverUsers.filter((approver) => approver.email).map((approver) => approver.email!),
|
recipients: approverUsers.filter((approver) => approver.email).map((approver) => approver.email!),
|
||||||
subjectLine: "Access Approval Request",
|
subjectLine: "Access Approval Request",
|
||||||
|
|
||||||
substitutions: {
|
substitutions: {
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
requesterFullName: `${requestedByUser.firstName} ${requestedByUser.lastName}`,
|
requesterFullName,
|
||||||
requesterEmail: requestedByUser.email,
|
requesterEmail: requestedByUser.email,
|
||||||
isTemporary,
|
isTemporary,
|
||||||
...(isTemporary && {
|
...(isTemporary && {
|
||||||
@@ -181,7 +215,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
secretPath,
|
secretPath,
|
||||||
environment: envSlug,
|
environment: envSlug,
|
||||||
permissions: accessTypes,
|
permissions: accessTypes,
|
||||||
approvalUrl: `${cfg.SITE_URL}/project/${project.id}/approval`
|
approvalUrl
|
||||||
},
|
},
|
||||||
template: SmtpTemplates.AccessApprovalRequest
|
template: SmtpTemplates.AccessApprovalRequest
|
||||||
});
|
});
|
||||||
|
@@ -2,10 +2,11 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import { RawAxiosRequestHeaders } from "axios";
|
import { RawAxiosRequestHeaders } from "axios";
|
||||||
|
|
||||||
import { SecretKeyEncoding } from "@app/db/schemas";
|
import { SecretKeyEncoding } from "@app/db/schemas";
|
||||||
|
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 } from "@app/lib/errors";
|
||||||
import { validateLocalIps } 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";
|
||||||
import { TLicenseServiceFactory } from "../license/license-service";
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
@@ -44,6 +45,7 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
}: TCreateAuditLogStreamDTO) => {
|
}: TCreateAuditLogStreamDTO) => {
|
||||||
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
|
if (!actorOrgId) throw new BadRequestError({ message: "Missing org id from token" });
|
||||||
|
|
||||||
|
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({
|
||||||
@@ -59,7 +61,9 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
validateLocalIps(url);
|
if (appCfg.isCloud) {
|
||||||
|
blockLocalAndPrivateIpAddresses(url);
|
||||||
|
}
|
||||||
|
|
||||||
const totalStreams = await auditLogStreamDAL.find({ orgId: actorOrgId });
|
const totalStreams = await auditLogStreamDAL.find({ orgId: actorOrgId });
|
||||||
if (totalStreams.length >= plan.auditLogStreamLimit) {
|
if (totalStreams.length >= plan.auditLogStreamLimit) {
|
||||||
@@ -131,7 +135,8 @@ export const auditLogStreamServiceFactory = ({
|
|||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
|
|
||||||
if (url) validateLocalIps(url);
|
const appCfg = getConfig();
|
||||||
|
if (url && appCfg.isCloud) blockLocalAndPrivateIpAddresses(url);
|
||||||
|
|
||||||
// testing connection first
|
// testing connection first
|
||||||
const streamHeaders: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
const streamHeaders: RawAxiosRequestHeaders = { "Content-Type": "application/json" };
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { AuditLogsSchema, TableName } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, stripUndefinedInWhere } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, stripUndefinedInWhere } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueName } from "@app/queue";
|
import { QueueName } from "@app/queue";
|
||||||
|
|
||||||
@@ -33,23 +33,44 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
.where(
|
.where(
|
||||||
stripUndefinedInWhere({
|
stripUndefinedInWhere({
|
||||||
projectId,
|
projectId,
|
||||||
orgId,
|
[`${TableName.AuditLog}.orgId`]: orgId,
|
||||||
eventType,
|
eventType,
|
||||||
actor,
|
|
||||||
userAgentType
|
userAgentType
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
.leftJoin(TableName.Project, `${TableName.AuditLog}.projectId`, `${TableName.Project}.id`)
|
||||||
|
|
||||||
|
.select(selectAllTableCols(TableName.AuditLog))
|
||||||
|
|
||||||
|
.select(
|
||||||
|
db.ref("name").withSchema(TableName.Project).as("projectName"),
|
||||||
|
db.ref("slug").withSchema(TableName.Project).as("projectSlug")
|
||||||
|
)
|
||||||
|
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.offset(offset)
|
.offset(offset)
|
||||||
.orderBy("createdAt", "desc");
|
.orderBy(`${TableName.AuditLog}.createdAt`, "desc");
|
||||||
|
|
||||||
|
if (actor) {
|
||||||
|
void sqlQuery.whereRaw(`"actorMetadata"->>'userId' = ?`, [actor]);
|
||||||
|
}
|
||||||
|
|
||||||
if (startDate) {
|
if (startDate) {
|
||||||
void sqlQuery.where("createdAt", ">=", startDate);
|
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, ">=", startDate);
|
||||||
}
|
}
|
||||||
if (endDate) {
|
if (endDate) {
|
||||||
void sqlQuery.where("createdAt", "<=", endDate);
|
void sqlQuery.where(`${TableName.AuditLog}.createdAt`, "<=", endDate);
|
||||||
}
|
}
|
||||||
const docs = await sqlQuery;
|
const docs = await sqlQuery;
|
||||||
return docs;
|
|
||||||
|
return docs.map((doc) => ({
|
||||||
|
...AuditLogsSchema.parse(doc),
|
||||||
|
project: {
|
||||||
|
name: doc.projectName,
|
||||||
|
slug: doc.projectSlug
|
||||||
|
}
|
||||||
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error });
|
throw new DatabaseError({ error });
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import { ForbiddenError } from "@casl/ability";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "../permission/permission-service";
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
|
||||||
import { TAuditLogDALFactory } from "./audit-log-dal";
|
import { TAuditLogDALFactory } from "./audit-log-dal";
|
||||||
@@ -11,7 +12,7 @@ import { EventType, TCreateAuditLogDTO, TListProjectAuditLogDTO } from "./audit-
|
|||||||
|
|
||||||
type TAuditLogServiceFactoryDep = {
|
type TAuditLogServiceFactoryDep = {
|
||||||
auditLogDAL: TAuditLogDALFactory;
|
auditLogDAL: TAuditLogDALFactory;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
||||||
auditLogQueue: TAuditLogQueueServiceFactory;
|
auditLogQueue: TAuditLogQueueServiceFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ export const auditLogServiceFactory = ({
|
|||||||
auditLogQueue,
|
auditLogQueue,
|
||||||
permissionService
|
permissionService
|
||||||
}: TAuditLogServiceFactoryDep) => {
|
}: TAuditLogServiceFactoryDep) => {
|
||||||
const listProjectAuditLogs = async ({
|
const listAuditLogs = async ({
|
||||||
userAgentType,
|
userAgentType,
|
||||||
eventType,
|
eventType,
|
||||||
offset,
|
offset,
|
||||||
@@ -36,14 +37,33 @@ export const auditLogServiceFactory = ({
|
|||||||
projectId,
|
projectId,
|
||||||
auditLogActor
|
auditLogActor
|
||||||
}: TListProjectAuditLogDTO) => {
|
}: TListProjectAuditLogDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
if (projectId) {
|
||||||
actor,
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actorId,
|
actor,
|
||||||
projectId,
|
actorId,
|
||||||
actorAuthMethod,
|
projectId,
|
||||||
actorOrgId
|
actorAuthMethod,
|
||||||
);
|
actorOrgId
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
||||||
|
} else {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE (dangtony98): Update this to organization-level audit log permission check once audit logs are moved
|
||||||
|
* to the organization level
|
||||||
|
*/
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If project ID is not provided, then we need to return all the audit logs for the organization itself.
|
||||||
|
|
||||||
const auditLogs = await auditLogDAL.find({
|
const auditLogs = await auditLogDAL.find({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
@@ -52,8 +72,9 @@ export const auditLogServiceFactory = ({
|
|||||||
eventType,
|
eventType,
|
||||||
userAgentType,
|
userAgentType,
|
||||||
actor: auditLogActor,
|
actor: auditLogActor,
|
||||||
projectId
|
...(projectId ? { projectId } : { orgId: actorOrgId })
|
||||||
});
|
});
|
||||||
|
|
||||||
return auditLogs.map(({ eventType: logEventType, actor: eActor, actorMetadata, eventMetadata, ...el }) => ({
|
return auditLogs.map(({ eventType: logEventType, actor: eActor, actorMetadata, eventMetadata, ...el }) => ({
|
||||||
...el,
|
...el,
|
||||||
event: { type: logEventType, metadata: eventMetadata },
|
event: { type: logEventType, metadata: eventMetadata },
|
||||||
@@ -76,6 +97,6 @@ export const auditLogServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
createAuditLog,
|
createAuditLog,
|
||||||
listProjectAuditLogs
|
listAuditLogs
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -6,14 +6,14 @@ import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
|
|||||||
|
|
||||||
export type TListProjectAuditLogDTO = {
|
export type TListProjectAuditLogDTO = {
|
||||||
auditLogActor?: string;
|
auditLogActor?: string;
|
||||||
projectId: string;
|
projectId?: string;
|
||||||
eventType?: string;
|
eventType?: string;
|
||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
userAgentType?: string;
|
userAgentType?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
} & TProjectPermission;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCreateAuditLogDTO = {
|
export type TCreateAuditLogDTO = {
|
||||||
event: Event;
|
event: Event;
|
||||||
@@ -140,6 +140,7 @@ export enum EventType {
|
|||||||
GET_CA_CRLS = "get-certificate-authority-crls",
|
GET_CA_CRLS = "get-certificate-authority-crls",
|
||||||
ISSUE_CERT = "issue-cert",
|
ISSUE_CERT = "issue-cert",
|
||||||
SIGN_CERT = "sign-cert",
|
SIGN_CERT = "sign-cert",
|
||||||
|
GET_CA_CERTIFICATE_TEMPLATES = "get-ca-certificate-templates",
|
||||||
GET_CERT = "get-cert",
|
GET_CERT = "get-cert",
|
||||||
DELETE_CERT = "delete-cert",
|
DELETE_CERT = "delete-cert",
|
||||||
REVOKE_CERT = "revoke-cert",
|
REVOKE_CERT = "revoke-cert",
|
||||||
@@ -169,7 +170,14 @@ export enum EventType {
|
|||||||
GET_CERTIFICATE_TEMPLATE = "get-certificate-template",
|
GET_CERTIFICATE_TEMPLATE = "get-certificate-template",
|
||||||
CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "create-certificate-template-est-config",
|
CREATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "create-certificate-template-est-config",
|
||||||
UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "update-certificate-template-est-config",
|
UPDATE_CERTIFICATE_TEMPLATE_EST_CONFIG = "update-certificate-template-est-config",
|
||||||
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config"
|
GET_CERTIFICATE_TEMPLATE_EST_CONFIG = "get-certificate-template-est-config",
|
||||||
|
ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration",
|
||||||
|
ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration",
|
||||||
|
GET_SLACK_INTEGRATION = "get-slack-integration",
|
||||||
|
UPDATE_SLACK_INTEGRATION = "update-slack-integration",
|
||||||
|
DELETE_SLACK_INTEGRATION = "delete-slack-integration",
|
||||||
|
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
||||||
|
UPDATE_PROJECT_SLACK_CONFIG = "update-project-slack-config"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActorMetadata {
|
interface UserActorMetadata {
|
||||||
@@ -1192,6 +1200,14 @@ interface SignCert {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetCaCertificateTemplates {
|
||||||
|
type: EventType.GET_CA_CERTIFICATE_TEMPLATES;
|
||||||
|
metadata: {
|
||||||
|
caId: string;
|
||||||
|
dn: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface GetCert {
|
interface GetCert {
|
||||||
type: EventType.GET_CERT;
|
type: EventType.GET_CERT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1446,6 +1462,63 @@ interface GetCertificateTemplateEstConfig {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface AttemptCreateSlackIntegration {
|
||||||
|
type: EventType.ATTEMPT_CREATE_SLACK_INTEGRATION;
|
||||||
|
metadata: {
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttemptReinstallSlackIntegration {
|
||||||
|
type: EventType.ATTEMPT_REINSTALL_SLACK_INTEGRATION;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateSlackIntegration {
|
||||||
|
type: EventType.UPDATE_SLACK_INTEGRATION;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
slug: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DeleteSlackIntegration {
|
||||||
|
type: EventType.DELETE_SLACK_INTEGRATION;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetSlackIntegration {
|
||||||
|
type: EventType.GET_SLACK_INTEGRATION;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateProjectSlackConfig {
|
||||||
|
type: EventType.UPDATE_PROJECT_SLACK_CONFIG;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
slackIntegrationId: string;
|
||||||
|
isAccessRequestNotificationEnabled: boolean;
|
||||||
|
accessRequestChannels: string;
|
||||||
|
isSecretRequestNotificationEnabled: boolean;
|
||||||
|
secretRequestChannels: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetProjectSlackConfig {
|
||||||
|
type: EventType.GET_PROJECT_SLACK_CONFIG;
|
||||||
|
metadata: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| GetSecretsEvent
|
| GetSecretsEvent
|
||||||
| GetSecretEvent
|
| GetSecretEvent
|
||||||
@@ -1547,6 +1620,7 @@ export type Event =
|
|||||||
| GetCaCrls
|
| GetCaCrls
|
||||||
| IssueCert
|
| IssueCert
|
||||||
| SignCert
|
| SignCert
|
||||||
|
| GetCaCertificateTemplates
|
||||||
| GetCert
|
| GetCert
|
||||||
| DeleteCert
|
| DeleteCert
|
||||||
| RevokeCert
|
| RevokeCert
|
||||||
@@ -1576,4 +1650,11 @@ export type Event =
|
|||||||
| DeleteCertificateTemplate
|
| DeleteCertificateTemplate
|
||||||
| CreateCertificateTemplateEstConfig
|
| CreateCertificateTemplateEstConfig
|
||||||
| UpdateCertificateTemplateEstConfig
|
| UpdateCertificateTemplateEstConfig
|
||||||
| GetCertificateTemplateEstConfig;
|
| GetCertificateTemplateEstConfig
|
||||||
|
| AttemptCreateSlackIntegration
|
||||||
|
| AttemptReinstallSlackIntegration
|
||||||
|
| UpdateSlackIntegration
|
||||||
|
| DeleteSlackIntegration
|
||||||
|
| GetSlackIntegration
|
||||||
|
| UpdateProjectSlackConfig
|
||||||
|
| GetProjectSlackConfig;
|
||||||
|
@@ -17,7 +17,7 @@ const generateUsername = () => {
|
|||||||
return alphaNumericNanoId(32);
|
return alphaNumericNanoId(32);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ElasticSearchDatabaseProvider = (): TDynamicProviderFns => {
|
export const ElasticSearchProvider = (): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
|
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import { AwsElastiCacheDatabaseProvider } from "./aws-elasticache";
|
import { AwsElastiCacheDatabaseProvider } from "./aws-elasticache";
|
||||||
import { AwsIamProvider } from "./aws-iam";
|
import { AwsIamProvider } from "./aws-iam";
|
||||||
import { CassandraProvider } from "./cassandra";
|
import { CassandraProvider } from "./cassandra";
|
||||||
import { ElasticSearchDatabaseProvider } from "./elastic-search";
|
import { ElasticSearchProvider } from "./elastic-search";
|
||||||
import { DynamicSecretProviders } from "./models";
|
import { DynamicSecretProviders } from "./models";
|
||||||
import { MongoAtlasProvider } from "./mongo-atlas";
|
import { MongoAtlasProvider } from "./mongo-atlas";
|
||||||
|
import { MongoDBProvider } from "./mongo-db";
|
||||||
|
import { RabbitMqProvider } from "./rabbit-mq";
|
||||||
import { RedisDatabaseProvider } from "./redis";
|
import { RedisDatabaseProvider } from "./redis";
|
||||||
import { SqlDatabaseProvider } from "./sql-database";
|
import { SqlDatabaseProvider } from "./sql-database";
|
||||||
|
|
||||||
@@ -14,5 +16,7 @@ export const buildDynamicSecretProviders = () => ({
|
|||||||
[DynamicSecretProviders.Redis]: RedisDatabaseProvider(),
|
[DynamicSecretProviders.Redis]: RedisDatabaseProvider(),
|
||||||
[DynamicSecretProviders.AwsElastiCache]: AwsElastiCacheDatabaseProvider(),
|
[DynamicSecretProviders.AwsElastiCache]: AwsElastiCacheDatabaseProvider(),
|
||||||
[DynamicSecretProviders.MongoAtlas]: MongoAtlasProvider(),
|
[DynamicSecretProviders.MongoAtlas]: MongoAtlasProvider(),
|
||||||
[DynamicSecretProviders.ElasticSearch]: ElasticSearchDatabaseProvider()
|
[DynamicSecretProviders.MongoDB]: MongoDBProvider(),
|
||||||
|
[DynamicSecretProviders.ElasticSearch]: ElasticSearchProvider(),
|
||||||
|
[DynamicSecretProviders.RabbitMq]: RabbitMqProvider()
|
||||||
});
|
});
|
||||||
|
@@ -17,7 +17,6 @@ export const DynamicSecretRedisDBSchema = z.object({
|
|||||||
port: z.number(),
|
port: z.number(),
|
||||||
username: z.string().trim(), // this is often "default".
|
username: z.string().trim(), // this is often "default".
|
||||||
password: z.string().trim().optional(),
|
password: z.string().trim().optional(),
|
||||||
|
|
||||||
creationStatement: z.string().trim(),
|
creationStatement: z.string().trim(),
|
||||||
revocationStatement: z.string().trim(),
|
revocationStatement: z.string().trim(),
|
||||||
renewStatement: z.string().trim().optional(),
|
renewStatement: z.string().trim().optional(),
|
||||||
@@ -57,6 +56,26 @@ export const DynamicSecretElasticSearchSchema = z.object({
|
|||||||
ca: z.string().optional()
|
ca: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DynamicSecretRabbitMqSchema = z.object({
|
||||||
|
host: z.string().trim().min(1),
|
||||||
|
port: z.number(),
|
||||||
|
tags: z.array(z.string().trim()).default([]),
|
||||||
|
|
||||||
|
username: z.string().trim().min(1),
|
||||||
|
password: z.string().trim().min(1),
|
||||||
|
|
||||||
|
ca: z.string().optional(),
|
||||||
|
|
||||||
|
virtualHost: z.object({
|
||||||
|
name: z.string().trim().min(1),
|
||||||
|
permissions: z.object({
|
||||||
|
read: z.string().trim().min(1),
|
||||||
|
write: z.string().trim().min(1),
|
||||||
|
configure: z.string().trim().min(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
export const DynamicSecretSqlDBSchema = z.object({
|
export const DynamicSecretSqlDBSchema = z.object({
|
||||||
client: z.nativeEnum(SqlProviders),
|
client: z.nativeEnum(SqlProviders),
|
||||||
host: z.string().trim().toLowerCase(),
|
host: z.string().trim().toLowerCase(),
|
||||||
@@ -131,6 +150,22 @@ export const DynamicSecretMongoAtlasSchema = z.object({
|
|||||||
.array()
|
.array()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const DynamicSecretMongoDBSchema = z.object({
|
||||||
|
host: z.string().min(1).trim().toLowerCase(),
|
||||||
|
port: z.number().optional(),
|
||||||
|
username: z.string().min(1).trim(),
|
||||||
|
password: z.string().min(1).trim(),
|
||||||
|
database: z.string().min(1).trim(),
|
||||||
|
ca: z.string().min(1).optional(),
|
||||||
|
roles: z
|
||||||
|
.string()
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.describe(
|
||||||
|
'Enum: "atlasAdmin" "backup" "clusterMonitor" "dbAdmin" "dbAdminAnyDatabase" "enableSharding" "read" "readAnyDatabase" "readWrite" "readWriteAnyDatabase" "<a custom role name>".Human-readable label that identifies a group of privileges assigned to a database user. This value can either be a built-in role or a custom role.'
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
export enum DynamicSecretProviders {
|
export enum DynamicSecretProviders {
|
||||||
SqlDatabase = "sql-database",
|
SqlDatabase = "sql-database",
|
||||||
Cassandra = "cassandra",
|
Cassandra = "cassandra",
|
||||||
@@ -138,7 +173,9 @@ export enum DynamicSecretProviders {
|
|||||||
Redis = "redis",
|
Redis = "redis",
|
||||||
AwsElastiCache = "aws-elasticache",
|
AwsElastiCache = "aws-elasticache",
|
||||||
MongoAtlas = "mongo-db-atlas",
|
MongoAtlas = "mongo-db-atlas",
|
||||||
ElasticSearch = "elastic-search"
|
ElasticSearch = "elastic-search",
|
||||||
|
MongoDB = "mongo-db",
|
||||||
|
RabbitMq = "rabbit-mq"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
||||||
@@ -148,7 +185,9 @@ export const DynamicSecretProviderSchema = z.discriminatedUnion("type", [
|
|||||||
z.object({ type: z.literal(DynamicSecretProviders.Redis), inputs: DynamicSecretRedisDBSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.Redis), inputs: DynamicSecretRedisDBSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.AwsElastiCache), inputs: DynamicSecretAwsElastiCacheSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }),
|
z.object({ type: z.literal(DynamicSecretProviders.MongoAtlas), inputs: DynamicSecretMongoAtlasSchema }),
|
||||||
z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema })
|
z.object({ type: z.literal(DynamicSecretProviders.ElasticSearch), inputs: DynamicSecretElasticSearchSchema }),
|
||||||
|
z.object({ type: z.literal(DynamicSecretProviders.MongoDB), inputs: DynamicSecretMongoDBSchema }),
|
||||||
|
z.object({ type: z.literal(DynamicSecretProviders.RabbitMq), inputs: DynamicSecretRabbitMqSchema })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type TDynamicProviderFns = {
|
export type TDynamicProviderFns = {
|
||||||
|
116
backend/src/ee/services/dynamic-secret/providers/mongo-db.ts
Normal file
116
backend/src/ee/services/dynamic-secret/providers/mongo-db.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { MongoClient } from "mongodb";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { DynamicSecretMongoDBSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
const generatePassword = (size = 48) => {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||||
|
return customAlphabet(charset, 48)(size);
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUsername = () => {
|
||||||
|
return alphaNumericNanoId(32);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MongoDBProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const providerInputs = await DynamicSecretMongoDBSchema.parseAsync(inputs);
|
||||||
|
if (
|
||||||
|
appCfg.isCloud &&
|
||||||
|
// localhost
|
||||||
|
// internal ips
|
||||||
|
(providerInputs.host === "host.docker.internal" ||
|
||||||
|
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||||
|
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
|
||||||
|
)
|
||||||
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
|
|
||||||
|
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
|
||||||
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = async (providerInputs: z.infer<typeof DynamicSecretMongoDBSchema>) => {
|
||||||
|
const isSrv = !providerInputs.port;
|
||||||
|
const uri = isSrv
|
||||||
|
? `mongodb+srv://${providerInputs.host}`
|
||||||
|
: `mongodb://${providerInputs.host}:${providerInputs.port}`;
|
||||||
|
|
||||||
|
const client = new MongoClient(uri, {
|
||||||
|
auth: {
|
||||||
|
username: providerInputs.username,
|
||||||
|
password: providerInputs.password
|
||||||
|
},
|
||||||
|
directConnection: !isSrv,
|
||||||
|
ca: providerInputs.ca
|
||||||
|
});
|
||||||
|
return client;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const isConnected = await client
|
||||||
|
.db(providerInputs.database)
|
||||||
|
.command({ ping: 1 })
|
||||||
|
.then(() => true);
|
||||||
|
|
||||||
|
await client.close();
|
||||||
|
return isConnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = generateUsername();
|
||||||
|
const password = generatePassword();
|
||||||
|
|
||||||
|
const db = client.db(providerInputs.database);
|
||||||
|
|
||||||
|
await db.command({
|
||||||
|
createUser: username,
|
||||||
|
pwd: password,
|
||||||
|
roles: providerInputs.roles
|
||||||
|
});
|
||||||
|
await client.close();
|
||||||
|
|
||||||
|
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (inputs: unknown, entityId: string) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const client = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = entityId;
|
||||||
|
|
||||||
|
const db = client.db(providerInputs.database);
|
||||||
|
await db.command({
|
||||||
|
dropUser: username
|
||||||
|
});
|
||||||
|
await client.close();
|
||||||
|
|
||||||
|
return { entityId: username };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (_inputs: unknown, entityId: string) => {
|
||||||
|
return { entityId };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
172
backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts
Normal file
172
backend/src/ee/services/dynamic-secret/providers/rabbit-mq.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import axios, { Axios } from "axios";
|
||||||
|
import https from "https";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { removeTrailingSlash } from "@app/lib/fn";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
|
import { DynamicSecretRabbitMqSchema, TDynamicProviderFns } from "./models";
|
||||||
|
|
||||||
|
const generatePassword = () => {
|
||||||
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!*$#";
|
||||||
|
return customAlphabet(charset, 64)();
|
||||||
|
};
|
||||||
|
|
||||||
|
const generateUsername = () => {
|
||||||
|
return alphaNumericNanoId(32);
|
||||||
|
};
|
||||||
|
|
||||||
|
type TCreateRabbitMQUser = {
|
||||||
|
axiosInstance: Axios;
|
||||||
|
createUser: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
tags: string[];
|
||||||
|
};
|
||||||
|
virtualHost: {
|
||||||
|
name: string;
|
||||||
|
permissions: {
|
||||||
|
read: string;
|
||||||
|
write: string;
|
||||||
|
configure: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TDeleteRabbitMqUser = {
|
||||||
|
axiosInstance: Axios;
|
||||||
|
usernameToDelete: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createRabbitMqUser({ axiosInstance, createUser, virtualHost }: TCreateRabbitMQUser): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Create user
|
||||||
|
const userUrl = `/users/${createUser.username}`;
|
||||||
|
const userData = {
|
||||||
|
password: createUser.password,
|
||||||
|
tags: createUser.tags.join(",")
|
||||||
|
};
|
||||||
|
|
||||||
|
await axiosInstance.put(userUrl, userData);
|
||||||
|
|
||||||
|
// Set permissions for the virtual host
|
||||||
|
if (virtualHost) {
|
||||||
|
const permissionData = {
|
||||||
|
configure: virtualHost.permissions.configure,
|
||||||
|
write: virtualHost.permissions.write,
|
||||||
|
read: virtualHost.permissions.read
|
||||||
|
};
|
||||||
|
|
||||||
|
await axiosInstance.put(
|
||||||
|
`/permissions/${encodeURIComponent(virtualHost.name)}/${createUser.username}`,
|
||||||
|
permissionData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error, "Error creating RabbitMQ user");
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteRabbitMqUser({ axiosInstance, usernameToDelete }: TDeleteRabbitMqUser) {
|
||||||
|
await axiosInstance.delete(`users/${usernameToDelete}`);
|
||||||
|
return { username: usernameToDelete };
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RabbitMqProvider = (): TDynamicProviderFns => {
|
||||||
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const isCloud = Boolean(appCfg.LICENSE_SERVER_KEY); // quick and dirty way to check if its cloud or not
|
||||||
|
|
||||||
|
const providerInputs = await DynamicSecretRabbitMqSchema.parseAsync(inputs);
|
||||||
|
if (
|
||||||
|
isCloud &&
|
||||||
|
// localhost
|
||||||
|
// internal ips
|
||||||
|
(providerInputs.host === "host.docker.internal" ||
|
||||||
|
providerInputs.host.match(/^10\.\d+\.\d+\.\d+/) ||
|
||||||
|
providerInputs.host.match(/^192\.168\.\d+\.\d+/))
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
|
}
|
||||||
|
if (providerInputs.host === "localhost" || providerInputs.host === "127.0.0.1") {
|
||||||
|
throw new BadRequestError({ message: "Invalid db host" });
|
||||||
|
}
|
||||||
|
|
||||||
|
return providerInputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getClient = async (providerInputs: z.infer<typeof DynamicSecretRabbitMqSchema>) => {
|
||||||
|
const axiosInstance = axios.create({
|
||||||
|
baseURL: `${removeTrailingSlash(providerInputs.host)}:${providerInputs.port}/api`,
|
||||||
|
auth: {
|
||||||
|
username: providerInputs.username,
|
||||||
|
password: providerInputs.password
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
|
||||||
|
...(providerInputs.ca && {
|
||||||
|
httpsAgent: new https.Agent({ ca: providerInputs.ca, rejectUnauthorized: false })
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return axiosInstance;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateConnection = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const connection = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const infoResponse = await connection.get("/whoami").then(() => true);
|
||||||
|
|
||||||
|
return infoResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = async (inputs: unknown) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const connection = await getClient(providerInputs);
|
||||||
|
|
||||||
|
const username = generateUsername();
|
||||||
|
const password = generatePassword();
|
||||||
|
|
||||||
|
await createRabbitMqUser({
|
||||||
|
axiosInstance: connection,
|
||||||
|
virtualHost: providerInputs.virtualHost,
|
||||||
|
createUser: {
|
||||||
|
password,
|
||||||
|
username,
|
||||||
|
tags: [...(providerInputs.tags ?? []), "infisical-user"]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { entityId: username, data: { DB_USERNAME: username, DB_PASSWORD: password } };
|
||||||
|
};
|
||||||
|
|
||||||
|
const revoke = async (inputs: unknown, entityId: string) => {
|
||||||
|
const providerInputs = await validateProviderInputs(inputs);
|
||||||
|
const connection = await getClient(providerInputs);
|
||||||
|
|
||||||
|
await deleteRabbitMqUser({ axiosInstance: connection, usernameToDelete: entityId });
|
||||||
|
|
||||||
|
return { entityId };
|
||||||
|
};
|
||||||
|
|
||||||
|
const renew = async (inputs: unknown, entityId: string) => {
|
||||||
|
// Do nothing
|
||||||
|
return { entityId };
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
validateProviderInputs,
|
||||||
|
validateConnection,
|
||||||
|
create,
|
||||||
|
revoke,
|
||||||
|
renew
|
||||||
|
};
|
||||||
|
};
|
@@ -41,10 +41,9 @@ export const userGroupMembershipDALFactory = (db: TDbClient) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// special query
|
// special query
|
||||||
const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string) => {
|
const findUserGroupMembershipsInProject = async (usernames: string[], projectId: string, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const usernameDocs: string[] = await db
|
const usernameDocs: string[] = await (tx || db.replicaNode())(TableName.UserGroupMembership)
|
||||||
.replicaNode()(TableName.UserGroupMembership)
|
|
||||||
.join(
|
.join(
|
||||||
TableName.GroupProjectMembership,
|
TableName.GroupProjectMembership,
|
||||||
`${TableName.UserGroupMembership}.groupId`,
|
`${TableName.UserGroupMembership}.groupId`,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
import { AbilityBuilder, createMongoAbility, ForcedSubject, MongoAbility } from "@casl/ability";
|
||||||
|
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
export enum ProjectPermissionActions {
|
export enum ProjectPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
@@ -75,117 +76,125 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
| [ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback]
|
||||||
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
| [ProjectPermissionActions.Edit, ProjectPermissionSub.Kms];
|
||||||
|
|
||||||
|
export const fullProjectPermissionSet: [ProjectPermissionActions, ProjectPermissionSub][] = [
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Secrets],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Secrets],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Member],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Member],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Member],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Member],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Groups],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Groups],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Groups],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Groups],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Role],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Role],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Role],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Role],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Integrations],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Integrations],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Identity],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Identity],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Identity],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Identity],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Settings],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Settings],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Settings],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Settings],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Environments],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Environments],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Environments],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Environments],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Tags],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Tags],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Tags],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Tags],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList],
|
||||||
|
|
||||||
|
// double check if all CRUD are needed for CA and Certificates
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateAuthorities],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateAuthorities],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateAuthorities],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.Certificates],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.Certificates],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections],
|
||||||
|
[ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections],
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Project],
|
||||||
|
[ProjectPermissionActions.Delete, ProjectPermissionSub.Project],
|
||||||
|
|
||||||
|
[ProjectPermissionActions.Edit, ProjectPermissionSub.Kms]
|
||||||
|
];
|
||||||
|
|
||||||
const buildAdminPermissionRules = () => {
|
const buildAdminPermissionRules = () => {
|
||||||
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
const { can, rules } = new AbilityBuilder<MongoAbility<ProjectPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets);
|
// Admins get full access to everything
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Secrets);
|
fullProjectPermissionSet.forEach((permission) => {
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Secrets);
|
const [action, subject] = permission;
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets);
|
can(action, subject);
|
||||||
|
});
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Member);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Member);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Groups);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Groups);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Groups);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Groups);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Role);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Role);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Role);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Integrations);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Identity);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Identity);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Identity);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.ServiceTokens);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Settings);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Settings);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Environments);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Environments);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Environments);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Environments);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Tags);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Tags);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.AuditLogs);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.AuditLogs);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.AuditLogs);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList);
|
|
||||||
|
|
||||||
// double check if all CRUD are needed for CA and Certificates
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.CertificateAuthorities);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateAuthorities);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateAuthorities);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Certificates);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.CertificateTemplates);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.CertificateTemplates);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.CertificateTemplates);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiAlerts);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiAlerts);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiAlerts);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections);
|
|
||||||
can(ProjectPermissionActions.Create, ProjectPermissionSub.PkiCollections);
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.PkiCollections);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Project);
|
|
||||||
can(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
|
||||||
|
|
||||||
can(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
|
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
@@ -372,4 +381,31 @@ export const isAtLeastAsPrivilegedWorkspace = (
|
|||||||
return set1.size >= set2.size;
|
return set1.size >= set2.size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Case: The user requests to create a role with permissions that are not valid and not supposed to be used ever.
|
||||||
|
* 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.
|
||||||
|
* If the permission set contains invalid permissions, the size will be different, and result in incorrect results.
|
||||||
|
*/
|
||||||
|
export const validateProjectPermissions = (permissions: unknown) => {
|
||||||
|
const parsedPermissions =
|
||||||
|
typeof permissions === "string" ? (JSON.parse(permissions) as string[]) : (permissions as string[]);
|
||||||
|
|
||||||
|
const flattenedPermissions = [...parsedPermissions];
|
||||||
|
|
||||||
|
for (const perm of flattenedPermissions) {
|
||||||
|
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 */
|
/* eslint-enable */
|
||||||
|
@@ -47,6 +47,9 @@ import {
|
|||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "@app/services/secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
import { TProjectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
|
import { triggerSlackNotification } from "@app/services/slack/slack-fns";
|
||||||
|
import { SlackTriggerFeature } from "@app/services/slack/slack-types";
|
||||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "@app/services/user/user-dal";
|
import { TUserDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
@@ -89,7 +92,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
secretVersionDAL: Pick<TSecretVersionDALFactory, "findLatestVersionMany" | "insertMany">;
|
||||||
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
secretVersionTagDAL: Pick<TSecretVersionTagDALFactory, "insertMany">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
userDAL: Pick<TUserDALFactory, "find" | "findOne">;
|
userDAL: Pick<TUserDALFactory, "find" | "findOne" | "findById">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "findOne">;
|
||||||
projectDAL: Pick<
|
projectDAL: Pick<
|
||||||
TProjectDALFactory,
|
TProjectDALFactory,
|
||||||
@@ -104,6 +107,7 @@ type TSecretApprovalRequestServiceFactoryDep = {
|
|||||||
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
secretVersionV2BridgeDAL: Pick<TSecretVersionV2DALFactory, "insertMany" | "findLatestVersionMany">;
|
||||||
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
secretVersionTagV2BridgeDAL: Pick<TSecretVersionV2TagDALFactory, "insertMany">;
|
||||||
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findById">;
|
secretApprovalPolicyDAL: Pick<TSecretApprovalPolicyDALFactory, "findById">;
|
||||||
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "getIntegrationDetailsByProject">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -132,7 +136,8 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
secretV2BridgeDAL,
|
secretV2BridgeDAL,
|
||||||
secretVersionV2BridgeDAL,
|
secretVersionV2BridgeDAL,
|
||||||
secretVersionTagV2BridgeDAL,
|
secretVersionTagV2BridgeDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
projectSlackConfigDAL
|
||||||
}: TSecretApprovalRequestServiceFactoryDep) => {
|
}: TSecretApprovalRequestServiceFactoryDep) => {
|
||||||
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
const requestCount = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod }: TApprovalRequestCountDTO) => {
|
||||||
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" });
|
||||||
@@ -1069,6 +1074,25 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
return { ...doc, commits: approvalCommits };
|
return { ...doc, commits: approvalCommits };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
|
await triggerSlackNotification({
|
||||||
|
projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
notification: {
|
||||||
|
type: SlackTriggerFeature.SECRET_APPROVAL,
|
||||||
|
payload: {
|
||||||
|
userEmail: user.email as string,
|
||||||
|
environment: env.name,
|
||||||
|
secretPath,
|
||||||
|
projectId,
|
||||||
|
requestId: secretApprovalRequest.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await sendApprovalEmailsFn({
|
await sendApprovalEmailsFn({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
@@ -1331,6 +1355,25 @@ export const secretApprovalRequestServiceFactory = ({
|
|||||||
return { ...doc, commits: approvalCommits };
|
return { ...doc, commits: approvalCommits };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const user = await userDAL.findById(secretApprovalRequest.committerUserId);
|
||||||
|
const env = await projectEnvDAL.findOne({ id: policy.envId });
|
||||||
|
await triggerSlackNotification({
|
||||||
|
projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
notification: {
|
||||||
|
type: SlackTriggerFeature.SECRET_APPROVAL,
|
||||||
|
payload: {
|
||||||
|
userEmail: user.email as string,
|
||||||
|
environment: env.name,
|
||||||
|
secretPath,
|
||||||
|
projectId,
|
||||||
|
requestId: secretApprovalRequest.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
await sendApprovalEmailsFn({
|
await sendApprovalEmailsFn({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
secretApprovalPolicyDAL,
|
secretApprovalPolicyDAL,
|
||||||
|
@@ -363,7 +363,12 @@ export const ORGANIZATIONS = {
|
|||||||
membershipId: "The ID of the membership to delete."
|
membershipId: "The ID of the membership to delete."
|
||||||
},
|
},
|
||||||
LIST_IDENTITY_MEMBERSHIPS: {
|
LIST_IDENTITY_MEMBERSHIPS: {
|
||||||
orgId: "The ID of the organization to get identity memberships from."
|
orgId: "The ID of the organization to get identity memberships from.",
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th identity membership.",
|
||||||
|
limit: "The number of identity memberships to return.",
|
||||||
|
orderBy: "The column to order identity memberships by.",
|
||||||
|
orderDirection: "The direction identity memberships will be sorted in.",
|
||||||
|
search: "The text string that identity membership names will be filtered by."
|
||||||
},
|
},
|
||||||
GET_PROJECTS: {
|
GET_PROJECTS: {
|
||||||
organizationId: "The ID of the organization to get projects from."
|
organizationId: "The ID of the organization to get projects from."
|
||||||
@@ -447,7 +452,9 @@ export const PROJECT_USERS = {
|
|||||||
INVITE_MEMBER: {
|
INVITE_MEMBER: {
|
||||||
projectId: "The ID of the project to invite the member to.",
|
projectId: "The ID of the project to invite the member to.",
|
||||||
emails: "A list of organization member emails to invite to the project.",
|
emails: "A list of organization member emails to invite to the project.",
|
||||||
usernames: "A list of usernames to invite to the project."
|
usernames: "A list of usernames to invite to the project.",
|
||||||
|
roleSlugs:
|
||||||
|
"A list of role slugs to assign to the newly created project membership. If nothing is provided, it will default to the Member role."
|
||||||
},
|
},
|
||||||
REMOVE_MEMBER: {
|
REMOVE_MEMBER: {
|
||||||
projectId: "The ID of the project to remove the member from.",
|
projectId: "The ID of the project to remove the member from.",
|
||||||
@@ -470,7 +477,12 @@ export const PROJECT_USERS = {
|
|||||||
|
|
||||||
export const PROJECT_IDENTITIES = {
|
export const PROJECT_IDENTITIES = {
|
||||||
LIST_IDENTITY_MEMBERSHIPS: {
|
LIST_IDENTITY_MEMBERSHIPS: {
|
||||||
projectId: "The ID of the project to get identity memberships from."
|
projectId: "The ID of the project to get identity memberships from.",
|
||||||
|
offset: "The offset to start from. If you enter 10, it will start from the 10th identity membership.",
|
||||||
|
limit: "The number of identity memberships to return.",
|
||||||
|
orderBy: "The column to order identity memberships by.",
|
||||||
|
orderDirection: "The direction identity memberships will be sorted in.",
|
||||||
|
search: "The text string that identity membership names will be filtered by."
|
||||||
},
|
},
|
||||||
GET_IDENTITY_MEMBERSHIP_BY_ID: {
|
GET_IDENTITY_MEMBERSHIP_BY_ID: {
|
||||||
identityId: "The ID of the identity to get the membership for.",
|
identityId: "The ID of the identity to get the membership for.",
|
||||||
@@ -1037,14 +1049,18 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
maxPathLength:
|
maxPathLength:
|
||||||
"The maximum number of intermediate CAs that may follow this CA in the certificate / CA chain. A maxPathLength of -1 implies no path limit on the chain.",
|
"The maximum number of intermediate CAs that may follow this CA in the certificate / CA chain. A maxPathLength of -1 implies no path limit on the chain.",
|
||||||
keyAlgorithm:
|
keyAlgorithm:
|
||||||
"The type of public key algorithm and size, in bits, of the key pair for the CA; when you create an intermediate CA, you must use a key algorithm supported by the parent CA."
|
"The type of public key algorithm and size, in bits, of the key pair for the CA; when you create an intermediate CA, you must use a key algorithm supported by the parent CA.",
|
||||||
|
requireTemplateForIssuance:
|
||||||
|
"Whether or not certificates for this CA can only be issued through certificate templates."
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
caId: "The ID of the CA to get"
|
caId: "The ID of the CA to get"
|
||||||
},
|
},
|
||||||
UPDATE: {
|
UPDATE: {
|
||||||
caId: "The ID of the CA to update",
|
caId: "The ID of the CA to update",
|
||||||
status: "The status of the CA to update to. This can be one of active or disabled"
|
status: "The status of the CA to update to. This can be one of active or disabled",
|
||||||
|
requireTemplateForIssuance:
|
||||||
|
"Whether or not certificates for this CA can only be issued through certificate templates."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
caId: "The ID of the CA to delete"
|
caId: "The ID of the CA to delete"
|
||||||
@@ -1067,6 +1083,10 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
certificateChain: "The certificate chain of the CA",
|
certificateChain: "The certificate chain of the CA",
|
||||||
serialNumber: "The serial number of the CA certificate"
|
serialNumber: "The serial number of the CA certificate"
|
||||||
},
|
},
|
||||||
|
GET_CERT_BY_ID: {
|
||||||
|
caId: "The ID of the CA to get the CA certificate from",
|
||||||
|
caCertId: "The ID of the CA certificate to get"
|
||||||
|
},
|
||||||
GET_CA_CERTS: {
|
GET_CA_CERTS: {
|
||||||
caId: "The ID of the CA to get the CA certificates for",
|
caId: "The ID of the CA to get the CA certificates for",
|
||||||
certificate: "The certificate body of the CA certificate",
|
certificate: "The certificate body of the CA certificate",
|
||||||
@@ -1106,11 +1126,15 @@ export const CERTIFICATE_AUTHORITIES = {
|
|||||||
issuingCaCertificate: "The certificate of the issuing CA",
|
issuingCaCertificate: "The certificate of the issuing CA",
|
||||||
certificateChain: "The certificate chain of the issued certificate",
|
certificateChain: "The certificate chain of the issued certificate",
|
||||||
privateKey: "The private key of the issued certificate",
|
privateKey: "The private key of the issued certificate",
|
||||||
serialNumber: "The serial number of the issued certificate"
|
serialNumber: "The serial number of the issued certificate",
|
||||||
|
keyUsages: "The key usage extension of the certificate",
|
||||||
|
extendedKeyUsages: "The extended key usage extension of the certificate"
|
||||||
},
|
},
|
||||||
SIGN_CERT: {
|
SIGN_CERT: {
|
||||||
caId: "The ID of the CA to issue the certificate from",
|
caId: "The ID of the CA to issue the certificate from",
|
||||||
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
pkiCollectionId: "The ID of the PKI collection to add the certificate to",
|
||||||
|
keyUsages: "The key usage extension of the certificate",
|
||||||
|
extendedKeyUsages: "The extended key usage extension of the certificate",
|
||||||
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance",
|
||||||
friendlyName: "A friendly name for the certificate",
|
friendlyName: "A friendly name for the certificate",
|
||||||
commonName: "The common name (CN) for the certificate",
|
commonName: "The common name (CN) for the certificate",
|
||||||
@@ -1160,7 +1184,10 @@ export const CERTIFICATE_TEMPLATES = {
|
|||||||
name: "The name of the template",
|
name: "The name of the template",
|
||||||
commonName: "The regular expression string to use for validating common names",
|
commonName: "The regular expression string to use for validating common names",
|
||||||
subjectAlternativeName: "The regular expression string to use for validating subject alternative names",
|
subjectAlternativeName: "The regular expression string to use for validating subject alternative names",
|
||||||
ttl: "The max TTL for the template"
|
ttl: "The max TTL for the template",
|
||||||
|
keyUsages: "The key usage constraint or default value for when template is used during certificate issuance",
|
||||||
|
extendedKeyUsages:
|
||||||
|
"The extended key usage constraint or default value for when template is used during certificate issuance"
|
||||||
},
|
},
|
||||||
GET: {
|
GET: {
|
||||||
certificateTemplateId: "The ID of the certificate template to get"
|
certificateTemplateId: "The ID of the certificate template to get"
|
||||||
@@ -1172,7 +1199,11 @@ export const CERTIFICATE_TEMPLATES = {
|
|||||||
name: "The updated name of the template",
|
name: "The updated name of the template",
|
||||||
commonName: "The updated regular expression string for validating common names",
|
commonName: "The updated regular expression string for validating common names",
|
||||||
subjectAlternativeName: "The updated regular expression string for validating subject alternative names",
|
subjectAlternativeName: "The updated regular expression string for validating subject alternative names",
|
||||||
ttl: "The updated max TTL for the template"
|
ttl: "The updated max TTL for the template",
|
||||||
|
keyUsages:
|
||||||
|
"The updated key usage constraint or default value for when template is used during certificate issuance",
|
||||||
|
extendedKeyUsages:
|
||||||
|
"The updated extended key usage constraint or default value for when template is used during certificate issuance"
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
certificateTemplateId: "The ID of the certificate template to delete"
|
certificateTemplateId: "The ID of the certificate template to delete"
|
||||||
|
@@ -146,7 +146,9 @@ const envSchema = z
|
|||||||
PLAIN_API_KEY: zpStr(z.string().optional()),
|
PLAIN_API_KEY: zpStr(z.string().optional()),
|
||||||
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional()),
|
PLAIN_WISH_LABEL_IDS: zpStr(z.string().optional()),
|
||||||
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false"),
|
DISABLE_AUDIT_LOG_GENERATION: zodStrBool.default("false"),
|
||||||
SSL_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default("x-ssl-client-cert")
|
SSL_CLIENT_CERTIFICATE_HEADER_KEY: zpStr(z.string().optional()).default("x-ssl-client-cert"),
|
||||||
|
WORKFLOW_SLACK_CLIENT_ID: zpStr(z.string().optional()),
|
||||||
|
WORKFLOW_SLACK_CLIENT_SECRET: zpStr(z.string().optional())
|
||||||
})
|
})
|
||||||
.transform((data) => ({
|
.transform((data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
@@ -5,6 +5,7 @@ import nacl from "tweetnacl";
|
|||||||
import tweetnacl from "tweetnacl-util";
|
import tweetnacl from "tweetnacl-util";
|
||||||
|
|
||||||
import { TUserEncryptionKeys } from "@app/db/schemas";
|
import { TUserEncryptionKeys } from "@app/db/schemas";
|
||||||
|
import { UserEncryption } from "@app/services/user/user-types";
|
||||||
|
|
||||||
import { decryptSymmetric128BitHexKeyUTF8, encryptAsymmetric, encryptSymmetric } from "./encryption";
|
import { decryptSymmetric128BitHexKeyUTF8, encryptAsymmetric, encryptSymmetric } from "./encryption";
|
||||||
|
|
||||||
@@ -36,12 +37,16 @@ export const srpCheckClientProof = async (
|
|||||||
// Ghost user related:
|
// Ghost user related:
|
||||||
// This functionality is intended for ghost user logic. This happens on the frontend when a user is being created.
|
// This functionality is intended for ghost user logic. This happens on the frontend when a user is being created.
|
||||||
// We replicate the same functionality on the backend when creating a ghost user.
|
// We replicate the same functionality on the backend when creating a ghost user.
|
||||||
export const generateUserSrpKeys = async (email: string, password: string) => {
|
export const generateUserSrpKeys = async (
|
||||||
|
email: string,
|
||||||
|
password: string,
|
||||||
|
customKeys?: { publicKey: string; privateKey: string }
|
||||||
|
) => {
|
||||||
const pair = nacl.box.keyPair();
|
const pair = nacl.box.keyPair();
|
||||||
const secretKeyUint8Array = pair.secretKey;
|
const secretKeyUint8Array = pair.secretKey;
|
||||||
const publicKeyUint8Array = pair.publicKey;
|
const publicKeyUint8Array = pair.publicKey;
|
||||||
const privateKey = tweetnacl.encodeBase64(secretKeyUint8Array);
|
const privateKey = customKeys?.privateKey || tweetnacl.encodeBase64(secretKeyUint8Array);
|
||||||
const publicKey = tweetnacl.encodeBase64(publicKeyUint8Array);
|
const publicKey = customKeys?.publicKey || tweetnacl.encodeBase64(publicKeyUint8Array);
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const client = new jsrp.client();
|
const client = new jsrp.client();
|
||||||
@@ -111,7 +116,7 @@ export const getUserPrivateKey = async (
|
|||||||
| "encryptionVersion"
|
| "encryptionVersion"
|
||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
if (user.encryptionVersion === 1) {
|
if (user.encryptionVersion === UserEncryption.V1) {
|
||||||
return decryptSymmetric128BitHexKeyUTF8({
|
return decryptSymmetric128BitHexKeyUTF8({
|
||||||
ciphertext: user.encryptedPrivateKey,
|
ciphertext: user.encryptedPrivateKey,
|
||||||
iv: user.iv,
|
iv: user.iv,
|
||||||
@@ -119,7 +124,12 @@ export const getUserPrivateKey = async (
|
|||||||
key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
|
key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (user.encryptionVersion === 2 && user.protectedKey && user.protectedKeyIV && user.protectedKeyTag) {
|
if (
|
||||||
|
user.encryptionVersion === UserEncryption.V2 &&
|
||||||
|
user.protectedKey &&
|
||||||
|
user.protectedKeyIV &&
|
||||||
|
user.protectedKeyTag
|
||||||
|
) {
|
||||||
const derivedKey = await argon2.hash(password, {
|
const derivedKey = await argon2.hash(password, {
|
||||||
salt: Buffer.from(user.salt),
|
salt: Buffer.from(user.salt),
|
||||||
memoryCost: 65536,
|
memoryCost: 65536,
|
||||||
|
@@ -52,3 +52,8 @@ export enum SecretSharingAccessType {
|
|||||||
Anyone = "anyone",
|
Anyone = "anyone",
|
||||||
Organization = "organization"
|
Organization = "organization"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrderByDirection {
|
||||||
|
ASC = "asc",
|
||||||
|
DESC = "desc"
|
||||||
|
}
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
export { isDisposableEmail } from "./validate-email";
|
export { isDisposableEmail } from "./validate-email";
|
||||||
export { validateLocalIps } from "./validate-url";
|
export { blockLocalAndPrivateIpAddresses } from "./validate-url";
|
||||||
|
@@ -1,10 +1,16 @@
|
|||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
export const isDisposableEmail = async (email: string) => {
|
export const isDisposableEmail = async (emails: string | string[]) => {
|
||||||
const emailDomain = email.split("@")[1];
|
|
||||||
const disposableEmails = await fs.readFile(path.join(__dirname, "disposable_emails.txt"), "utf8");
|
const disposableEmails = await fs.readFile(path.join(__dirname, "disposable_emails.txt"), "utf8");
|
||||||
|
if (Array.isArray(emails)) {
|
||||||
|
return emails.some((email) => {
|
||||||
|
const emailDomain = email.split("@")[1];
|
||||||
|
return disposableEmails.split("\n").includes(emailDomain);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const emailDomain = emails.split("@")[1];
|
||||||
if (disposableEmails.split("\n").includes(emailDomain)) return true;
|
if (disposableEmails.split("\n").includes(emailDomain)) return true;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { getConfig } from "../config/env";
|
import { getConfig } from "../config/env";
|
||||||
import { BadRequestError } from "../errors";
|
import { BadRequestError } from "../errors";
|
||||||
|
|
||||||
export const validateLocalIps = (url: string) => {
|
export const blockLocalAndPrivateIpAddresses = (url: string) => {
|
||||||
const validUrl = new URL(url);
|
const validUrl = new URL(url);
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
// on cloud local ips are not allowed
|
// on cloud local ips are not allowed
|
||||||
|
@@ -10,7 +10,7 @@ import fastifyFormBody from "@fastify/formbody";
|
|||||||
import helmet from "@fastify/helmet";
|
import helmet from "@fastify/helmet";
|
||||||
import type { FastifyRateLimitOptions } from "@fastify/rate-limit";
|
import type { FastifyRateLimitOptions } from "@fastify/rate-limit";
|
||||||
import ratelimiter from "@fastify/rate-limit";
|
import ratelimiter from "@fastify/rate-limit";
|
||||||
import fasitfy from "fastify";
|
import fastify from "fastify";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { Logger } from "pino";
|
import { Logger } from "pino";
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ type TMain = {
|
|||||||
// Run the server!
|
// Run the server!
|
||||||
export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
|
export const main = async ({ db, smtp, logger, queue, keyStore }: TMain) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const server = fasitfy({
|
const server = fastify({
|
||||||
logger: appCfg.NODE_ENV === "test" ? false : logger,
|
logger: appCfg.NODE_ENV === "test" ? false : logger,
|
||||||
trustProxy: true,
|
trustProxy: true,
|
||||||
connectionTimeout: 30 * 1000,
|
connectionTimeout: 30 * 1000,
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import fastifyPlugin from "fastify-plugin";
|
import fastifyPlugin from "fastify-plugin";
|
||||||
|
import { JsonWebTokenError } from "jsonwebtoken";
|
||||||
import { ZodError } from "zod";
|
import { ZodError } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -11,6 +12,12 @@ import {
|
|||||||
UnauthorizedError
|
UnauthorizedError
|
||||||
} from "@app/lib/errors";
|
} from "@app/lib/errors";
|
||||||
|
|
||||||
|
enum JWTErrors {
|
||||||
|
JwtExpired = "jwt expired",
|
||||||
|
JwtMalformed = "jwt malformed",
|
||||||
|
InvalidAlgorithm = "invalid algorithm"
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -36,6 +43,27 @@ export const fastifyErrHandler = fastifyPlugin(async (server: FastifyZodProvider
|
|||||||
status: error.status,
|
status: error.status,
|
||||||
detail: error.detail
|
detail: error.detail
|
||||||
});
|
});
|
||||||
|
// Handle JWT errors and make them more human-readable for the end-user.
|
||||||
|
} else if (error instanceof JsonWebTokenError) {
|
||||||
|
const message = (() => {
|
||||||
|
if (error.message === JWTErrors.JwtExpired) {
|
||||||
|
return "Your token has expired. Please re-authenticate.";
|
||||||
|
}
|
||||||
|
if (error.message === JWTErrors.JwtMalformed) {
|
||||||
|
return "The provided access token is malformed. Please use a valid token or generate a new one and try again.";
|
||||||
|
}
|
||||||
|
if (error.message === JWTErrors.InvalidAlgorithm) {
|
||||||
|
return "The access token is signed with an invalid algorithm. Please provide a valid token and try again.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.message;
|
||||||
|
})();
|
||||||
|
|
||||||
|
void res.status(401).send({
|
||||||
|
statusCode: 401,
|
||||||
|
error: "TokenError",
|
||||||
|
message
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
void res.send(error);
|
void res.send(error);
|
||||||
}
|
}
|
||||||
|
@@ -182,6 +182,9 @@ import { secretVersionV2BridgeDALFactory } from "@app/services/secret-v2-bridge/
|
|||||||
import { secretVersionV2TagBridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
import { secretVersionV2TagBridgeDALFactory } from "@app/services/secret-v2-bridge/secret-version-tag-dal";
|
||||||
import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal";
|
import { serviceTokenDALFactory } from "@app/services/service-token/service-token-dal";
|
||||||
import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
import { serviceTokenServiceFactory } from "@app/services/service-token/service-token-service";
|
||||||
|
import { projectSlackConfigDALFactory } from "@app/services/slack/project-slack-config-dal";
|
||||||
|
import { slackIntegrationDALFactory } from "@app/services/slack/slack-integration-dal";
|
||||||
|
import { slackServiceFactory } from "@app/services/slack/slack-service";
|
||||||
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
import { TSmtpService } from "@app/services/smtp/smtp-service";
|
||||||
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
||||||
import { getServerCfg, superAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg, superAdminServiceFactory } from "@app/services/super-admin/super-admin-service";
|
||||||
@@ -194,6 +197,8 @@ import { userAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
|||||||
import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
import { userEngagementServiceFactory } from "@app/services/user-engagement/user-engagement-service";
|
||||||
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
import { webhookDALFactory } from "@app/services/webhook/webhook-dal";
|
||||||
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
import { webhookServiceFactory } from "@app/services/webhook/webhook-service";
|
||||||
|
import { workflowIntegrationDALFactory } from "@app/services/workflow-integration/workflow-integration-dal";
|
||||||
|
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
import { injectAuditLogInfo } from "../plugins/audit-log";
|
import { injectAuditLogInfo } from "../plugins/audit-log";
|
||||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||||
@@ -322,6 +327,10 @@ export const registerRoutes = async (
|
|||||||
const externalKmsDAL = externalKmsDALFactory(db);
|
const externalKmsDAL = externalKmsDALFactory(db);
|
||||||
const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
|
const kmsRootConfigDAL = kmsRootConfigDALFactory(db);
|
||||||
|
|
||||||
|
const slackIntegrationDAL = slackIntegrationDALFactory(db);
|
||||||
|
const projectSlackConfigDAL = projectSlackConfigDALFactory(db);
|
||||||
|
const workflowIntegrationDAL = workflowIntegrationDALFactory(db);
|
||||||
|
|
||||||
const permissionService = permissionServiceFactory({
|
const permissionService = permissionServiceFactory({
|
||||||
permissionDAL,
|
permissionDAL,
|
||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
@@ -464,11 +473,13 @@ export const registerRoutes = async (
|
|||||||
userAliasDAL,
|
userAliasDAL,
|
||||||
orgMembershipDAL,
|
orgMembershipDAL,
|
||||||
tokenService,
|
tokenService,
|
||||||
|
permissionService,
|
||||||
|
groupProjectDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectMembershipDAL
|
projectMembershipDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL, tokenDAL: authTokenDAL });
|
const loginService = authLoginServiceFactory({ userDAL, smtpService, tokenService, orgDAL });
|
||||||
const passwordService = authPaswordServiceFactory({
|
const passwordService = authPaswordServiceFactory({
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService,
|
smtpService,
|
||||||
@@ -482,12 +493,12 @@ export const registerRoutes = async (
|
|||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
userGroupMembershipDAL,
|
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
incidentContactDAL,
|
incidentContactDAL,
|
||||||
tokenService,
|
tokenService,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
projectUserAdditionalPrivilegeDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
|
projectRoleDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
orgMembershipDAL,
|
orgMembershipDAL,
|
||||||
@@ -520,8 +531,10 @@ export const registerRoutes = async (
|
|||||||
serverCfgDAL: superAdminDAL,
|
serverCfgDAL: superAdminDAL,
|
||||||
orgService,
|
orgService,
|
||||||
keyStore,
|
keyStore,
|
||||||
licenseService
|
licenseService,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgAdminService = orgAdminServiceFactory({
|
const orgAdminService = orgAdminServiceFactory({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
@@ -721,7 +734,9 @@ export const registerRoutes = async (
|
|||||||
keyStore,
|
keyStore,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
certificateTemplateDAL
|
certificateTemplateDAL,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
slackIntegrationDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const projectEnvService = projectEnvServiceFactory({
|
const projectEnvService = projectEnvServiceFactory({
|
||||||
@@ -872,7 +887,8 @@ export const registerRoutes = async (
|
|||||||
smtpService,
|
smtpService,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
licenseService
|
licenseService,
|
||||||
|
projectSlackConfigDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretService = secretServiceFactory({
|
const secretService = secretServiceFactory({
|
||||||
@@ -922,7 +938,9 @@ export const registerRoutes = async (
|
|||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
accessApprovalPolicyApproverDAL
|
accessApprovalPolicyApproverDAL,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretReplicationService = secretReplicationServiceFactory({
|
const secretReplicationService = secretReplicationServiceFactory({
|
||||||
@@ -1150,6 +1168,18 @@ export const registerRoutes = async (
|
|||||||
userDAL
|
userDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const slackService = slackServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
kmsService,
|
||||||
|
slackIntegrationDAL,
|
||||||
|
workflowIntegrationDAL
|
||||||
|
});
|
||||||
|
|
||||||
|
const workflowIntegrationService = workflowIntegrationServiceFactory({
|
||||||
|
permissionService,
|
||||||
|
workflowIntegrationDAL
|
||||||
|
});
|
||||||
|
|
||||||
await superAdminService.initServerCfg();
|
await superAdminService.initServerCfg();
|
||||||
//
|
//
|
||||||
// setup the communication with license key server
|
// setup the communication with license key server
|
||||||
@@ -1231,7 +1261,9 @@ export const registerRoutes = async (
|
|||||||
secretSharing: secretSharingService,
|
secretSharing: secretSharingService,
|
||||||
userEngagement: userEngagementService,
|
userEngagement: userEngagementService,
|
||||||
externalKms: externalKmsService,
|
externalKms: externalKmsService,
|
||||||
orgAdmin: orgAdminService
|
orgAdmin: orgAdminService,
|
||||||
|
slack: slackService,
|
||||||
|
workflowIntegration: workflowIntegrationService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
|
@@ -21,7 +21,12 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).extend({
|
config: SuperAdminSchema.omit({
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
encryptedSlackClientId: true,
|
||||||
|
encryptedSlackClientSecret: true
|
||||||
|
}).extend({
|
||||||
isMigrationModeOn: z.boolean(),
|
isMigrationModeOn: z.boolean(),
|
||||||
defaultAuthOrgSlug: z.string().nullable(),
|
defaultAuthOrgSlug: z.string().nullable(),
|
||||||
isSecretScanningDisabled: z.boolean()
|
isSecretScanningDisabled: z.boolean()
|
||||||
@@ -62,7 +67,9 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional()
|
.optional()
|
||||||
.refine((methods) => !methods || methods.length > 0, {
|
.refine((methods) => !methods || methods.length > 0, {
|
||||||
message: "At least one login method should be enabled."
|
message: "At least one login method should be enabled."
|
||||||
})
|
}),
|
||||||
|
slackClientId: z.string().optional(),
|
||||||
|
slackClientSecret: z.string().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -123,6 +130,32 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/integrations/slack/config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
clientId: z.string(),
|
||||||
|
clientSecret: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async () => {
|
||||||
|
const adminSlackConfig = await server.services.superAdmin.getAdminSlackConfig();
|
||||||
|
|
||||||
|
return adminSlackConfig;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
url: "/user-management/users/:userId",
|
url: "/user-management/users/:userId",
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { CertificateAuthoritiesSchema } from "@app/db/schemas";
|
import { CertificateAuthoritiesSchema, CertificateTemplatesSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
|
import { CERTIFICATE_AUTHORITIES } 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 { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
|
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
|
||||||
import {
|
import {
|
||||||
validateAltNamesField,
|
validateAltNamesField,
|
||||||
@@ -42,7 +43,11 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
keyAlgorithm: z
|
keyAlgorithm: z
|
||||||
.nativeEnum(CertKeyAlgorithm)
|
.nativeEnum(CertKeyAlgorithm)
|
||||||
.default(CertKeyAlgorithm.RSA_2048)
|
.default(CertKeyAlgorithm.RSA_2048)
|
||||||
.describe(CERTIFICATE_AUTHORITIES.CREATE.keyAlgorithm)
|
.describe(CERTIFICATE_AUTHORITIES.CREATE.keyAlgorithm),
|
||||||
|
requireTemplateForIssuance: z
|
||||||
|
.boolean()
|
||||||
|
.default(false)
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.CREATE.requireTemplateForIssuance)
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -135,6 +140,33 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// this endpoint will be used to serve the CA certificate when a client makes a request
|
||||||
|
// against the Authority Information Access CA Issuer URL
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:caId/certificates/:caCertId/der",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get DER-encoded certificate of CA",
|
||||||
|
params: z.object({
|
||||||
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caId),
|
||||||
|
caCertId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caCertId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.instanceof(Buffer)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const caCert = await server.services.certificateAuthority.getCaCertById(req.params);
|
||||||
|
|
||||||
|
res.header("Content-Type", "application/pkix-cert");
|
||||||
|
|
||||||
|
return Buffer.from(caCert.rawData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/:caId",
|
url: "/:caId",
|
||||||
@@ -148,7 +180,11 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.UPDATE.caId)
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.UPDATE.caId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]).optional().describe(CERTIFICATE_AUTHORITIES.UPDATE.status)
|
status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]).optional().describe(CERTIFICATE_AUTHORITIES.UPDATE.status),
|
||||||
|
requireTemplateForIssuance: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.CREATE.requireTemplateForIssuance)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -565,7 +601,9 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter)
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter),
|
||||||
|
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
||||||
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -645,7 +683,9 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter)
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter),
|
||||||
|
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
|
||||||
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -700,6 +740,51 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:caId/certificate-templates",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
description: "Get list of certificate templates for the CA",
|
||||||
|
params: z.object({
|
||||||
|
caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.caId)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificateTemplates: CertificateTemplatesSchema.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificateTemplates, ca } = await server.services.certificateAuthority.getCaCertificateTemplates({
|
||||||
|
caId: req.params.caId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_CA_CERTIFICATE_TEMPLATES,
|
||||||
|
metadata: {
|
||||||
|
caId: ca.id,
|
||||||
|
dn: ca.dn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificateTemplates
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:caId/crls",
|
url: "/:caId/crls",
|
||||||
|
@@ -7,7 +7,7 @@ import { CERTIFICATE_AUTHORITIES, CERTIFICATES } 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 { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { CrlReason } from "@app/services/certificate/certificate-types";
|
import { CertExtendedKeyUsage, CertKeyUsage, CrlReason } from "@app/services/certificate/certificate-types";
|
||||||
import {
|
import {
|
||||||
validateAltNamesField,
|
validateAltNamesField,
|
||||||
validateCaDateField
|
validateCaDateField
|
||||||
@@ -86,7 +86,17 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter)
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.extendedKeyUsages)
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -177,7 +187,17 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
|
||||||
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
|
||||||
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter)
|
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.extendedKeyUsages)
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
@@ -7,6 +7,7 @@ import { CERTIFICATE_TEMPLATES } 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 { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
||||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
|
||||||
@@ -74,7 +75,19 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
|||||||
ttl: z
|
ttl: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.describe(CERTIFICATE_TEMPLATES.CREATE.ttl)
|
.describe(CERTIFICATE_TEMPLATES.CREATE.ttl),
|
||||||
|
keyUsages: z
|
||||||
|
.nativeEnum(CertKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT])
|
||||||
|
.describe(CERTIFICATE_TEMPLATES.CREATE.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.default([])
|
||||||
|
.describe(CERTIFICATE_TEMPLATES.CREATE.extendedKeyUsages)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: sanitizedCertificateTemplate
|
200: sanitizedCertificateTemplate
|
||||||
@@ -130,7 +143,13 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
|||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
.optional()
|
.optional()
|
||||||
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl)
|
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl),
|
||||||
|
keyUsages: z.nativeEnum(CertKeyUsage).array().optional().describe(CERTIFICATE_TEMPLATES.UPDATE.keyUsages),
|
||||||
|
extendedKeyUsages: z
|
||||||
|
.nativeEnum(CertExtendedKeyUsage)
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
.describe(CERTIFICATE_TEMPLATES.UPDATE.extendedKeyUsages)
|
||||||
}),
|
}),
|
||||||
params: z.object({
|
params: z.object({
|
||||||
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId)
|
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId)
|
||||||
|
@@ -246,12 +246,13 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
description: true
|
description: true
|
||||||
}).optional(),
|
}).optional(),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
}).array()
|
}).array(),
|
||||||
|
totalCount: z.number()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identities = await server.services.identity.listOrgIdentities({
|
const { identityMemberships, totalCount } = await server.services.identity.listOrgIdentities({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@@ -259,7 +260,7 @@ export const registerIdentityRouter = async (server: FastifyZodProvider) => {
|
|||||||
orgId: req.query.orgId
|
orgId: req.query.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { identities };
|
return { identities: identityMemberships, totalCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -29,11 +29,13 @@ import { registerSecretFolderRouter } from "./secret-folder-router";
|
|||||||
import { registerSecretImportRouter } from "./secret-import-router";
|
import { registerSecretImportRouter } from "./secret-import-router";
|
||||||
import { registerSecretSharingRouter } from "./secret-sharing-router";
|
import { registerSecretSharingRouter } from "./secret-sharing-router";
|
||||||
import { registerSecretTagRouter } from "./secret-tag-router";
|
import { registerSecretTagRouter } from "./secret-tag-router";
|
||||||
|
import { registerSlackRouter } from "./slack-router";
|
||||||
import { registerSsoRouter } from "./sso-router";
|
import { registerSsoRouter } from "./sso-router";
|
||||||
import { registerUserActionRouter } from "./user-action-router";
|
import { registerUserActionRouter } from "./user-action-router";
|
||||||
import { registerUserEngagementRouter } from "./user-engagement-router";
|
import { registerUserEngagementRouter } from "./user-engagement-router";
|
||||||
import { registerUserRouter } from "./user-router";
|
import { registerUserRouter } from "./user-router";
|
||||||
import { registerWebhookRouter } from "./webhook-router";
|
import { registerWebhookRouter } from "./webhook-router";
|
||||||
|
import { registerWorkflowIntegrationRouter } from "./workflow-integration-router";
|
||||||
|
|
||||||
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||||
await server.register(registerSsoRouter, { prefix: "/sso" });
|
await server.register(registerSsoRouter, { prefix: "/sso" });
|
||||||
@@ -61,6 +63,14 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerSecretImportRouter, { prefix: "/secret-imports" });
|
await server.register(registerSecretImportRouter, { prefix: "/secret-imports" });
|
||||||
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
|
await server.register(registerSecretFolderRouter, { prefix: "/folders" });
|
||||||
|
|
||||||
|
await server.register(
|
||||||
|
async (workflowIntegrationRouter) => {
|
||||||
|
await workflowIntegrationRouter.register(registerWorkflowIntegrationRouter);
|
||||||
|
await workflowIntegrationRouter.register(registerSlackRouter, { prefix: "/slack" });
|
||||||
|
},
|
||||||
|
{ prefix: "/workflow-integrations" }
|
||||||
|
);
|
||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (projectRouter) => {
|
async (projectRouter) => {
|
||||||
await projectRouter.register(registerProjectRouter);
|
await projectRouter.register(registerProjectRouter);
|
||||||
|
@@ -18,9 +18,14 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
inviteeEmails: z.array(z.string().trim().email()),
|
inviteeEmails: z.array(z.string().trim().email()),
|
||||||
organizationId: z.string().trim(),
|
organizationId: z.string().trim(),
|
||||||
projectIds: z.array(z.string().trim()).optional(),
|
projects: z
|
||||||
projectRoleSlug: z.nativeEnum(ProjectMembershipRole).optional(),
|
.object({
|
||||||
organizationRoleSlug: z.nativeEnum(OrgMembershipRole)
|
id: z.string(),
|
||||||
|
projectRoleSlug: z.string().array().default([ProjectMembershipRole.Member])
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional(),
|
||||||
|
organizationRoleSlug: z.string().default(OrgMembershipRole.Member)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -40,12 +45,12 @@ export const registerInviteOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
if (req.auth.actor !== ActorType.USER) return;
|
if (req.auth.actor !== ActorType.USER) return;
|
||||||
|
|
||||||
const completeInviteLinks = await server.services.org.inviteUserToOrganization({
|
const { signupTokens: completeInviteLinks } = await server.services.org.inviteUserToOrganization({
|
||||||
orgId: req.body.organizationId,
|
orgId: req.body.organizationId,
|
||||||
userId: req.permission.id,
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
inviteeEmails: req.body.inviteeEmails,
|
inviteeEmails: req.body.inviteeEmails,
|
||||||
projectIds: req.body.projectIds,
|
projects: req.body.projects,
|
||||||
projectRoleSlug: req.body.projectRoleSlug,
|
|
||||||
organizationRoleSlug: req.body.organizationRoleSlug,
|
organizationRoleSlug: req.body.organizationRoleSlug,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId
|
actorOrgId: req.permission.orgId
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
AuditLogsSchema,
|
||||||
GroupsSchema,
|
GroupsSchema,
|
||||||
IncidentContactsSchema,
|
IncidentContactsSchema,
|
||||||
OrganizationsSchema,
|
OrganizationsSchema,
|
||||||
@@ -8,7 +9,9 @@ import {
|
|||||||
OrgRolesSchema,
|
OrgRolesSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { AUDIT_LOGS, ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
|
import { getLastMidnightDateISO } from "@app/lib/fn";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -62,6 +65,68 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/audit-logs",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
description: "Get all audit logs for an organization",
|
||||||
|
querystring: z.object({
|
||||||
|
eventType: z.nativeEnum(EventType).optional().describe(AUDIT_LOGS.EXPORT.eventType),
|
||||||
|
userAgentType: z.nativeEnum(UserAgentType).optional().describe(AUDIT_LOGS.EXPORT.userAgentType),
|
||||||
|
startDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.startDate),
|
||||||
|
endDate: z.string().datetime().optional().describe(AUDIT_LOGS.EXPORT.endDate),
|
||||||
|
offset: z.coerce.number().default(0).describe(AUDIT_LOGS.EXPORT.offset),
|
||||||
|
limit: z.coerce.number().default(20).describe(AUDIT_LOGS.EXPORT.limit),
|
||||||
|
actor: z.string().optional().describe(AUDIT_LOGS.EXPORT.actor)
|
||||||
|
}),
|
||||||
|
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
auditLogs: AuditLogsSchema.omit({
|
||||||
|
eventMetadata: true,
|
||||||
|
eventType: true,
|
||||||
|
actor: true,
|
||||||
|
actorMetadata: true
|
||||||
|
})
|
||||||
|
.merge(
|
||||||
|
z.object({
|
||||||
|
project: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string()
|
||||||
|
}),
|
||||||
|
event: z.object({
|
||||||
|
type: z.string(),
|
||||||
|
metadata: z.any()
|
||||||
|
}),
|
||||||
|
actor: z.object({
|
||||||
|
type: z.string(),
|
||||||
|
metadata: z.any()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const auditLogs = await server.services.auditLog.listAuditLogs({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
...req.query,
|
||||||
|
endDate: req.query.endDate,
|
||||||
|
startDate: req.query.startDate || getLastMidnightDateISO(),
|
||||||
|
auditLogActor: req.query.actor,
|
||||||
|
actor: req.permission.type
|
||||||
|
});
|
||||||
|
return { auditLogs };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:organizationId/users",
|
url: "/:organizationId/users",
|
||||||
|
@@ -4,14 +4,17 @@ import {
|
|||||||
IntegrationsSchema,
|
IntegrationsSchema,
|
||||||
ProjectMembershipsSchema,
|
ProjectMembershipsSchema,
|
||||||
ProjectRolesSchema,
|
ProjectRolesSchema,
|
||||||
|
ProjectSlackConfigsSchema,
|
||||||
UserEncryptionKeysSchema,
|
UserEncryptionKeysSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { PROJECTS } from "@app/lib/api-docs";
|
import { PROJECTS } from "@app/lib/api-docs";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import { ProjectFilterType } from "@app/services/project/project-types";
|
import { ProjectFilterType } from "@app/services/project/project-types";
|
||||||
|
import { validateSlackChannelsField } from "@app/services/slack/slack-auth-validators";
|
||||||
|
|
||||||
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
|
import { integrationAuthPubSchema, SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||||
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
|
import { sanitizedServiceTokenSchema } from "../v2/service-token-router";
|
||||||
@@ -542,4 +545,111 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { serviceTokenData };
|
return { serviceTokenData };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:workspaceId/slack-config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ProjectSlackConfigsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slackIntegrationId: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
accessRequestChannels: true,
|
||||||
|
isSecretRequestNotificationEnabled: true,
|
||||||
|
secretRequestChannels: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const slackConfig = await server.services.project.getProjectSlackConfig({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (slackConfig) {
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_PROJECT_SLACK_CONFIG,
|
||||||
|
metadata: {
|
||||||
|
id: slackConfig.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return slackConfig;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PUT",
|
||||||
|
url: "/:workspaceId/slack-config",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
workspaceId: z.string().trim()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slackIntegrationId: z.string(),
|
||||||
|
isAccessRequestNotificationEnabled: z.boolean(),
|
||||||
|
accessRequestChannels: validateSlackChannelsField,
|
||||||
|
isSecretRequestNotificationEnabled: z.boolean(),
|
||||||
|
secretRequestChannels: validateSlackChannelsField
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: ProjectSlackConfigsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
slackIntegrationId: true,
|
||||||
|
isAccessRequestNotificationEnabled: true,
|
||||||
|
accessRequestChannels: true,
|
||||||
|
isSecretRequestNotificationEnabled: true,
|
||||||
|
secretRequestChannels: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const slackConfig = await server.services.project.updateProjectSlackConfig({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: req.params.workspaceId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_PROJECT_SLACK_CONFIG,
|
||||||
|
metadata: {
|
||||||
|
id: slackConfig.id,
|
||||||
|
slackIntegrationId: slackConfig.slackIntegrationId,
|
||||||
|
isAccessRequestNotificationEnabled: slackConfig.isAccessRequestNotificationEnabled,
|
||||||
|
accessRequestChannels: slackConfig.accessRequestChannels,
|
||||||
|
isSecretRequestNotificationEnabled: slackConfig.isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels: slackConfig.secretRequestChannels
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return slackConfig;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
355
backend/src/server/routes/v1/slack-router.ts
Normal file
355
backend/src/server/routes/v1/slack-router.ts
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { SlackIntegrationsSchema, WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
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";
|
||||||
|
|
||||||
|
const sanitizedSlackIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
description: true,
|
||||||
|
slug: true,
|
||||||
|
integration: true
|
||||||
|
}).merge(
|
||||||
|
SlackIntegrationsSchema.pick({
|
||||||
|
teamName: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export const registerSlackRouter = async (server: FastifyZodProvider) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/install",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
}),
|
||||||
|
description: z.string().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const url = await server.services.slack.getInstallUrl({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
...req.query
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ATTEMPT_CREATE_SLACK_INTEGRATION,
|
||||||
|
metadata: {
|
||||||
|
slug: req.query.slug,
|
||||||
|
description: req.query.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/reinstall",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const url = await server.services.slack.getReinstallUrl({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.query.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.ATTEMPT_REINSTALL_SLACK_INTEGRATION,
|
||||||
|
metadata: {
|
||||||
|
id: req.query.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
response: {
|
||||||
|
200: sanitizedSlackIntegrationSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const slackIntegrations = await server.services.slack.getSlackIntegrationsByOrg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return slackIntegrations;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSlackIntegrationSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const deletedSlackIntegration = await server.services.slack.deleteSlackIntegration({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: deletedSlackIntegration.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.DELETE_SLACK_INTEGRATION,
|
||||||
|
metadata: {
|
||||||
|
id: deletedSlackIntegration.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return deletedSlackIntegration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSlackIntegrationSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const slackIntegration = await server.services.slack.getSlackIntegrationById({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: slackIntegration.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_SLACK_INTEGRATION,
|
||||||
|
metadata: {
|
||||||
|
id: slackIntegration.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return slackIntegration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:id/channels",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const slackChannels = await server.services.slack.getSlackIntegrationChannels({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id
|
||||||
|
});
|
||||||
|
|
||||||
|
return slackChannels;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:id",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
description: z.string().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: sanitizedSlackIntegrationSchema
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const slackIntegration = await server.services.slack.updateSlackIntegration({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
id: req.params.id,
|
||||||
|
...req.body
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: slackIntegration.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_SLACK_INTEGRATION,
|
||||||
|
metadata: {
|
||||||
|
id: slackIntegration.id,
|
||||||
|
slug: slackIntegration.slug,
|
||||||
|
description: slackIntegration.description as string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return slackIntegration;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/oauth_redirect",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const installer = await server.services.slack.getSlackInstaller();
|
||||||
|
|
||||||
|
return installer.handleCallback(req.raw, res.raw, {
|
||||||
|
failureAsync: async () => {
|
||||||
|
return res.redirect(appCfg.SITE_URL as string);
|
||||||
|
},
|
||||||
|
successAsync: async (installation) => {
|
||||||
|
const metadata = JSON.parse(installation.metadata || "") as {
|
||||||
|
orgId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
return res.redirect(`${appCfg.SITE_URL}/org/${metadata.orgId}/settings?selectedTab=workflow-integrations`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -57,7 +57,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
cb(null, false);
|
cb(error as Error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -91,7 +91,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
return cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
cb(null, false);
|
cb(error as Error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -126,7 +126,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
return cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
cb(null, false);
|
cb(error as Error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@@ -134,4 +134,39 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/me/:username/groups",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
username: z.string().trim()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
slug: z.string(),
|
||||||
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const groupMemberships = await server.services.user.listUserGroups({
|
||||||
|
username: req.params.username,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actor: req.permission.type
|
||||||
|
});
|
||||||
|
|
||||||
|
return groupMemberships;
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
42
backend/src/server/routes/v1/workflow-integration-router.ts
Normal file
42
backend/src/server/routes/v1/workflow-integration-router.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { WorkflowIntegrationsSchema } from "@app/db/schemas";
|
||||||
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
const sanitizedWorkflowIntegrationSchema = WorkflowIntegrationsSchema.pick({
|
||||||
|
id: true,
|
||||||
|
description: true,
|
||||||
|
slug: true,
|
||||||
|
integration: true
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerWorkflowIntegrationRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
response: {
|
||||||
|
200: sanitizedWorkflowIntegrationSchema.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const workflowIntegrations = await server.services.workflowIntegration.getIntegrationsByOrg({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return workflowIntegrations;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -2,9 +2,11 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
import { IdentitiesSchema, IdentityOrgMembershipsSchema, OrgRolesSchema } from "@app/db/schemas";
|
||||||
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
import { ORGANIZATIONS } from "@app/lib/api-docs";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { readLimit } from "@app/server/config/rateLimiter";
|
import { readLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { OrgIdentityOrderBy } from "@app/services/identity/identity-types";
|
||||||
|
|
||||||
export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -24,6 +26,27 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
|
orgId: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orgId)
|
||||||
}),
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
offset: z.coerce.number().min(0).default(0).describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.offset).optional(),
|
||||||
|
limit: z.coerce
|
||||||
|
.number()
|
||||||
|
.min(1)
|
||||||
|
.max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added
|
||||||
|
.default(100)
|
||||||
|
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.limit)
|
||||||
|
.optional(),
|
||||||
|
orderBy: z
|
||||||
|
.nativeEnum(OrgIdentityOrderBy)
|
||||||
|
.default(OrgIdentityOrderBy.Name)
|
||||||
|
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy)
|
||||||
|
.optional(),
|
||||||
|
orderDirection: z
|
||||||
|
.nativeEnum(OrderByDirection)
|
||||||
|
.default(OrderByDirection.ASC)
|
||||||
|
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection)
|
||||||
|
.optional(),
|
||||||
|
search: z.string().trim().describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.search).optional()
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMemberships: IdentityOrgMembershipsSchema.merge(
|
identityMemberships: IdentityOrgMembershipsSchema.merge(
|
||||||
@@ -37,20 +60,26 @@ export const registerIdentityOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}).optional(),
|
}).optional(),
|
||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true })
|
||||||
})
|
})
|
||||||
).array()
|
).array(),
|
||||||
|
totalCount: z.number()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityMemberships = await server.services.identity.listOrgIdentities({
|
const { identityMemberships, totalCount } = await server.services.identity.listOrgIdentities({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
orgId: req.params.orgId
|
orgId: req.params.orgId,
|
||||||
|
limit: req.query.limit,
|
||||||
|
offset: req.query.offset,
|
||||||
|
orderBy: req.query.orderBy,
|
||||||
|
orderDirection: req.query.orderDirection,
|
||||||
|
search: req.query.search
|
||||||
});
|
});
|
||||||
|
|
||||||
return { identityMemberships };
|
return { identityMemberships, totalCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@@ -7,11 +7,13 @@ import {
|
|||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUserMembershipRolesSchema
|
ProjectUserMembershipRolesSchema
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { PROJECT_IDENTITIES } from "@app/lib/api-docs";
|
import { ORGANIZATIONS, PROJECT_IDENTITIES } from "@app/lib/api-docs";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { ProjectIdentityOrderBy } from "@app/services/identity-project/identity-project-types";
|
||||||
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
import { ProjectUserMembershipTemporaryMode } from "@app/services/project-membership/project-membership-types";
|
||||||
|
|
||||||
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
import { SanitizedProjectSchema } from "../sanitizedSchemas";
|
||||||
@@ -214,6 +216,32 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
projectId: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.projectId)
|
||||||
}),
|
}),
|
||||||
|
querystring: z.object({
|
||||||
|
offset: z.coerce
|
||||||
|
.number()
|
||||||
|
.min(0)
|
||||||
|
.default(0)
|
||||||
|
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.offset)
|
||||||
|
.optional(),
|
||||||
|
limit: z.coerce
|
||||||
|
.number()
|
||||||
|
.min(1)
|
||||||
|
.max(20000) // TODO: temp limit until combobox added to add identity to project modal, reduce once added
|
||||||
|
.default(100)
|
||||||
|
.describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.limit)
|
||||||
|
.optional(),
|
||||||
|
orderBy: z
|
||||||
|
.nativeEnum(ProjectIdentityOrderBy)
|
||||||
|
.default(ProjectIdentityOrderBy.Name)
|
||||||
|
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderBy)
|
||||||
|
.optional(),
|
||||||
|
orderDirection: z
|
||||||
|
.nativeEnum(OrderByDirection)
|
||||||
|
.default(OrderByDirection.ASC)
|
||||||
|
.describe(ORGANIZATIONS.LIST_IDENTITY_MEMBERSHIPS.orderDirection)
|
||||||
|
.optional(),
|
||||||
|
search: z.string().trim().describe(PROJECT_IDENTITIES.LIST_IDENTITY_MEMBERSHIPS.search).optional()
|
||||||
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityMemberships: z
|
identityMemberships: z
|
||||||
@@ -239,19 +267,25 @@ export const registerIdentityProjectRouter = async (server: FastifyZodProvider)
|
|||||||
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }),
|
identity: IdentitiesSchema.pick({ name: true, id: true, authMethod: true }),
|
||||||
project: SanitizedProjectSchema.pick({ name: true, id: true })
|
project: SanitizedProjectSchema.pick({ name: true, id: true })
|
||||||
})
|
})
|
||||||
.array()
|
.array(),
|
||||||
|
totalCount: z.number()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const identityMemberships = await server.services.identityProject.listProjectIdentities({
|
const { identityMemberships, totalCount } = await server.services.identityProject.listProjectIdentities({
|
||||||
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: req.params.projectId
|
projectId: req.params.projectId,
|
||||||
|
limit: req.query.limit,
|
||||||
|
offset: req.query.offset,
|
||||||
|
orderBy: req.query.orderBy,
|
||||||
|
orderDirection: req.query.orderDirection,
|
||||||
|
search: req.query.search
|
||||||
});
|
});
|
||||||
return { identityMemberships };
|
return { identityMemberships, totalCount };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipsSchema } from "@app/db/schemas";
|
import { OrgMembershipRole, ProjectMembershipRole, ProjectMembershipsSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { PROJECT_USERS } from "@app/lib/api-docs";
|
import { PROJECT_USERS } from "@app/lib/api-docs";
|
||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -26,7 +26,8 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
emails: z.string().email().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.emails),
|
emails: z.string().email().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.emails),
|
||||||
usernames: z.string().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.usernames)
|
usernames: z.string().array().default([]).describe(PROJECT_USERS.INVITE_MEMBER.usernames),
|
||||||
|
roleSlugs: z.string().array().optional().describe(PROJECT_USERS.INVITE_MEMBER.roleSlugs)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -36,14 +37,21 @@ export const registerProjectMembershipRouter = async (server: FastifyZodProvider
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const memberships = await server.services.projectMembership.addUsersToProjectNonE2EE({
|
const usernamesAndEmails = [...req.body.emails, ...req.body.usernames];
|
||||||
projectId: req.params.projectId,
|
const { projectMemberships: memberships } = await server.services.org.inviteUserToOrganization({
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
emails: req.body.emails,
|
inviteeEmails: usernamesAndEmails,
|
||||||
usernames: req.body.usernames
|
orgId: req.permission.orgId,
|
||||||
|
organizationRoleSlug: OrgMembershipRole.NoAccess,
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
id: req.params.projectId,
|
||||||
|
projectRoleSlug: [ProjectMembershipRole.Member]
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
|
@@ -42,7 +42,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
organizationId: z.string().trim()
|
organizationId: z.string().trim(),
|
||||||
|
userAgent: z.enum(["cli"]).optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -53,7 +54,7 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const tokens = await server.services.login.selectOrganization({
|
const tokens = await server.services.login.selectOrganization({
|
||||||
userAgent: req.headers["user-agent"],
|
userAgent: req.body.userAgent ?? req.headers["user-agent"],
|
||||||
authJwtToken: req.headers.authorization,
|
authJwtToken: req.headers.authorization,
|
||||||
organizationId: req.body.organizationId,
|
organizationId: req.body.organizationId,
|
||||||
ipAddress: req.realIp
|
ipAddress: req.realIp
|
||||||
|
@@ -12,7 +12,6 @@ import { BadRequestError, DatabaseError, UnauthorizedError } from "@app/lib/erro
|
|||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
|
|
||||||
import { TTokenDALFactory } from "../auth-token/auth-token-dal";
|
|
||||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||||
import { TokenType } from "../auth-token/auth-token-types";
|
import { TokenType } from "../auth-token/auth-token-types";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
@@ -34,7 +33,6 @@ type TAuthLoginServiceFactoryDep = {
|
|||||||
orgDAL: TOrgDALFactory;
|
orgDAL: TOrgDALFactory;
|
||||||
tokenService: TAuthTokenServiceFactory;
|
tokenService: TAuthTokenServiceFactory;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
tokenDAL: TTokenDALFactory;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TAuthLoginFactory = ReturnType<typeof authLoginServiceFactory>;
|
export type TAuthLoginFactory = ReturnType<typeof authLoginServiceFactory>;
|
||||||
@@ -42,8 +40,7 @@ export const authLoginServiceFactory = ({
|
|||||||
userDAL,
|
userDAL,
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService,
|
smtpService,
|
||||||
orgDAL,
|
orgDAL
|
||||||
tokenDAL
|
|
||||||
}: TAuthLoginServiceFactoryDep) => {
|
}: TAuthLoginServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Private
|
* Private
|
||||||
@@ -376,8 +373,6 @@ export const authLoginServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await tokenDAL.incrementTokenSessionVersion(user.id, decodedToken.tokenVersionId);
|
|
||||||
|
|
||||||
const tokens = await generateUserTokens({
|
const tokens = await generateUserTokens({
|
||||||
authMethod: decodedToken.authMethod,
|
authMethod: decodedToken.authMethod,
|
||||||
user,
|
user,
|
||||||
|
@@ -1,15 +1,15 @@
|
|||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
|
import { OrgMembershipStatus, SecretKeyEncoding, TableName } from "@app/db/schemas";
|
||||||
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
|
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
|
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -17,14 +17,14 @@ import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal
|
|||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
|
|
||||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||||
import { TokenMetadataType, TokenType, TTokenMetadata } from "../auth-token/auth-token-types";
|
import { TokenType } from "../auth-token/auth-token-types";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
import { TOrgServiceFactory } from "../org/org-service";
|
||||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||||
import { addMembersToProject } from "../project-membership/project-membership-fns";
|
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
|
import { UserEncryption } from "../user/user-types";
|
||||||
import { TAuthDALFactory } from "./auth-dal";
|
import { TAuthDALFactory } from "./auth-dal";
|
||||||
import { validateProviderAuthToken, validateSignUpAuthorization } from "./auth-fns";
|
import { validateProviderAuthToken, validateSignUpAuthorization } from "./auth-fns";
|
||||||
import { TCompleteAccountInviteDTO, TCompleteAccountSignupDTO } from "./auth-signup-type";
|
import { TCompleteAccountInviteDTO, TCompleteAccountSignupDTO } from "./auth-signup-type";
|
||||||
@@ -67,8 +67,6 @@ export const authSignupServiceFactory = ({
|
|||||||
smtpService,
|
smtpService,
|
||||||
orgService,
|
orgService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectMembershipDAL,
|
|
||||||
projectUserMembershipRoleDAL,
|
|
||||||
licenseService
|
licenseService
|
||||||
}: TAuthSignupDep) => {
|
}: TAuthSignupDep) => {
|
||||||
// first step of signup. create user and send email
|
// first step of signup. create user and send email
|
||||||
@@ -177,32 +175,88 @@ export const authSignupServiceFactory = ({
|
|||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
iv: encryptedPrivateKeyIV,
|
iv: encryptedPrivateKeyIV,
|
||||||
tag: encryptedPrivateKeyTag,
|
tag: encryptedPrivateKeyTag,
|
||||||
encryptionVersion: 2
|
encryptionVersion: UserEncryption.V2
|
||||||
});
|
});
|
||||||
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
|
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
|
||||||
const updateduser = await authDAL.transaction(async (tx) => {
|
const updateduser = await authDAL.transaction(async (tx) => {
|
||||||
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
||||||
if (!us) throw new Error("User not found");
|
if (!us) throw new Error("User not found");
|
||||||
const userEncKey = await userDAL.upsertUserEncryptionKey(
|
const systemGeneratedUserEncryptionKey = await userDAL.findUserEncKeyByUserId(us.id, tx);
|
||||||
us.id,
|
let userEncKey;
|
||||||
{
|
|
||||||
salt,
|
// below condition is true means this is system generated credentials
|
||||||
verifier,
|
// the private key is actually system generated password
|
||||||
publicKey,
|
// thus we will re-encrypt the system generated private key with the new password
|
||||||
protectedKey,
|
// akhilmhdh: you may find this like why? The reason is simple we are moving away from e2ee and these are pieces of it
|
||||||
protectedKeyIV,
|
// without a dummy key in place some things will break and backward compatiability too. 2025 we will be removing all these things
|
||||||
protectedKeyTag,
|
if (
|
||||||
encryptedPrivateKey,
|
systemGeneratedUserEncryptionKey &&
|
||||||
iv: encryptedPrivateKeyIV,
|
!systemGeneratedUserEncryptionKey.hashedPassword &&
|
||||||
tag: encryptedPrivateKeyTag,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey &&
|
||||||
hashedPassword,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag &&
|
||||||
serverEncryptedPrivateKeyEncoding: encoding,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV &&
|
||||||
serverEncryptedPrivateKeyTag: tag,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding
|
||||||
serverEncryptedPrivateKeyIV: iv,
|
) {
|
||||||
serverEncryptedPrivateKey: ciphertext
|
// get server generated password
|
||||||
},
|
const serverGeneratedPassword = infisicalSymmetricDecrypt({
|
||||||
tx
|
iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV,
|
||||||
);
|
tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag,
|
||||||
|
ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey,
|
||||||
|
keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
|
||||||
|
});
|
||||||
|
const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, {
|
||||||
|
...systemGeneratedUserEncryptionKey
|
||||||
|
});
|
||||||
|
const encKeys = await generateUserSrpKeys(email, password, {
|
||||||
|
publicKey: systemGeneratedUserEncryptionKey.publicKey,
|
||||||
|
privateKey: serverGeneratedPrivateKey
|
||||||
|
});
|
||||||
|
// now reencrypt server generated key with user provided password
|
||||||
|
userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||||
|
us.id,
|
||||||
|
{
|
||||||
|
encryptionVersion: UserEncryption.V2,
|
||||||
|
protectedKey: encKeys.protectedKey,
|
||||||
|
protectedKeyIV: encKeys.protectedKeyIV,
|
||||||
|
protectedKeyTag: encKeys.protectedKeyTag,
|
||||||
|
publicKey: encKeys.publicKey,
|
||||||
|
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
||||||
|
iv: encKeys.encryptedPrivateKeyIV,
|
||||||
|
tag: encKeys.encryptedPrivateKeyTag,
|
||||||
|
salt: encKeys.salt,
|
||||||
|
verifier: encKeys.verifier,
|
||||||
|
hashedPassword,
|
||||||
|
serverEncryptedPrivateKeyEncoding: encoding,
|
||||||
|
serverEncryptedPrivateKeyTag: tag,
|
||||||
|
serverEncryptedPrivateKeyIV: iv,
|
||||||
|
serverEncryptedPrivateKey: ciphertext
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||||
|
us.id,
|
||||||
|
{
|
||||||
|
encryptionVersion: UserEncryption.V2,
|
||||||
|
salt,
|
||||||
|
verifier,
|
||||||
|
publicKey,
|
||||||
|
protectedKey,
|
||||||
|
protectedKeyIV,
|
||||||
|
protectedKeyTag,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag,
|
||||||
|
hashedPassword,
|
||||||
|
serverEncryptedPrivateKeyEncoding: encoding,
|
||||||
|
serverEncryptedPrivateKeyTag: tag,
|
||||||
|
serverEncryptedPrivateKeyIV: iv,
|
||||||
|
serverEncryptedPrivateKey: ciphertext
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If it's SAML Auth and the organization ID is present, we should check if the user has a pending invite for this org, and accept it
|
// If it's SAML Auth and the organization ID is present, we should check if the user has a pending invite for this org, and accept it
|
||||||
if (
|
if (
|
||||||
(isAuthMethodSaml(authMethod) || [AuthMethod.LDAP, AuthMethod.OIDC].includes(authMethod as AuthMethod)) &&
|
(isAuthMethodSaml(authMethod) || [AuthMethod.LDAP, AuthMethod.OIDC].includes(authMethod as AuthMethod)) &&
|
||||||
@@ -312,8 +366,7 @@ export const authSignupServiceFactory = ({
|
|||||||
encryptedPrivateKey,
|
encryptedPrivateKey,
|
||||||
encryptedPrivateKeyIV,
|
encryptedPrivateKeyIV,
|
||||||
encryptedPrivateKeyTag,
|
encryptedPrivateKeyTag,
|
||||||
authorization,
|
authorization
|
||||||
tokenMetadata
|
|
||||||
}: TCompleteAccountInviteDTO) => {
|
}: TCompleteAccountInviteDTO) => {
|
||||||
const user = await userDAL.findUserByUsername(email);
|
const user = await userDAL.findUserByUsername(email);
|
||||||
if (!user || (user && user.isAccepted)) {
|
if (!user || (user && user.isAccepted)) {
|
||||||
@@ -348,65 +401,76 @@ export const authSignupServiceFactory = ({
|
|||||||
const updateduser = await authDAL.transaction(async (tx) => {
|
const updateduser = await authDAL.transaction(async (tx) => {
|
||||||
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
const us = await userDAL.updateById(user.id, { firstName, lastName, isAccepted: true }, tx);
|
||||||
if (!us) throw new Error("User not found");
|
if (!us) throw new Error("User not found");
|
||||||
const userEncKey = await userDAL.upsertUserEncryptionKey(
|
const systemGeneratedUserEncryptionKey = await userDAL.findUserEncKeyByUserId(us.id, tx);
|
||||||
us.id,
|
let userEncKey;
|
||||||
{
|
// this means this is system generated credentials
|
||||||
salt,
|
// now replace the private key
|
||||||
encryptionVersion: 2,
|
if (
|
||||||
verifier,
|
systemGeneratedUserEncryptionKey &&
|
||||||
publicKey,
|
!systemGeneratedUserEncryptionKey.hashedPassword &&
|
||||||
protectedKey,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey &&
|
||||||
protectedKeyIV,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag &&
|
||||||
protectedKeyTag,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV &&
|
||||||
encryptedPrivateKey,
|
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding
|
||||||
iv: encryptedPrivateKeyIV,
|
) {
|
||||||
tag: encryptedPrivateKeyTag,
|
// get server generated password
|
||||||
hashedPassword,
|
const serverGeneratedPassword = infisicalSymmetricDecrypt({
|
||||||
serverEncryptedPrivateKeyEncoding: encoding,
|
iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV,
|
||||||
serverEncryptedPrivateKeyTag: tag,
|
tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag,
|
||||||
serverEncryptedPrivateKeyIV: iv,
|
ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey,
|
||||||
serverEncryptedPrivateKey: ciphertext
|
keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
|
||||||
},
|
});
|
||||||
tx
|
const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, {
|
||||||
);
|
...systemGeneratedUserEncryptionKey
|
||||||
|
});
|
||||||
if (tokenMetadata) {
|
const encKeys = await generateUserSrpKeys(email, password, {
|
||||||
const metadataObj = jwt.verify(tokenMetadata, appCfg.AUTH_SECRET) as TTokenMetadata;
|
publicKey: systemGeneratedUserEncryptionKey.publicKey,
|
||||||
|
privateKey: serverGeneratedPrivateKey
|
||||||
if (
|
});
|
||||||
metadataObj?.payload?.userId !== user.id ||
|
// now reencrypt server generated key with user provided password
|
||||||
metadataObj?.payload?.orgId !== orgMembership.orgId ||
|
userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||||
metadataObj?.type !== TokenMetadataType.InviteToProjects
|
us.id,
|
||||||
) {
|
{
|
||||||
throw new UnauthorizedError({
|
encryptionVersion: 2,
|
||||||
message: "Malformed or invalid metadata token"
|
protectedKey: encKeys.protectedKey,
|
||||||
});
|
protectedKeyIV: encKeys.protectedKeyIV,
|
||||||
}
|
protectedKeyTag: encKeys.protectedKeyTag,
|
||||||
|
publicKey: encKeys.publicKey,
|
||||||
for await (const projectId of metadataObj.payload.projectIds) {
|
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
||||||
await addMembersToProject({
|
iv: encKeys.encryptedPrivateKeyIV,
|
||||||
orgDAL,
|
tag: encKeys.encryptedPrivateKeyTag,
|
||||||
projectDAL,
|
salt: encKeys.salt,
|
||||||
projectMembershipDAL,
|
verifier: encKeys.verifier,
|
||||||
projectKeyDAL,
|
hashedPassword,
|
||||||
userGroupMembershipDAL,
|
serverEncryptedPrivateKeyEncoding: encoding,
|
||||||
projectBotDAL,
|
serverEncryptedPrivateKeyTag: tag,
|
||||||
projectUserMembershipRoleDAL,
|
serverEncryptedPrivateKeyIV: iv,
|
||||||
smtpService
|
serverEncryptedPrivateKey: ciphertext
|
||||||
}).addMembersToNonE2EEProject(
|
},
|
||||||
{
|
tx
|
||||||
emails: [user.email!],
|
);
|
||||||
usernames: [],
|
} else {
|
||||||
projectId,
|
userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||||
projectMembershipRole: metadataObj.payload.projectRoleSlug,
|
us.id,
|
||||||
sendEmails: false
|
{
|
||||||
},
|
encryptionVersion: UserEncryption.V2,
|
||||||
{
|
salt,
|
||||||
tx,
|
verifier,
|
||||||
throwOnProjectNotFound: false
|
publicKey,
|
||||||
}
|
protectedKey,
|
||||||
);
|
protectedKeyIV,
|
||||||
}
|
protectedKeyTag,
|
||||||
|
encryptedPrivateKey,
|
||||||
|
iv: encryptedPrivateKeyIV,
|
||||||
|
tag: encryptedPrivateKeyTag,
|
||||||
|
hashedPassword,
|
||||||
|
serverEncryptedPrivateKeyEncoding: encoding,
|
||||||
|
serverEncryptedPrivateKeyTag: tag,
|
||||||
|
serverEncryptedPrivateKeyIV: iv,
|
||||||
|
serverEncryptedPrivateKey: ciphertext
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedMembersips = await orgDAL.updateMembership(
|
const updatedMembersips = await orgDAL.updateMembership(
|
||||||
|
@@ -15,7 +15,7 @@ import {
|
|||||||
|
|
||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
export const createSerialNumber = () => {
|
export const createSerialNumber = () => {
|
||||||
const randomBytes = crypto.randomBytes(32);
|
const randomBytes = crypto.randomBytes(20);
|
||||||
randomBytes[0] &= 0x7f; // ensure the first bit is 0
|
randomBytes[0] &= 0x7f; // ensure the first bit is 0
|
||||||
return randomBytes.toString("hex");
|
return randomBytes.toString("hex");
|
||||||
};
|
};
|
||||||
|
@@ -19,7 +19,13 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
|
import {
|
||||||
|
CertExtendedKeyUsage,
|
||||||
|
CertExtendedKeyUsageOIDToName,
|
||||||
|
CertKeyAlgorithm,
|
||||||
|
CertKeyUsage,
|
||||||
|
CertStatus
|
||||||
|
} from "../certificate/certificate-types";
|
||||||
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
|
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
|
||||||
import { validateCertificateDetailsAgainstTemplate } from "../certificate-template/certificate-template-fns";
|
import { validateCertificateDetailsAgainstTemplate } from "../certificate-template/certificate-template-fns";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
@@ -41,6 +47,7 @@ import {
|
|||||||
TCreateCaDTO,
|
TCreateCaDTO,
|
||||||
TDeleteCaDTO,
|
TDeleteCaDTO,
|
||||||
TGetCaCertDTO,
|
TGetCaCertDTO,
|
||||||
|
TGetCaCertificateTemplatesDTO,
|
||||||
TGetCaCertsDTO,
|
TGetCaCertsDTO,
|
||||||
TGetCaCsrDTO,
|
TGetCaCsrDTO,
|
||||||
TGetCaDTO,
|
TGetCaDTO,
|
||||||
@@ -64,7 +71,7 @@ type TCertificateAuthorityServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">;
|
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">;
|
||||||
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">;
|
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">;
|
||||||
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getById">;
|
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getById" | "find">;
|
||||||
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
|
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
||||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||||
@@ -108,6 +115,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
notAfter,
|
notAfter,
|
||||||
maxPathLength,
|
maxPathLength,
|
||||||
keyAlgorithm,
|
keyAlgorithm,
|
||||||
|
requireTemplateForIssuance,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
@@ -170,7 +178,8 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate,
|
notAfter: notAfterDate,
|
||||||
serialNumber
|
serialNumber
|
||||||
})
|
}),
|
||||||
|
requireTemplateForIssuance
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -213,7 +222,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
keys,
|
keys,
|
||||||
extensions: [
|
extensions: [
|
||||||
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
||||||
new x509.ExtendedKeyUsageExtension(["1.2.3.4.5.6.7", "2.3.4.5.6.7.8"], true),
|
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(keys.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(keys.publicKey)
|
||||||
@@ -303,7 +311,15 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
* Update CA with id [caId].
|
* Update CA with id [caId].
|
||||||
* Note: Used to enable/disable CA
|
* Note: Used to enable/disable CA
|
||||||
*/
|
*/
|
||||||
const updateCaById = async ({ caId, status, actorId, actorAuthMethod, actor, actorOrgId }: TUpdateCaDTO) => {
|
const updateCaById = async ({
|
||||||
|
caId,
|
||||||
|
status,
|
||||||
|
requireTemplateForIssuance,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUpdateCaDTO) => {
|
||||||
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 BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
@@ -320,7 +336,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
ProjectPermissionSub.CertificateAuthorities
|
ProjectPermissionSub.CertificateAuthorities
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedCa = await certificateAuthorityDAL.updateById(caId, { status });
|
const updatedCa = await certificateAuthorityDAL.updateById(caId, { status, requireTemplateForIssuance });
|
||||||
|
|
||||||
return updatedCa;
|
return updatedCa;
|
||||||
};
|
};
|
||||||
@@ -496,7 +512,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
ca.maxPathLength === -1 || !ca.maxPathLength ? undefined : ca.maxPathLength,
|
ca.maxPathLength === -1 || !ca.maxPathLength ? undefined : ca.maxPathLength,
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
new x509.ExtendedKeyUsageExtension(["1.2.3.4.5.6.7", "2.3.4.5.6.7.8"], true),
|
|
||||||
// eslint-disable-next-line no-bitwise
|
// eslint-disable-next-line no-bitwise
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(caPublicKey)
|
await x509.SubjectKeyIdentifierExtension.create(caPublicKey)
|
||||||
@@ -753,6 +768,39 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return CA certificate object by ID
|
||||||
|
*/
|
||||||
|
const getCaCertById = async ({ caId, caCertId }: { caId: string; caCertId: string }) => {
|
||||||
|
const caCert = await certificateAuthorityCertDAL.findOne({
|
||||||
|
caId,
|
||||||
|
id: caCertId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!caCert) {
|
||||||
|
throw new NotFoundError({ message: "CA certificate not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
const keyId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: keyId
|
||||||
|
});
|
||||||
|
|
||||||
|
const decryptedCaCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: caCert.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const caCertObj = new x509.X509Certificate(decryptedCaCert);
|
||||||
|
|
||||||
|
return caCertObj;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Issue certificate to be imported back in for intermediate CA
|
* Issue certificate to be imported back in for intermediate CA
|
||||||
*/
|
*/
|
||||||
@@ -767,6 +815,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
notAfter,
|
notAfter,
|
||||||
maxPathLength
|
maxPathLength
|
||||||
}: TSignIntermediateDTO) => {
|
}: TSignIntermediateDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
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 BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
@@ -841,7 +890,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { caPrivateKey } = await getCaCredentials({
|
const { caPrivateKey, caSecret } = await getCaCredentials({
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthoritySecretDAL,
|
certificateAuthoritySecretDAL,
|
||||||
@@ -850,6 +899,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const serialNumber = createSerialNumber();
|
const serialNumber = createSerialNumber();
|
||||||
|
|
||||||
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
const intermediateCert = await x509.X509CertificateGenerator.create({
|
const intermediateCert = await x509.X509CertificateGenerator.create({
|
||||||
serialNumber,
|
serialNumber,
|
||||||
subject: csrObj.subject,
|
subject: csrObj.subject,
|
||||||
@@ -869,7 +923,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
),
|
),
|
||||||
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1043,7 +1101,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
}: TIssueCertFromCaDTO) => {
|
}: TIssueCertFromCaDTO) => {
|
||||||
let ca: TCertificateAuthorities | undefined;
|
let ca: TCertificateAuthorities | undefined;
|
||||||
let certificateTemplate: TCertificateTemplates | undefined;
|
let certificateTemplate: TCertificateTemplates | undefined;
|
||||||
@@ -1079,6 +1139,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
if (ca.requireTemplateForIssuance && !certificateTemplate) {
|
||||||
|
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
||||||
|
}
|
||||||
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
|
|
||||||
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
if (ca.notAfter && new Date() > new Date(ca.notAfter)) {
|
||||||
@@ -1156,16 +1219,70 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`;
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
|
|
||||||
const extensions: x509.Extension[] = [
|
const extensions: x509.Extension[] = [
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
|
||||||
new x509.BasicConstraintsExtension(false),
|
new x509.BasicConstraintsExtension(false),
|
||||||
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
}),
|
||||||
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// handle key usages
|
||||||
|
let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? [];
|
||||||
|
if (keyUsages === undefined && !certificateTemplate) {
|
||||||
|
selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyUsages === undefined && certificateTemplate) {
|
||||||
|
selectedKeyUsages = (certificateTemplate.keyUsages ?? []) as CertKeyUsage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyUsages?.length && certificateTemplate) {
|
||||||
|
const validKeyUsages = certificateTemplate.keyUsages || [];
|
||||||
|
if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedKeyUsages = keyUsages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
||||||
|
if (keyUsagesBitValue) {
|
||||||
|
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extended key usages
|
||||||
|
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = extendedKeyUsages ?? [];
|
||||||
|
if (extendedKeyUsages === undefined && certificateTemplate) {
|
||||||
|
selectedExtendedKeyUsages = (certificateTemplate.extendedKeyUsages ?? []) as CertExtendedKeyUsage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extendedKeyUsages?.length && certificateTemplate) {
|
||||||
|
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
|
||||||
|
if (extendedKeyUsages.some((eku) => !validExtendedKeyUsages.includes(eku))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid extended key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedExtendedKeyUsages = extendedKeyUsages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedExtendedKeyUsages.length) {
|
||||||
|
extensions.push(
|
||||||
|
new x509.ExtendedKeyUsageExtension(
|
||||||
|
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let altNamesArray: {
|
let altNamesArray: {
|
||||||
type: "email" | "dns";
|
type: "email" | "dns";
|
||||||
value: string;
|
value: string;
|
||||||
@@ -1247,7 +1364,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
altNames,
|
altNames,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate
|
notAfter: notAfterDate,
|
||||||
|
keyUsages: selectedKeyUsages,
|
||||||
|
extendedKeyUsages: selectedExtendedKeyUsages
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -1296,6 +1415,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
* Note: CSR is generated externally and submitted to Infisical.
|
* Note: CSR is generated externally and submitted to Infisical.
|
||||||
*/
|
*/
|
||||||
const signCertFromCa = async (dto: TSignCertFromCaDTO) => {
|
const signCertFromCa = async (dto: TSignCertFromCaDTO) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
let ca: TCertificateAuthorities | undefined;
|
let ca: TCertificateAuthorities | undefined;
|
||||||
let certificateTemplate: TCertificateTemplates | undefined;
|
let certificateTemplate: TCertificateTemplates | undefined;
|
||||||
|
|
||||||
@@ -1309,7 +1429,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
altNames,
|
altNames,
|
||||||
ttl,
|
ttl,
|
||||||
notBefore,
|
notBefore,
|
||||||
notAfter
|
notAfter,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
} = dto;
|
} = dto;
|
||||||
|
|
||||||
let collectionId = pkiCollectionId;
|
let collectionId = pkiCollectionId;
|
||||||
@@ -1349,6 +1471,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
|
||||||
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" });
|
||||||
|
if (ca.requireTemplateForIssuance && !certificateTemplate) {
|
||||||
|
throw new BadRequestError({ message: "Certificate template is required for issuance" });
|
||||||
|
}
|
||||||
|
|
||||||
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId);
|
||||||
|
|
||||||
@@ -1417,7 +1542,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
message: "A common name (CN) is required in the CSR or as a parameter to this endpoint"
|
message: "A common name (CN) is required in the CSR or as a parameter to this endpoint"
|
||||||
});
|
});
|
||||||
|
|
||||||
const { caPrivateKey } = await getCaCredentials({
|
const { caPrivateKey, caSecret } = await getCaCredentials({
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthoritySecretDAL,
|
certificateAuthoritySecretDAL,
|
||||||
@@ -1425,13 +1550,115 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id });
|
||||||
|
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`;
|
||||||
|
|
||||||
|
const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`;
|
||||||
const extensions: x509.Extension[] = [
|
const extensions: x509.Extension[] = [
|
||||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true),
|
|
||||||
new x509.BasicConstraintsExtension(false),
|
new x509.BasicConstraintsExtension(false),
|
||||||
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
||||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey),
|
||||||
|
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
|
||||||
|
new x509.AuthorityInfoAccessExtension({
|
||||||
|
caIssuers: new x509.GeneralName("url", caIssuerUrl)
|
||||||
|
}),
|
||||||
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// handle key usages
|
||||||
|
const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension;
|
||||||
|
let csrKeyUsages: CertKeyUsage[] = [];
|
||||||
|
if (csrKeyUsageExtension) {
|
||||||
|
csrKeyUsages = Object.values(CertKeyUsage).filter(
|
||||||
|
(keyUsage) => (x509.KeyUsageFlags[keyUsage] & csrKeyUsageExtension.usages) !== 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? [];
|
||||||
|
if (keyUsages === undefined && !certificateTemplate) {
|
||||||
|
if (csrKeyUsageExtension) {
|
||||||
|
selectedKeyUsages = csrKeyUsages;
|
||||||
|
} else {
|
||||||
|
selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyUsages === undefined && certificateTemplate) {
|
||||||
|
if (csrKeyUsageExtension) {
|
||||||
|
const validKeyUsages = certificateTemplate.keyUsages || [];
|
||||||
|
if (csrKeyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedKeyUsages = csrKeyUsages;
|
||||||
|
} else {
|
||||||
|
selectedKeyUsages = (certificateTemplate.keyUsages ?? []) as CertKeyUsage[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyUsages?.length && certificateTemplate) {
|
||||||
|
const validKeyUsages = certificateTemplate.keyUsages || [];
|
||||||
|
if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedKeyUsages = keyUsages;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
|
||||||
|
if (keyUsagesBitValue) {
|
||||||
|
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle extended key usages
|
||||||
|
const csrExtendedKeyUsageExtension = csrObj.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension;
|
||||||
|
let csrExtendedKeyUsages: CertExtendedKeyUsage[] = [];
|
||||||
|
if (csrExtendedKeyUsageExtension) {
|
||||||
|
csrExtendedKeyUsages = csrExtendedKeyUsageExtension.usages.map(
|
||||||
|
(ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = extendedKeyUsages ?? [];
|
||||||
|
if (extendedKeyUsages === undefined && !certificateTemplate && csrExtendedKeyUsageExtension) {
|
||||||
|
selectedExtendedKeyUsages = csrExtendedKeyUsages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extendedKeyUsages === undefined && certificateTemplate) {
|
||||||
|
if (csrExtendedKeyUsageExtension) {
|
||||||
|
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
|
||||||
|
if (csrExtendedKeyUsages.some((eku) => !validExtendedKeyUsages.includes(eku))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid extended key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedExtendedKeyUsages = csrExtendedKeyUsages;
|
||||||
|
} else {
|
||||||
|
selectedExtendedKeyUsages = (certificateTemplate.extendedKeyUsages ?? []) as CertExtendedKeyUsage[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extendedKeyUsages?.length && certificateTemplate) {
|
||||||
|
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
|
||||||
|
if (extendedKeyUsages.some((keyUsage) => !validExtendedKeyUsages.includes(keyUsage))) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid extended key usage value based on template policy"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectedExtendedKeyUsages = extendedKeyUsages;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedExtendedKeyUsages.length) {
|
||||||
|
extensions.push(
|
||||||
|
new x509.ExtendedKeyUsageExtension(
|
||||||
|
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let altNamesFromCsr: string = "";
|
let altNamesFromCsr: string = "";
|
||||||
let altNamesArray: {
|
let altNamesArray: {
|
||||||
type: "email" | "dns";
|
type: "email" | "dns";
|
||||||
@@ -1527,7 +1754,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
altNames: altNamesFromCsr || altNames,
|
altNames: altNamesFromCsr || altNames,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
notBefore: notBeforeDate,
|
notBefore: notBeforeDate,
|
||||||
notAfter: notAfterDate
|
notAfter: notAfterDate,
|
||||||
|
keyUsages: selectedKeyUsages,
|
||||||
|
extendedKeyUsages: selectedExtendedKeyUsages
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -1570,6 +1799,40 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return list of certificate templates for CA with id [caId].
|
||||||
|
*/
|
||||||
|
const getCaCertificateTemplates = async ({
|
||||||
|
caId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetCaCertificateTemplatesDTO) => {
|
||||||
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
|
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.CertificateTemplates
|
||||||
|
);
|
||||||
|
|
||||||
|
const certificateTemplates = await certificateTemplateDAL.find({ caId });
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificateTemplates,
|
||||||
|
ca
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createCa,
|
createCa,
|
||||||
getCaById,
|
getCaById,
|
||||||
@@ -1579,9 +1842,11 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
renewCaCert,
|
renewCaCert,
|
||||||
getCaCerts,
|
getCaCerts,
|
||||||
getCaCert,
|
getCaCert,
|
||||||
|
getCaCertById,
|
||||||
signIntermediate,
|
signIntermediate,
|
||||||
importCertToCa,
|
importCertToCa,
|
||||||
issueCertFromCa,
|
issueCertFromCa,
|
||||||
signCertFromCa
|
signCertFromCa,
|
||||||
|
getCaCertificateTemplates
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -4,7 +4,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
import { CertKeyAlgorithm } from "../certificate/certificate-types";
|
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "../certificate/certificate-types";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||||
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
|
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
|
||||||
@@ -38,6 +38,7 @@ export type TCreateCaDTO = {
|
|||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
maxPathLength: number;
|
maxPathLength: number;
|
||||||
keyAlgorithm: CertKeyAlgorithm;
|
keyAlgorithm: CertKeyAlgorithm;
|
||||||
|
requireTemplateForIssuance: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetCaDTO = {
|
export type TGetCaDTO = {
|
||||||
@@ -47,6 +48,7 @@ export type TGetCaDTO = {
|
|||||||
export type TUpdateCaDTO = {
|
export type TUpdateCaDTO = {
|
||||||
caId: string;
|
caId: string;
|
||||||
status?: CaStatus;
|
status?: CaStatus;
|
||||||
|
requireTemplateForIssuance?: boolean;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDeleteCaDTO = {
|
export type TDeleteCaDTO = {
|
||||||
@@ -95,6 +97,8 @@ export type TIssueCertFromCaDTO = {
|
|||||||
ttl: string;
|
ttl: string;
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TSignCertFromCaDTO =
|
export type TSignCertFromCaDTO =
|
||||||
@@ -110,6 +114,8 @@ export type TSignCertFromCaDTO =
|
|||||||
ttl?: string;
|
ttl?: string;
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
}
|
}
|
||||||
| ({
|
| ({
|
||||||
isInternal: false;
|
isInternal: false;
|
||||||
@@ -123,8 +129,14 @@ export type TSignCertFromCaDTO =
|
|||||||
ttl: string;
|
ttl: string;
|
||||||
notBefore?: string;
|
notBefore?: string;
|
||||||
notAfter?: string;
|
notAfter?: string;
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
} & Omit<TProjectPermission, "projectId">);
|
} & Omit<TProjectPermission, "projectId">);
|
||||||
|
|
||||||
|
export type TGetCaCertificateTemplatesDTO = {
|
||||||
|
caId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TDNParts = {
|
export type TDNParts = {
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
@@ -7,7 +7,7 @@ const isValidDate = (dateString: string) => {
|
|||||||
|
|
||||||
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
export const validateCaDateField = z.string().trim().refine(isValidDate, { message: "Invalid date format" });
|
||||||
|
|
||||||
export const hostnameRegex = /^(?!:\/\/)([a-zA-Z0-9-_]{1,63}\.?)+(?!:\/\/)([a-zA-Z]{2,63})$/;
|
export const hostnameRegex = /^(?!:\/\/)(\*\.)?([a-zA-Z0-9-_]{1,63}\.?)+(?!:\/\/)([a-zA-Z]{2,63})$/;
|
||||||
export const validateAltNamesField = z
|
export const validateAltNamesField = z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
@@ -9,7 +9,9 @@ export const sanitizedCertificateTemplate = CertificateTemplatesSchema.pick({
|
|||||||
commonName: true,
|
commonName: true,
|
||||||
subjectAlternativeName: true,
|
subjectAlternativeName: true,
|
||||||
pkiCollectionId: true,
|
pkiCollectionId: true,
|
||||||
ttl: true
|
ttl: true,
|
||||||
|
keyUsages: true,
|
||||||
|
extendedKeyUsages: true
|
||||||
}).merge(
|
}).merge(
|
||||||
z.object({
|
z.object({
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
|
@@ -57,7 +57,9 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
}: TCreateCertTemplateDTO) => {
|
}: TCreateCertTemplateDTO) => {
|
||||||
const ca = await certificateAuthorityDAL.findById(caId);
|
const ca = await certificateAuthorityDAL.findById(caId);
|
||||||
if (!ca) {
|
if (!ca) {
|
||||||
@@ -86,7 +88,9 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
name,
|
name,
|
||||||
commonName,
|
commonName,
|
||||||
subjectAlternativeName,
|
subjectAlternativeName,
|
||||||
ttl
|
ttl,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -113,7 +117,9 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actor,
|
actor,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
}: TUpdateCertTemplateDTO) => {
|
}: TUpdateCertTemplateDTO) => {
|
||||||
const certTemplate = await certificateTemplateDAL.getById(id);
|
const certTemplate = await certificateTemplateDAL.getById(id);
|
||||||
if (!certTemplate) {
|
if (!certTemplate) {
|
||||||
@@ -153,7 +159,9 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
commonName,
|
commonName,
|
||||||
subjectAlternativeName,
|
subjectAlternativeName,
|
||||||
name,
|
name,
|
||||||
ttl
|
ttl,
|
||||||
|
keyUsages,
|
||||||
|
extendedKeyUsages
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
export type TCreateCertTemplateDTO = {
|
export type TCreateCertTemplateDTO = {
|
||||||
caId: string;
|
caId: string;
|
||||||
@@ -7,6 +8,8 @@ export type TCreateCertTemplateDTO = {
|
|||||||
commonName: string;
|
commonName: string;
|
||||||
subjectAlternativeName: string;
|
subjectAlternativeName: string;
|
||||||
ttl: string;
|
ttl: string;
|
||||||
|
keyUsages: CertKeyUsage[];
|
||||||
|
extendedKeyUsages: CertExtendedKeyUsage[];
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TUpdateCertTemplateDTO = {
|
export type TUpdateCertTemplateDTO = {
|
||||||
@@ -17,6 +20,8 @@ export type TUpdateCertTemplateDTO = {
|
|||||||
commonName?: string;
|
commonName?: string;
|
||||||
subjectAlternativeName?: string;
|
subjectAlternativeName?: string;
|
||||||
ttl?: string;
|
ttl?: string;
|
||||||
|
keyUsages?: CertKeyUsage[];
|
||||||
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetCertTemplateDTO = {
|
export type TGetCertTemplateDTO = {
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
|
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export enum CertStatus {
|
export enum CertStatus {
|
||||||
@@ -12,6 +14,36 @@ export enum CertKeyAlgorithm {
|
|||||||
ECDSA_P384 = "EC_secp384r1"
|
ECDSA_P384 = "EC_secp384r1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CertKeyUsage {
|
||||||
|
DIGITAL_SIGNATURE = "digitalSignature",
|
||||||
|
KEY_ENCIPHERMENT = "keyEncipherment",
|
||||||
|
NON_REPUDIATION = "nonRepudiation",
|
||||||
|
DATA_ENCIPHERMENT = "dataEncipherment",
|
||||||
|
KEY_AGREEMENT = "keyAgreement",
|
||||||
|
KEY_CERT_SIGN = "keyCertSign",
|
||||||
|
CRL_SIGN = "cRLSign",
|
||||||
|
ENCIPHER_ONLY = "encipherOnly",
|
||||||
|
DECIPHER_ONLY = "decipherOnly"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CertExtendedKeyUsage {
|
||||||
|
CLIENT_AUTH = "clientAuth",
|
||||||
|
SERVER_AUTH = "serverAuth",
|
||||||
|
CODE_SIGNING = "codeSigning",
|
||||||
|
EMAIL_PROTECTION = "emailProtection",
|
||||||
|
TIMESTAMPING = "timeStamping",
|
||||||
|
OCSP_SIGNING = "ocspSigning"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CertExtendedKeyUsageOIDToName: Record<string, CertExtendedKeyUsage> = {
|
||||||
|
[x509.ExtendedKeyUsage.clientAuth]: CertExtendedKeyUsage.CLIENT_AUTH,
|
||||||
|
[x509.ExtendedKeyUsage.serverAuth]: CertExtendedKeyUsage.SERVER_AUTH,
|
||||||
|
[x509.ExtendedKeyUsage.codeSigning]: CertExtendedKeyUsage.CODE_SIGNING,
|
||||||
|
[x509.ExtendedKeyUsage.emailProtection]: CertExtendedKeyUsage.EMAIL_PROTECTION,
|
||||||
|
[x509.ExtendedKeyUsage.ocspSigning]: CertExtendedKeyUsage.OCSP_SIGNING,
|
||||||
|
[x509.ExtendedKeyUsage.timeStamping]: CertExtendedKeyUsage.TIMESTAMPING
|
||||||
|
};
|
||||||
|
|
||||||
export enum CrlReason {
|
export enum CrlReason {
|
||||||
UNSPECIFIED = "UNSPECIFIED",
|
UNSPECIFIED = "UNSPECIFIED",
|
||||||
KEY_COMPROMISE = "KEY_COMPROMISE",
|
KEY_COMPROMISE = "KEY_COMPROMISE",
|
||||||
|
@@ -95,6 +95,30 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const findByUserId = async (userId: string, orgId: string, tx?: Knex) => {
|
||||||
|
try {
|
||||||
|
const docs = await (tx || db.replicaNode())(TableName.UserGroupMembership)
|
||||||
|
.where(`${TableName.UserGroupMembership}.userId`, userId)
|
||||||
|
.join(TableName.Groups, function () {
|
||||||
|
this.on(`${TableName.UserGroupMembership}.groupId`, "=", `${TableName.Groups}.id`).andOn(
|
||||||
|
`${TableName.Groups}.orgId`,
|
||||||
|
"=",
|
||||||
|
db.raw("?", [orgId])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.select(
|
||||||
|
db.ref("id").withSchema(TableName.Groups),
|
||||||
|
db.ref("name").withSchema(TableName.Groups),
|
||||||
|
db.ref("slug").withSchema(TableName.Groups),
|
||||||
|
db.ref("orgId").withSchema(TableName.Groups)
|
||||||
|
);
|
||||||
|
|
||||||
|
return docs;
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "FindByUserId" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// The GroupProjectMembership table has a reference to the project (projectId) AND the group (groupId).
|
// The GroupProjectMembership table has a reference to the project (projectId) AND the group (groupId).
|
||||||
// We need to join the GroupProjectMembership table with the Groups table to get the group name and slug.
|
// We need to join the GroupProjectMembership table with the Groups table to get the group name and slug.
|
||||||
// We also need to join the GroupProjectMembershipRole table to get the role of the group in the project.
|
// We also need to join the GroupProjectMembershipRole table to get the role of the group in the project.
|
||||||
@@ -128,7 +152,7 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.ProjectRoles}.id`
|
`${TableName.ProjectRoles}.id`
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.GroupProjectMembership),
|
db.ref("id").withSchema(TableName.UserGroupMembership),
|
||||||
db.ref("isGhost").withSchema(TableName.Users),
|
db.ref("isGhost").withSchema(TableName.Users),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("email").withSchema(TableName.Users),
|
db.ref("email").withSchema(TableName.Users),
|
||||||
@@ -197,5 +221,5 @@ export const groupProjectDALFactory = (db: TDbClient) => {
|
|||||||
return members;
|
return members;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...groupProjectOrm, findByProjectId, findAllProjectGroupMembers };
|
return { ...groupProjectOrm, findByProjectId, findByUserId, findAllProjectGroupMembers };
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
import picomatch from "picomatch";
|
||||||
|
|
||||||
|
export const doesFieldValueMatchOidcPolicy = (fieldValue: string, policyValue: string) =>
|
||||||
|
policyValue === fieldValue || picomatch.isMatch(fieldValue, policyValue);
|
@@ -28,6 +28,7 @@ import { TIdentityAccessTokenDALFactory } from "../identity-access-token/identit
|
|||||||
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
import { TIdentityAccessTokenJwtPayload } from "../identity-access-token/identity-access-token-types";
|
||||||
import { TOrgBotDALFactory } from "../org/org-bot-dal";
|
import { TOrgBotDALFactory } from "../org/org-bot-dal";
|
||||||
import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal";
|
import { TIdentityOidcAuthDALFactory } from "./identity-oidc-auth-dal";
|
||||||
|
import { doesFieldValueMatchOidcPolicy } from "./identity-oidc-auth-fns";
|
||||||
import {
|
import {
|
||||||
TAttachOidcAuthDTO,
|
TAttachOidcAuthDTO,
|
||||||
TGetOidcAuthDTO,
|
TGetOidcAuthDTO,
|
||||||
@@ -123,7 +124,7 @@ export const identityOidcAuthServiceFactory = ({
|
|||||||
}) as Record<string, string>;
|
}) as Record<string, string>;
|
||||||
|
|
||||||
if (identityOidcAuth.boundSubject) {
|
if (identityOidcAuth.boundSubject) {
|
||||||
if (tokenData.sub !== identityOidcAuth.boundSubject) {
|
if (!doesFieldValueMatchOidcPolicy(tokenData.sub, identityOidcAuth.boundSubject)) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Access denied: OIDC subject not allowed."
|
message: "Access denied: OIDC subject not allowed."
|
||||||
});
|
});
|
||||||
@@ -131,7 +132,11 @@ export const identityOidcAuthServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (identityOidcAuth.boundAudiences) {
|
if (identityOidcAuth.boundAudiences) {
|
||||||
if (!identityOidcAuth.boundAudiences.split(", ").includes(tokenData.aud)) {
|
if (
|
||||||
|
!identityOidcAuth.boundAudiences
|
||||||
|
.split(", ")
|
||||||
|
.some((policyValue) => doesFieldValueMatchOidcPolicy(tokenData.aud, policyValue))
|
||||||
|
) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Access denied: OIDC audience not allowed."
|
message: "Access denied: OIDC audience not allowed."
|
||||||
});
|
});
|
||||||
@@ -142,7 +147,9 @@ export const identityOidcAuthServiceFactory = ({
|
|||||||
Object.keys(identityOidcAuth.boundClaims).forEach((claimKey) => {
|
Object.keys(identityOidcAuth.boundClaims).forEach((claimKey) => {
|
||||||
const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey];
|
const claimValue = (identityOidcAuth.boundClaims as Record<string, string>)[claimKey];
|
||||||
// handle both single and multi-valued claims
|
// handle both single and multi-valued claims
|
||||||
if (!claimValue.split(", ").some((claimEntry) => tokenData[claimKey] === claimEntry)) {
|
if (
|
||||||
|
!claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(tokenData[claimKey], claimEntry))
|
||||||
|
) {
|
||||||
throw new ForbiddenRequestError({
|
throw new ForbiddenRequestError({
|
||||||
message: "Access denied: OIDC claim not allowed."
|
message: "Access denied: OIDC claim not allowed."
|
||||||
});
|
});
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName } from "@app/db/schemas";
|
import { TableName, TIdentities } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, sqlNestRelationships } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
|
import { ProjectIdentityOrderBy, TListProjectIdentityDTO } from "@app/services/identity-project/identity-project-types";
|
||||||
|
|
||||||
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
export type TIdentityProjectDALFactory = ReturnType<typeof identityProjectDALFactory>;
|
||||||
|
|
||||||
@@ -107,12 +109,45 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByProjectId = async (projectId: string, filter: { identityId?: string } = {}, tx?: Knex) => {
|
const findByProjectId = async (
|
||||||
|
projectId: string,
|
||||||
|
filter: { identityId?: string } & Pick<
|
||||||
|
TListProjectIdentityDTO,
|
||||||
|
"limit" | "offset" | "search" | "orderBy" | "orderDirection"
|
||||||
|
> = {},
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const docs = await (tx || db.replicaNode())(TableName.IdentityProjectMembership)
|
// TODO: scott - optimize, there's redundancy here with project membership and the below query
|
||||||
|
const fetchIdentitySubquery = (tx || db.replicaNode())(TableName.Identity)
|
||||||
|
.where((qb) => {
|
||||||
|
if (filter.search) {
|
||||||
|
void qb.whereILike(`${TableName.Identity}.name`, `%${filter.search}%`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join(
|
||||||
|
TableName.IdentityProjectMembership,
|
||||||
|
`${TableName.IdentityProjectMembership}.identityId`,
|
||||||
|
`${TableName.Identity}.id`
|
||||||
|
)
|
||||||
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
|
.orderBy(
|
||||||
|
`${TableName.Identity}.${filter.orderBy ?? ProjectIdentityOrderBy.Name}`,
|
||||||
|
filter.orderDirection ?? OrderByDirection.ASC
|
||||||
|
)
|
||||||
|
.select(selectAllTableCols(TableName.Identity))
|
||||||
|
.as(TableName.Identity); // required for subqueries
|
||||||
|
|
||||||
|
if (filter.limit) {
|
||||||
|
void fetchIdentitySubquery.offset(filter.offset ?? 0).limit(filter.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = (tx || db.replicaNode())(TableName.IdentityProjectMembership)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
.join<TIdentities, TIdentities>(fetchIdentitySubquery, (bd) => {
|
||||||
|
bd.on(`${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`);
|
||||||
|
})
|
||||||
.where((qb) => {
|
.where((qb) => {
|
||||||
if (filter.identityId) {
|
if (filter.identityId) {
|
||||||
void qb.where("identityId", filter.identityId);
|
void qb.where("identityId", filter.identityId);
|
||||||
@@ -154,6 +189,19 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("name").as("projectName").withSchema(TableName.Project)
|
db.ref("name").as("projectName").withSchema(TableName.Project)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// TODO: scott - joins seem to reorder identities so need to order again, for the sake of urgency will optimize at a later point
|
||||||
|
if (filter.orderBy) {
|
||||||
|
switch (filter.orderBy) {
|
||||||
|
case "name":
|
||||||
|
void query.orderBy(`${TableName.Identity}.${filter.orderBy}`, filter.orderDirection);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await query;
|
||||||
|
|
||||||
const members = sqlNestRelationships({
|
const members = sqlNestRelationships({
|
||||||
data: docs,
|
data: docs,
|
||||||
parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt, projectName }) => ({
|
parentMapper: ({ identityId, identityName, identityAuthMethod, id, createdAt, updatedAt, projectName }) => ({
|
||||||
@@ -208,9 +256,37 @@ export const identityProjectDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCountByProjectId = async (
|
||||||
|
projectId: string,
|
||||||
|
filter: { identityId?: string } & Pick<TListProjectIdentityDTO, "search"> = {},
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const identities = await (tx || db.replicaNode())(TableName.IdentityProjectMembership)
|
||||||
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
|
.join(TableName.Project, `${TableName.IdentityProjectMembership}.projectId`, `${TableName.Project}.id`)
|
||||||
|
.join(TableName.Identity, `${TableName.IdentityProjectMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
|
.where((qb) => {
|
||||||
|
if (filter.identityId) {
|
||||||
|
void qb.where("identityId", filter.identityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.search) {
|
||||||
|
void qb.whereILike(`${TableName.Identity}.name`, `%${filter.search}%`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
return Number(identities[0].count);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "GetCountByProjectId" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...identityProjectOrm,
|
...identityProjectOrm,
|
||||||
findByIdentityId,
|
findByIdentityId,
|
||||||
findByProjectId
|
findByProjectId,
|
||||||
|
getCountByProjectId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -268,7 +268,12 @@ export const identityProjectServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
orderBy,
|
||||||
|
orderDirection,
|
||||||
|
search
|
||||||
}: TListProjectIdentityDTO) => {
|
}: TListProjectIdentityDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
@@ -279,8 +284,17 @@ export const identityProjectServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Identity);
|
||||||
|
|
||||||
const identityMemberships = await identityProjectDAL.findByProjectId(projectId);
|
const identityMemberships = await identityProjectDAL.findByProjectId(projectId, {
|
||||||
return identityMemberships;
|
limit,
|
||||||
|
offset,
|
||||||
|
orderBy,
|
||||||
|
orderDirection,
|
||||||
|
search
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalCount = await identityProjectDAL.getCountByProjectId(projectId, { search });
|
||||||
|
|
||||||
|
return { identityMemberships, totalCount };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectIdentityByIdentityId = async ({
|
const getProjectIdentityByIdentityId = async ({
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { OrderByDirection, TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
import { ProjectUserMembershipTemporaryMode } from "../project-membership/project-membership-types";
|
||||||
|
|
||||||
@@ -40,8 +40,18 @@ export type TDeleteProjectIdentityDTO = {
|
|||||||
identityId: string;
|
identityId: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TListProjectIdentityDTO = TProjectPermission;
|
export type TListProjectIdentityDTO = {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
orderBy?: ProjectIdentityOrderBy;
|
||||||
|
orderDirection?: OrderByDirection;
|
||||||
|
search?: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetProjectIdentityByIdentityIdDTO = {
|
export type TGetProjectIdentityByIdentityIdDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
|
export enum ProjectIdentityOrderBy {
|
||||||
|
Name = "name"
|
||||||
|
}
|
||||||
|
@@ -42,7 +42,7 @@ export const identityUaClientSecretDALFactory = (db: TDbClient) => {
|
|||||||
})
|
})
|
||||||
.orWhere((qb) => {
|
.orWhere((qb) => {
|
||||||
void qb
|
void qb
|
||||||
.where("clientSecretNumUses", ">", 0)
|
.where("clientSecretNumUsesLimit", ">", 0)
|
||||||
.andWhere(
|
.andWhere(
|
||||||
"clientSecretNumUses",
|
"clientSecretNumUses",
|
||||||
">=",
|
">=",
|
||||||
|
@@ -4,6 +4,8 @@ import { TDbClient } from "@app/db";
|
|||||||
import { TableName, TIdentityOrgMemberships } from "@app/db/schemas";
|
import { TableName, TIdentityOrgMemberships } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
import { OrderByDirection } from "@app/lib/types";
|
||||||
|
import { TListOrgIdentitiesByOrgIdDTO } from "@app/services/identity/identity-types";
|
||||||
|
|
||||||
export type TIdentityOrgDALFactory = ReturnType<typeof identityOrgDALFactory>;
|
export type TIdentityOrgDALFactory = ReturnType<typeof identityOrgDALFactory>;
|
||||||
|
|
||||||
@@ -27,9 +29,20 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const find = async (filter: Partial<TIdentityOrgMemberships>, tx?: Knex) => {
|
const find = async (
|
||||||
|
{
|
||||||
|
limit,
|
||||||
|
offset = 0,
|
||||||
|
orderBy,
|
||||||
|
orderDirection = OrderByDirection.ASC,
|
||||||
|
search,
|
||||||
|
...filter
|
||||||
|
}: Partial<TIdentityOrgMemberships> &
|
||||||
|
Pick<TListOrgIdentitiesByOrgIdDTO, "offset" | "limit" | "orderBy" | "orderDirection" | "search">,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const docs = await (tx || db.replicaNode())(TableName.IdentityOrgMembership)
|
const query = (tx || db.replicaNode())(TableName.IdentityOrgMembership)
|
||||||
.where(filter)
|
.where(filter)
|
||||||
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
@@ -44,6 +57,30 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").as("identityId").withSchema(TableName.Identity))
|
.select(db.ref("id").as("identityId").withSchema(TableName.Identity))
|
||||||
.select(db.ref("name").as("identityName").withSchema(TableName.Identity))
|
.select(db.ref("name").as("identityName").withSchema(TableName.Identity))
|
||||||
.select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity));
|
.select(db.ref("authMethod").as("identityAuthMethod").withSchema(TableName.Identity));
|
||||||
|
|
||||||
|
if (limit) {
|
||||||
|
void query.offset(offset).limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderBy) {
|
||||||
|
switch (orderBy) {
|
||||||
|
case "name":
|
||||||
|
void query.orderBy(`${TableName.Identity}.${orderBy}`, orderDirection);
|
||||||
|
break;
|
||||||
|
case "role":
|
||||||
|
void query.orderBy(`${TableName.IdentityOrgMembership}.${orderBy}`, orderDirection);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search?.length) {
|
||||||
|
void query.whereILike(`${TableName.Identity}.name`, `%${search}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await query;
|
||||||
|
|
||||||
return docs.map(
|
return docs.map(
|
||||||
({
|
({
|
||||||
crId,
|
crId,
|
||||||
@@ -79,5 +116,27 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...identityOrgOrm, find, findOne };
|
const countAllOrgIdentities = async (
|
||||||
|
{ search, ...filter }: Partial<TIdentityOrgMemberships> & Pick<TListOrgIdentitiesByOrgIdDTO, "search">,
|
||||||
|
tx?: Knex
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const query = (tx || db.replicaNode())(TableName.IdentityOrgMembership)
|
||||||
|
.where(filter)
|
||||||
|
.join(TableName.Identity, `${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if (search?.length) {
|
||||||
|
void query.whereILike(`${TableName.Identity}.name`, `%${search}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const identities = await query;
|
||||||
|
|
||||||
|
return Number(identities[0].count);
|
||||||
|
} catch (error) {
|
||||||
|
throw new DatabaseError({ error, name: "countAllOrgIdentities" });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...identityOrgOrm, find, findOne, countAllOrgIdentities };
|
||||||
};
|
};
|
||||||
|
@@ -6,7 +6,6 @@ import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/pe
|
|||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
import { isAtLeastAsPrivileged } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
|
||||||
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
import { TIdentityProjectDALFactory } from "@app/services/identity-project/identity-project-dal";
|
||||||
|
|
||||||
import { ActorType } from "../auth/auth-type";
|
import { ActorType } from "../auth/auth-type";
|
||||||
@@ -16,6 +15,7 @@ import {
|
|||||||
TCreateIdentityDTO,
|
TCreateIdentityDTO,
|
||||||
TDeleteIdentityDTO,
|
TDeleteIdentityDTO,
|
||||||
TGetIdentityByIdDTO,
|
TGetIdentityByIdDTO,
|
||||||
|
TListOrgIdentitiesByOrgIdDTO,
|
||||||
TListProjectIdentitiesByIdentityIdDTO,
|
TListProjectIdentitiesByIdentityIdDTO,
|
||||||
TUpdateIdentityDTO
|
TUpdateIdentityDTO
|
||||||
} from "./identity-types";
|
} from "./identity-types";
|
||||||
@@ -58,7 +58,8 @@ export const identityServiceFactory = ({
|
|||||||
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged identity" });
|
if (!hasRequiredPriviledges) throw new BadRequestError({ message: "Failed to create a more privileged identity" });
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
|
||||||
|
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to create identity due to identity limit reached. Upgrade plan to create more identities."
|
message: "Failed to create identity due to identity limit reached. Upgrade plan to create more identities."
|
||||||
@@ -195,14 +196,36 @@ export const identityServiceFactory = ({
|
|||||||
return { ...deletedIdentity, orgId: identityOrgMembership.orgId };
|
return { ...deletedIdentity, orgId: identityOrgMembership.orgId };
|
||||||
};
|
};
|
||||||
|
|
||||||
const listOrgIdentities = async ({ orgId, actor, actorId, actorAuthMethod, actorOrgId }: TOrgPermission) => {
|
const listOrgIdentities = async ({
|
||||||
|
orgId,
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
orderBy,
|
||||||
|
orderDirection,
|
||||||
|
search
|
||||||
|
}: TListOrgIdentitiesByOrgIdDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
const identityMemberships = await identityOrgMembershipDAL.find({
|
const identityMemberships = await identityOrgMembershipDAL.find({
|
||||||
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId
|
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
orderBy,
|
||||||
|
orderDirection,
|
||||||
|
search
|
||||||
});
|
});
|
||||||
return identityMemberships;
|
|
||||||
|
const totalCount = await identityOrgMembershipDAL.countAllOrgIdentities({
|
||||||
|
[`${TableName.IdentityOrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
|
search
|
||||||
|
});
|
||||||
|
|
||||||
|
return { identityMemberships, totalCount };
|
||||||
};
|
};
|
||||||
|
|
||||||
const listProjectIdentitiesByIdentityId = async ({
|
const listProjectIdentitiesByIdentityId = async ({
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { IPType } from "@app/lib/ip";
|
import { IPType } from "@app/lib/ip";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { OrderByDirection, TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TCreateIdentityDTO = {
|
export type TCreateIdentityDTO = {
|
||||||
role: string;
|
role: string;
|
||||||
@@ -29,3 +29,16 @@ export interface TIdentityTrustedIp {
|
|||||||
export type TListProjectIdentitiesByIdentityIdDTO = {
|
export type TListProjectIdentitiesByIdentityIdDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
} & Omit<TOrgPermission, "orgId">;
|
} & Omit<TOrgPermission, "orgId">;
|
||||||
|
|
||||||
|
export type TListOrgIdentitiesByOrgIdDTO = {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
orderBy?: OrgIdentityOrderBy;
|
||||||
|
orderDirection?: OrderByDirection;
|
||||||
|
search?: string;
|
||||||
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export enum OrgIdentityOrderBy {
|
||||||
|
Name = "name",
|
||||||
|
Role = "role"
|
||||||
|
}
|
||||||
|
@@ -242,37 +242,12 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const octokit = new Octokit({
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
|
const repos = (await new Octokit({
|
||||||
auth: accessToken
|
auth: accessToken
|
||||||
});
|
}).paginate("GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}", {
|
||||||
|
per_page: 100
|
||||||
const getAllRepos = async () => {
|
})) as GitHubApp[];
|
||||||
let repos: GitHubApp[] = [];
|
|
||||||
let page = 1;
|
|
||||||
const perPage = 100;
|
|
||||||
let hasMore = true;
|
|
||||||
|
|
||||||
while (hasMore) {
|
|
||||||
const response = await octokit.request(
|
|
||||||
"GET /user/repos{?visibility,affiliation,type,sort,direction,per_page,page,since,before}",
|
|
||||||
{
|
|
||||||
per_page: perPage,
|
|
||||||
page
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if ((response.data as GitHubApp[]).length > 0) {
|
|
||||||
repos = repos.concat(response.data as GitHubApp[]);
|
|
||||||
page += 1;
|
|
||||||
} else {
|
|
||||||
hasMore = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return repos;
|
|
||||||
};
|
|
||||||
|
|
||||||
const repos = await getAllRepos();
|
|
||||||
|
|
||||||
const apps = repos
|
const apps = repos
|
||||||
.filter((a: GitHubApp) => a.permissions.admin === true)
|
.filter((a: GitHubApp) => a.permissions.admin === true)
|
||||||
@@ -460,16 +435,21 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
|
|||||||
*/
|
*/
|
||||||
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
|
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
|
||||||
const res = (
|
const res = (
|
||||||
await request.get<{ reponame: string }[]>(`${IntegrationUrls.CIRCLECI_API_URL}/v1.1/projects`, {
|
await request.get<{ reponame: string; username: string; vcs_url: string }[]>(
|
||||||
headers: {
|
`${IntegrationUrls.CIRCLECI_API_URL}/v1.1/projects`,
|
||||||
"Circle-Token": accessToken,
|
{
|
||||||
"Accept-Encoding": "application/json"
|
headers: {
|
||||||
|
"Circle-Token": accessToken,
|
||||||
|
"Accept-Encoding": "application/json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
const apps = res?.map((a) => ({
|
const apps = res.map((a) => ({
|
||||||
name: a?.reponame
|
owner: a.username, // username maps to unique organization name in CircleCI
|
||||||
|
name: a.reponame, // reponame maps to project name within an organization in CircleCI
|
||||||
|
appId: a.vcs_url.split("/").pop() // vcs_url maps to the project id in CircleCI
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return apps;
|
return apps;
|
||||||
|
@@ -207,6 +207,12 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!secrets[key].value) {
|
||||||
|
logger.warn(
|
||||||
|
`syncSecretsGcpsecretManager: create secret value in gcp where [key=${key}] and integration appId [appId=${integration.appId}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await request.post(
|
await request.post(
|
||||||
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||||
{
|
{
|
||||||
@@ -237,6 +243,12 @@ const syncSecretsGCPSecretManager = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else if (secrets[key].value !== res[key]) {
|
} else if (secrets[key].value !== res[key]) {
|
||||||
|
if (!secrets[key].value) {
|
||||||
|
logger.warn(
|
||||||
|
`syncSecretsGcpsecretManager: update secret value in gcp where [key=${key}] and integration appId [appId=${integration.appId}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await request.post(
|
await request.post(
|
||||||
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${integration.appId}/secrets/${key}:addVersion`,
|
||||||
{
|
{
|
||||||
@@ -567,8 +579,8 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
});
|
});
|
||||||
ssm.config.update(config);
|
ssm.config.update(config);
|
||||||
|
|
||||||
const metadata = z.record(z.any()).parse(integration.metadata || {});
|
const metadata = IntegrationMetadataSchema.parse(integration.metadata);
|
||||||
const awsParameterStoreSecretsObj: Record<string, AWS.SSM.Parameter> = {};
|
const awsParameterStoreSecretsObj: Record<string, AWS.SSM.Parameter & { KeyId?: string }> = {};
|
||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: integration sync triggered for ssm with [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [shouldDisableDelete=${metadata.shouldDisableDelete}]`
|
`getIntegrationSecrets: integration sync triggered for ssm with [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [shouldDisableDelete=${metadata.shouldDisableDelete}]`
|
||||||
);
|
);
|
||||||
@@ -598,18 +610,57 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
nextToken = parameters.NextToken;
|
nextToken = parameters.NextToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
let areParametersKmsKeysFetched = false;
|
||||||
`getIntegrationSecrets: all fetched keys from AWS SSM [projectId=${projectId}] [environment=${
|
|
||||||
integration.environment.slug
|
if (metadata.kmsKeyId) {
|
||||||
}] [secretPath=${integration.secretPath}] [awsParameterStoreSecretsObj=${Object.keys(
|
// we put this inside a try catch so that existing integrations without the ssm:DescribeParameters
|
||||||
awsParameterStoreSecretsObj
|
// AWS permission will not break
|
||||||
).join(",")}]`
|
try {
|
||||||
);
|
let hasNextDescribePage = true;
|
||||||
logger.info(
|
let describeNextToken: string | undefined;
|
||||||
`getIntegrationSecrets: all secrets from Infisical to send to AWS SSM [projectId=${projectId}] [environment=${
|
|
||||||
integration.environment.slug
|
while (hasNextDescribePage) {
|
||||||
}] [secretPath=${integration.secretPath}] [secrets=${Object.keys(secrets).join(",")}]`
|
const parameters = await ssm
|
||||||
);
|
.describeParameters({
|
||||||
|
MaxResults: 10,
|
||||||
|
NextToken: describeNextToken,
|
||||||
|
ParameterFilters: [
|
||||||
|
{
|
||||||
|
Key: "Path",
|
||||||
|
Option: "OneLevel",
|
||||||
|
Values: [integration.path as string]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
|
||||||
|
if (parameters.Parameters) {
|
||||||
|
parameters.Parameters.forEach((parameter) => {
|
||||||
|
if (parameter.Name) {
|
||||||
|
const secKey = parameter.Name.substring((integration.path as string).length);
|
||||||
|
awsParameterStoreSecretsObj[secKey].KeyId = parameter.KeyId;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
areParametersKmsKeysFetched = true;
|
||||||
|
hasNextDescribePage = Boolean(parameters.NextToken);
|
||||||
|
describeNextToken = parameters.NextToken;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
if ((error as any).code === "AccessDeniedException") {
|
||||||
|
logger.error(
|
||||||
|
`AWS Parameter Store Error [integration=${integration.id}]: double check AWS account permissions (refer to the Infisical docs)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
response = {
|
||||||
|
isSynced: false,
|
||||||
|
syncMessage: (error as AWSError)?.message || "Error syncing with AWS Parameter Store"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Identify secrets to create
|
// Identify secrets to create
|
||||||
// don't use Promise.all() and promise map here
|
// don't use Promise.all() and promise map here
|
||||||
// it will cause rate limit
|
// it will cause rate limit
|
||||||
@@ -620,7 +671,7 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
// -> create secret
|
// -> create secret
|
||||||
if (secrets[key].value) {
|
if (secrets[key].value) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: create secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
`getIntegrationSecrets: create secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
|
||||||
);
|
);
|
||||||
await ssm
|
await ssm
|
||||||
.putParameter({
|
.putParameter({
|
||||||
@@ -648,7 +699,7 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(
|
logger.error(
|
||||||
err,
|
err,
|
||||||
`getIntegrationSecrets: create secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
`getIntegrationSecrets: create secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if ((err as any).code === "AccessDeniedException") {
|
if ((err as any).code === "AccessDeniedException") {
|
||||||
@@ -667,16 +718,23 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
// case: secret exists in AWS parameter store
|
// case: secret exists in AWS parameter store
|
||||||
} else {
|
} else {
|
||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: update secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
`getIntegrationSecrets: update secret in AWS SSM for [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
|
||||||
);
|
);
|
||||||
// -> update secret
|
|
||||||
if (awsParameterStoreSecretsObj[key].Value !== secrets[key].value) {
|
const shouldUpdateKms =
|
||||||
|
areParametersKmsKeysFetched &&
|
||||||
|
Boolean(metadata.kmsKeyId) &&
|
||||||
|
awsParameterStoreSecretsObj[key].KeyId !== metadata.kmsKeyId;
|
||||||
|
|
||||||
|
// we ensure that the KMS key configured in the integration is applied for ALL parameters on AWS
|
||||||
|
if (shouldUpdateKms || awsParameterStoreSecretsObj[key].Value !== secrets[key].value) {
|
||||||
await ssm
|
await ssm
|
||||||
.putParameter({
|
.putParameter({
|
||||||
Name: `${integration.path}${key}`,
|
Name: `${integration.path}${key}`,
|
||||||
Type: "SecureString",
|
Type: "SecureString",
|
||||||
Value: secrets[key].value,
|
Value: secrets[key].value,
|
||||||
Overwrite: true
|
Overwrite: true,
|
||||||
|
...(metadata.kmsKeyId && { KeyId: metadata.kmsKeyId })
|
||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
}
|
}
|
||||||
@@ -698,7 +756,7 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(
|
logger.error(
|
||||||
err,
|
err,
|
||||||
`getIntegrationSecrets: update secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}]`
|
`getIntegrationSecrets: update secret in AWS SSM for failed [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}]`
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
if ((err as any).code === "AccessDeniedException") {
|
if ((err as any).code === "AccessDeniedException") {
|
||||||
@@ -728,11 +786,11 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
for (const key in awsParameterStoreSecretsObj) {
|
for (const key in awsParameterStoreSecretsObj) {
|
||||||
if (Object.hasOwn(awsParameterStoreSecretsObj, key)) {
|
if (Object.hasOwn(awsParameterStoreSecretsObj, key)) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=2]`
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=2]`
|
||||||
);
|
);
|
||||||
if (!(key in secrets)) {
|
if (!(key in secrets)) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=3]`
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=3]`
|
||||||
);
|
);
|
||||||
// case:
|
// case:
|
||||||
// -> delete secret
|
// -> delete secret
|
||||||
@@ -742,7 +800,7 @@ const syncSecretsAWSParameterStore = async ({
|
|||||||
})
|
})
|
||||||
.promise();
|
.promise();
|
||||||
logger.info(
|
logger.info(
|
||||||
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [key=${key}] [step=4]`
|
`getIntegrationSecrets: inside of shouldDisableDelete AWS SSM [projectId=${projectId}] [environment=${integration.environment.slug}] [secretPath=${integration.secretPath}] [step=4]`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
@@ -1929,22 +1987,62 @@ const syncSecretsCircleCI = async ({
|
|||||||
secrets: Record<string, { value: string; comment?: string }>;
|
secrets: Record<string, { value: string; comment?: string }>;
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
}) => {
|
}) => {
|
||||||
const circleciOrganizationDetail = (
|
const getProjectSlug = async () => {
|
||||||
await request.get(`${IntegrationUrls.CIRCLECI_API_URL}/v2/me/collaborations`, {
|
const requestConfig = {
|
||||||
headers: {
|
headers: {
|
||||||
"Circle-Token": accessToken,
|
"Circle-Token": accessToken,
|
||||||
"Accept-Encoding": "application/json"
|
"Accept-Encoding": "application/json"
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
).data[0];
|
|
||||||
|
|
||||||
const { slug } = circleciOrganizationDetail;
|
try {
|
||||||
|
const projectDetails = (
|
||||||
|
await request.get<{ slug: string }>(
|
||||||
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${integration.appId}`,
|
||||||
|
requestConfig
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
return projectDetails.slug;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof AxiosError) {
|
||||||
|
if (err.response?.data?.message !== "Not Found") {
|
||||||
|
throw new Error("Failed to get project slug from CircleCI during first attempt.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backwards compatibility with old CircleCI integrations where we don't keep track of the organization name, so we can't filter by organization
|
||||||
|
try {
|
||||||
|
const circleCiOrganization = (
|
||||||
|
await request.get<{ slug: string; name: string }[]>(
|
||||||
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/me/collaborations`,
|
||||||
|
requestConfig
|
||||||
|
)
|
||||||
|
).data;
|
||||||
|
|
||||||
|
// Case 1: This is a new integration where the organization name is stored under `integration.owner`
|
||||||
|
if (integration.owner) {
|
||||||
|
const org = circleCiOrganization.find((o) => o.name === integration.owner);
|
||||||
|
if (org) {
|
||||||
|
return `${org.slug}/${integration.app}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2: This is an old integration where the organization name is not stored, so we have to assume the first organization is the correct one
|
||||||
|
return `${circleCiOrganization[0].slug}/${integration.app}`;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error("Failed to get project slug from CircleCI during second attempt.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const projectSlug = await getProjectSlug();
|
||||||
|
|
||||||
// sync secrets to CircleCI
|
// sync secrets to CircleCI
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Object.keys(secrets).map(async (key) =>
|
Object.keys(secrets).map(async (key) =>
|
||||||
request.post(
|
request.post(
|
||||||
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${projectSlug}/envvar`,
|
||||||
{
|
{
|
||||||
name: key,
|
name: key,
|
||||||
value: secrets[key].value
|
value: secrets[key].value
|
||||||
@@ -1962,7 +2060,7 @@ const syncSecretsCircleCI = async ({
|
|||||||
// get secrets from CircleCI
|
// get secrets from CircleCI
|
||||||
const getSecretsRes = (
|
const getSecretsRes = (
|
||||||
await request.get<{ items: { name: string }[] }>(
|
await request.get<{ items: { name: string }[] }>(
|
||||||
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
|
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${projectSlug}/envvar`,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
"Circle-Token": accessToken,
|
"Circle-Token": accessToken,
|
||||||
@@ -1976,15 +2074,12 @@ const syncSecretsCircleCI = async ({
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
getSecretsRes.map(async (sec) => {
|
getSecretsRes.map(async (sec) => {
|
||||||
if (!(sec.name in secrets)) {
|
if (!(sec.name in secrets)) {
|
||||||
return request.delete(
|
return request.delete(`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${projectSlug}/envvar/${sec.name}`, {
|
||||||
`${IntegrationUrls.CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar/${sec.name}`,
|
headers: {
|
||||||
{
|
"Circle-Token": accessToken,
|
||||||
headers: {
|
"Content-Type": "application/json"
|
||||||
"Circle-Token": accessToken,
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@@ -208,6 +208,23 @@ export const kmsServiceFactory = ({
|
|||||||
return org.kmsDefaultKeyId;
|
return org.kmsDefaultKeyId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const encryptWithRootKey = async () => {
|
||||||
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
|
return ({ plainText }: { plainText: Buffer }) => {
|
||||||
|
const encryptedPlainTextBlob = cipher.encrypt(plainText, ROOT_ENCRYPTION_KEY);
|
||||||
|
|
||||||
|
return Promise.resolve({ cipherTextBlob: encryptedPlainTextBlob });
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const decryptWithRootKey = async () => {
|
||||||
|
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||||
|
return ({ cipherTextBlob }: { cipherTextBlob: Buffer }) => {
|
||||||
|
const decryptedBlob = cipher.decrypt(cipherTextBlob, ROOT_ENCRYPTION_KEY);
|
||||||
|
return Promise.resolve(decryptedBlob);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const decryptWithKmsKey = async ({
|
const decryptWithKmsKey = async ({
|
||||||
kmsId,
|
kmsId,
|
||||||
depth = 0
|
depth = 0
|
||||||
@@ -808,6 +825,8 @@ export const kmsServiceFactory = ({
|
|||||||
decryptWithKmsKey,
|
decryptWithKmsKey,
|
||||||
encryptWithInputKey,
|
encryptWithInputKey,
|
||||||
decryptWithInputKey,
|
decryptWithInputKey,
|
||||||
|
encryptWithRootKey,
|
||||||
|
decryptWithRootKey,
|
||||||
getOrgKmsKeyId,
|
getOrgKmsKeyId,
|
||||||
getProjectSecretManagerKmsKeyId,
|
getProjectSecretManagerKmsKeyId,
|
||||||
updateProjectSecretManagerKmsKey,
|
updateProjectSecretManagerKmsKey,
|
||||||
|
@@ -153,7 +153,6 @@ export const orgAdminServiceFactory = ({
|
|||||||
members: [
|
members: [
|
||||||
{
|
{
|
||||||
orgMembershipId: membership.id,
|
orgMembershipId: membership.id,
|
||||||
projectMembershipRole: ProjectMembershipRole.Admin,
|
|
||||||
userPublicKey: userEncryptionKey.publicKey
|
userPublicKey: userEncryptionKey.publicKey
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -9,22 +9,26 @@ import {
|
|||||||
OrgMembershipStatus,
|
OrgMembershipStatus,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectVersion,
|
ProjectVersion,
|
||||||
|
SecretKeyEncoding,
|
||||||
TableName,
|
TableName,
|
||||||
|
TProjectMemberships,
|
||||||
|
TProjectUserMembershipRolesInsert,
|
||||||
TUsers
|
TUsers
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TProjects } from "@app/db/schemas/projects";
|
import { TProjects } from "@app/db/schemas/projects";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
|
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
||||||
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
import { TProjectUserAdditionalPrivilegeDALFactory } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-dal";
|
||||||
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
import { TSamlConfigDALFactory } from "@app/ee/services/saml-config/saml-config-dal";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
import { generateAsymmetricKeyPair } from "@app/lib/crypto";
|
||||||
import { generateSymmetricKey, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
|
||||||
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
@@ -32,14 +36,14 @@ import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
|||||||
|
|
||||||
import { ActorAuthMethod, ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType, AuthMethod, AuthTokenType } from "../auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "../auth-token/auth-token-service";
|
||||||
import { TokenMetadataType, TokenType, TTokenMetadata } from "../auth-token/auth-token-types";
|
import { TokenType } from "../auth-token/auth-token-types";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { verifyProjectVersions } from "../project/project-fns";
|
import { assignWorkspaceKeysToMembers } from "../project/project-fns";
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
||||||
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
|
||||||
import { addMembersToProject } from "../project-membership/project-membership-fns";
|
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||||
|
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TIncidentContactsDALFactory } from "./incident-contacts-dal";
|
import { TIncidentContactsDALFactory } from "./incident-contacts-dal";
|
||||||
@@ -84,7 +88,7 @@ type TOrgServiceFactoryDep = {
|
|||||||
"getPlan" | "updateSubscriptionOrgMemberCount" | "generateOrgCustomerId" | "removeOrgCustomer"
|
"getPlan" | "updateSubscriptionOrgMemberCount" | "generateOrgCustomerId" | "removeOrgCustomer"
|
||||||
>;
|
>;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findUserGroupMembershipsInProject">;
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
|
||||||
};
|
};
|
||||||
@@ -108,8 +112,8 @@ export const orgServiceFactory = ({
|
|||||||
tokenService,
|
tokenService,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
|
projectRoleDAL,
|
||||||
samlConfigDAL,
|
samlConfigDAL,
|
||||||
userGroupMembershipDAL,
|
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectUserMembershipRoleDAL
|
projectUserMembershipRoleDAL
|
||||||
}: TOrgServiceFactoryDep) => {
|
}: TOrgServiceFactoryDep) => {
|
||||||
@@ -440,18 +444,17 @@ export const orgServiceFactory = ({
|
|||||||
*/
|
*/
|
||||||
const inviteUserToOrganization = async ({
|
const inviteUserToOrganization = async ({
|
||||||
orgId,
|
orgId,
|
||||||
userId,
|
actorId,
|
||||||
|
actor,
|
||||||
inviteeEmails,
|
inviteeEmails,
|
||||||
organizationRoleSlug,
|
organizationRoleSlug,
|
||||||
projectRoleSlug,
|
projects: invitedProjects,
|
||||||
projectIds,
|
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
}: TInviteUserToOrgDTO) => {
|
}: TInviteUserToOrgDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
|
||||||
|
|
||||||
const org = await orgDAL.findOrgById(orgId);
|
const org = await orgDAL.findOrgById(orgId);
|
||||||
|
|
||||||
@@ -461,219 +464,352 @@ export const orgServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isEmailInvalid = await isDisposableEmail(inviteeEmails);
|
||||||
|
if (isEmailInvalid) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Provided a disposable email",
|
||||||
|
name: "Org invite"
|
||||||
|
});
|
||||||
|
}
|
||||||
const plan = await licenseService.getPlan(orgId);
|
const plan = await licenseService.getPlan(orgId);
|
||||||
if (plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
if (plan?.slug !== "enterprise" && plan?.memberLimit && plan.membersUsed >= plan.memberLimit) {
|
||||||
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
// limit imposed on number of members allowed / number of members used exceeds the number of members allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
if (plan?.slug !== "enterprise" && plan?.identityLimit && plan.identitiesUsed >= plan.identityLimit) {
|
||||||
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
// limit imposed on number of identities allowed / number of identities used exceeds the number of identities allowed
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
message: "Failed to invite member due to member limit reached. Upgrade plan to invite more members."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projectIds?.length) {
|
const isCustomOrgRole = !Object.values(OrgMembershipRole).includes(organizationRoleSlug as OrgMembershipRole);
|
||||||
const projects = await projectDAL.find({
|
if (isCustomOrgRole) {
|
||||||
orgId,
|
if (!plan?.rbac)
|
||||||
$in: {
|
|
||||||
id: projectIds
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if its not v3, throw an error
|
|
||||||
if (!verifyProjectVersions(projects, ProjectVersion.V3)) {
|
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "One or more selected projects are not compatible with this operation. Please upgrade your projects."
|
message: "Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const inviteeUsers = await orgDAL.transaction(async (tx) => {
|
const projectsToInvite = invitedProjects?.length
|
||||||
const users: Pick<
|
? await projectDAL.find({
|
||||||
TUsers & { orgId: string },
|
orgId,
|
||||||
"id" | "firstName" | "lastName" | "email" | "orgId" | "username"
|
$in: {
|
||||||
>[] = [];
|
id: invitedProjects?.map(({ id }) => id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
if (projectsToInvite.length !== invitedProjects?.length) {
|
||||||
|
throw new UnauthorizedError({
|
||||||
|
message: "One or more project doesn't have access to"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectsToInvite.some((el) => el.version !== ProjectVersion.V3)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "One or more selected projects are not compatible with this operation. Please upgrade your projects."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const mailsForOrgInvitation: { email: string; userId: string; firstName: string; lastName: string }[] = [];
|
||||||
|
const mailsForProjectInvitaion: { email: string[]; projectName: string }[] = [];
|
||||||
|
const newProjectMemberships: TProjectMemberships[] = [];
|
||||||
|
await orgDAL.transaction(async (tx) => {
|
||||||
|
const users: Pick<TUsers, "id" | "firstName" | "lastName" | "email" | "username">[] = [];
|
||||||
|
|
||||||
for await (const inviteeEmail of inviteeEmails) {
|
for await (const inviteeEmail of inviteeEmails) {
|
||||||
const inviteeUser = await userDAL.findUserByUsername(inviteeEmail, tx);
|
let inviteeUser = await userDAL.findUserByUsername(inviteeEmail, tx);
|
||||||
|
|
||||||
if (inviteeUser) {
|
// if the user doesn't exist we create the user with the email
|
||||||
// if user already exist means its already part of infisical
|
if (!inviteeUser) {
|
||||||
// Thus the signup flow is not needed anymore
|
inviteeUser = await userDAL.create(
|
||||||
const [inviteeMembership] = await orgDAL.findMembership(
|
|
||||||
{
|
{
|
||||||
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
isAccepted: false,
|
||||||
[`${TableName.OrgMembership}.userId` as "userId"]: inviteeUser.id
|
email: inviteeEmail,
|
||||||
|
username: inviteeEmail,
|
||||||
|
authMethods: [AuthMethod.EMAIL],
|
||||||
|
isGhost: false
|
||||||
},
|
},
|
||||||
{ tx }
|
tx
|
||||||
);
|
);
|
||||||
if (inviteeMembership && inviteeMembership.status === OrgMembershipStatus.Accepted) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: `Failed to invite members because ${inviteeEmail} is already part of the organization`,
|
|
||||||
name: "Invite user to org"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inviteeMembership) {
|
|
||||||
await orgDAL.createMembership(
|
|
||||||
{
|
|
||||||
userId: inviteeUser.id,
|
|
||||||
inviteEmail: inviteeEmail,
|
|
||||||
orgId,
|
|
||||||
role: OrgMembershipRole.Member,
|
|
||||||
status: OrgMembershipStatus.Invited,
|
|
||||||
isActive: true
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (projectIds?.length) {
|
|
||||||
if (
|
|
||||||
organizationRoleSlug === OrgMembershipRole.Custom ||
|
|
||||||
projectRoleSlug === ProjectMembershipRole.Custom
|
|
||||||
) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Custom roles are not supported for inviting users to projects and organizations"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!projectRoleSlug) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Selecting a project role is required to invite users to projects"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await projectMembershipDAL.insertMany(
|
|
||||||
projectIds.map((id) => ({ projectId: id, userId: inviteeUser.id })),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
for await (const projectId of projectIds) {
|
|
||||||
await addMembersToProject({
|
|
||||||
orgDAL,
|
|
||||||
projectDAL,
|
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
projectBotDAL,
|
|
||||||
projectUserMembershipRoleDAL,
|
|
||||||
smtpService
|
|
||||||
}).addMembersToNonE2EEProject(
|
|
||||||
{
|
|
||||||
emails: [inviteeEmail],
|
|
||||||
usernames: [],
|
|
||||||
projectId,
|
|
||||||
projectMembershipRole: projectRoleSlug,
|
|
||||||
sendEmails: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
tx
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [{ ...inviteeUser, orgId }];
|
|
||||||
}
|
}
|
||||||
const isEmailInvalid = await isDisposableEmail(inviteeEmail);
|
|
||||||
if (isEmailInvalid) {
|
const inviteeUserId = inviteeUser?.id;
|
||||||
throw new BadRequestError({
|
const existingEncrytionKey = await userDAL.findUserEncKeyByUserId(inviteeUserId, tx);
|
||||||
message: "Provided a disposable email",
|
|
||||||
name: "Org invite"
|
// when user is missing the encrytion keys
|
||||||
|
// this could happen either if user doesn't exist or user didn't find step 3 of generating the encryption keys of srp
|
||||||
|
// So what we do is we generate a random secure password and then encrypt it with a random pub-private key
|
||||||
|
// Then when user sign in (as login is not possible as isAccepted is false) we rencrypt the private key with the user password
|
||||||
|
if (!inviteeUser || (inviteeUser && !inviteeUser?.isAccepted && !existingEncrytionKey)) {
|
||||||
|
const serverGeneratedPassword = crypto.randomBytes(32).toString("hex");
|
||||||
|
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(serverGeneratedPassword);
|
||||||
|
const encKeys = await generateUserSrpKeys(inviteeEmail, serverGeneratedPassword);
|
||||||
|
await userDAL.createUserEncryption(
|
||||||
|
{
|
||||||
|
userId: inviteeUserId,
|
||||||
|
encryptionVersion: 2,
|
||||||
|
protectedKey: encKeys.protectedKey,
|
||||||
|
protectedKeyIV: encKeys.protectedKeyIV,
|
||||||
|
protectedKeyTag: encKeys.protectedKeyTag,
|
||||||
|
publicKey: encKeys.publicKey,
|
||||||
|
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
||||||
|
iv: encKeys.encryptedPrivateKeyIV,
|
||||||
|
tag: encKeys.encryptedPrivateKeyTag,
|
||||||
|
salt: encKeys.salt,
|
||||||
|
verifier: encKeys.verifier,
|
||||||
|
serverEncryptedPrivateKeyEncoding: encoding,
|
||||||
|
serverEncryptedPrivateKeyTag: tag,
|
||||||
|
serverEncryptedPrivateKeyIV: iv,
|
||||||
|
serverEncryptedPrivateKey: ciphertext
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [inviteeMembership] = await orgDAL.findMembership(
|
||||||
|
{
|
||||||
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
|
[`${TableName.OrgMembership}.userId` as "userId"]: inviteeUserId
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
|
|
||||||
|
// if there exist no org membership we set is as given by the request
|
||||||
|
if (!inviteeMembership) {
|
||||||
|
// as its used by project invite also
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member);
|
||||||
|
let roleId;
|
||||||
|
const orgRole = isCustomOrgRole ? OrgMembershipRole.Custom : organizationRoleSlug;
|
||||||
|
if (isCustomOrgRole) {
|
||||||
|
const customRole = await orgRoleDAL.findOne({ slug: organizationRoleSlug, orgId });
|
||||||
|
if (!customRole)
|
||||||
|
throw new BadRequestError({ name: "Invite membership", message: "Organization role not found" });
|
||||||
|
roleId = customRole.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await orgDAL.createMembership(
|
||||||
|
{
|
||||||
|
userId: inviteeUser.id,
|
||||||
|
inviteEmail: inviteeEmail,
|
||||||
|
orgId,
|
||||||
|
role: orgRole,
|
||||||
|
status: OrgMembershipStatus.Invited,
|
||||||
|
isActive: true,
|
||||||
|
roleId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
mailsForOrgInvitation.push({
|
||||||
|
email: inviteeEmail,
|
||||||
|
userId: inviteeUser.id,
|
||||||
|
firstName: inviteeUser?.firstName || "",
|
||||||
|
lastName: inviteeUser.lastName || ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// not invited before
|
|
||||||
const user = await userDAL.create(
|
users.push(inviteeUser);
|
||||||
{
|
}
|
||||||
username: inviteeEmail,
|
|
||||||
email: inviteeEmail,
|
const userIds = users.map(({ id }) => id);
|
||||||
isAccepted: false,
|
const userEncryptionKeys = await userDAL.findUserEncKeyByUserIdsBatch({ userIds }, tx);
|
||||||
authMethods: [AuthMethod.EMAIL],
|
// we don't need to spam with email. Thus org invitation doesn't need project invitation again
|
||||||
isGhost: false
|
const userIdsWithOrgInvitation = new Set(mailsForOrgInvitation.map((el) => el.userId));
|
||||||
},
|
|
||||||
tx
|
// if there exist no project membership we set is as given by the request
|
||||||
|
for await (const project of projectsToInvite) {
|
||||||
|
const projectId = project.id;
|
||||||
|
const { permission: projectPermission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
);
|
);
|
||||||
await orgDAL.createMembership(
|
ForbiddenError.from(projectPermission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Create,
|
||||||
|
ProjectPermissionSub.Member
|
||||||
|
);
|
||||||
|
const existingMembers = await projectMembershipDAL.find(
|
||||||
{
|
{
|
||||||
inviteEmail: inviteeEmail,
|
projectId: project.id,
|
||||||
orgId,
|
$in: { userId: userIds }
|
||||||
userId: user.id,
|
|
||||||
role: organizationRoleSlug,
|
|
||||||
status: OrgMembershipStatus.Invited,
|
|
||||||
isActive: true
|
|
||||||
},
|
},
|
||||||
tx
|
{ tx }
|
||||||
|
);
|
||||||
|
const existingMembersGroupByUserId = groupBy(existingMembers, (i) => i.userId);
|
||||||
|
const userWithEncryptionKeyInvitedToProject = userEncryptionKeys.filter(
|
||||||
|
(user) => !existingMembersGroupByUserId?.[user.userId]
|
||||||
);
|
);
|
||||||
|
|
||||||
users.push({
|
// eslint-disable-next-line no-continue
|
||||||
...user,
|
if (!userWithEncryptionKeyInvitedToProject.length) continue;
|
||||||
orgId
|
|
||||||
|
// validate custom project role
|
||||||
|
const invitedProjectRoles = invitedProjects.find((el) => el.id === project.id)?.projectRoleSlug || [
|
||||||
|
ProjectMembershipRole.Member
|
||||||
|
];
|
||||||
|
|
||||||
|
const customProjectRoles = invitedProjectRoles.filter(
|
||||||
|
(role) => !Object.values(ProjectMembershipRole).includes(role as ProjectMembershipRole)
|
||||||
|
);
|
||||||
|
const hasCustomRole = Boolean(customProjectRoles.length);
|
||||||
|
if (hasCustomRole) {
|
||||||
|
if (!plan?.rbac)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to assign custom role due to RBAC restriction. Upgrade plan to assign custom role to member."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const customRoles = hasCustomRole
|
||||||
|
? await projectRoleDAL.find({
|
||||||
|
projectId,
|
||||||
|
$in: { slug: customProjectRoles.map((role) => role) }
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
if (customRoles.length !== customProjectRoles.length)
|
||||||
|
throw new BadRequestError({ message: "Custom role not found" });
|
||||||
|
|
||||||
|
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||||
|
|
||||||
|
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
|
||||||
|
if (!ghostUser) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find sudo user"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, tx);
|
||||||
|
if (!ghostUserLatestKey) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find sudo user latest key"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
||||||
|
if (!bot) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Failed to find bot"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const botPrivateKey = infisicalSymmetricDecrypt({
|
||||||
|
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
||||||
|
iv: bot.iv,
|
||||||
|
tag: bot.tag,
|
||||||
|
ciphertext: bot.encryptedPrivateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
const newWsMembers = assignWorkspaceKeysToMembers({
|
||||||
|
decryptKey: ghostUserLatestKey,
|
||||||
|
userPrivateKey: botPrivateKey,
|
||||||
|
members: userWithEncryptionKeyInvitedToProject.map((userEnc) => ({
|
||||||
|
orgMembershipId: userEnc.userId,
|
||||||
|
projectMembershipRole: ProjectMembershipRole.Admin,
|
||||||
|
userPublicKey: userEnc.publicKey
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
const projectMemberships = await projectMembershipDAL.insertMany(
|
||||||
|
userWithEncryptionKeyInvitedToProject.map((userEnc) => ({
|
||||||
|
projectId,
|
||||||
|
userId: userEnc.userId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
newProjectMemberships.push(...projectMemberships);
|
||||||
|
|
||||||
|
const sanitizedProjectMembershipRoles: TProjectUserMembershipRolesInsert[] = [];
|
||||||
|
invitedProjectRoles.forEach((projectRole) => {
|
||||||
|
const isCustomRole = Boolean(customRolesGroupBySlug?.[projectRole]?.[0]);
|
||||||
|
projectMemberships.forEach((membership) => {
|
||||||
|
sanitizedProjectMembershipRoles.push({
|
||||||
|
projectMembershipId: membership.id,
|
||||||
|
role: isCustomRole ? ProjectMembershipRole.Custom : projectRole,
|
||||||
|
customRoleId: customRolesGroupBySlug[projectRole] ? customRolesGroupBySlug[projectRole][0].id : null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await projectUserMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
||||||
|
|
||||||
|
await projectKeyDAL.insertMany(
|
||||||
|
newWsMembers.map((el) => ({
|
||||||
|
encryptedKey: el.workspaceEncryptedKey,
|
||||||
|
nonce: el.workspaceEncryptedNonce,
|
||||||
|
senderId: ghostUser.id,
|
||||||
|
receiverId: el.orgMembershipId,
|
||||||
|
projectId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
mailsForProjectInvitaion.push({
|
||||||
|
email: userWithEncryptionKeyInvitedToProject
|
||||||
|
.filter((el) => !userIdsWithOrgInvitation.has(el.userId))
|
||||||
|
.map((el) => el.email || el.username),
|
||||||
|
projectName: project.name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return users;
|
return users;
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = await userDAL.findById(userId);
|
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
||||||
|
|
||||||
const signupTokens: { email: string; link: string }[] = [];
|
const signupTokens: { email: string; link: string }[] = [];
|
||||||
if (inviteeUsers) {
|
// send org invite mail
|
||||||
for await (const invitee of inviteeUsers) {
|
await Promise.allSettled(
|
||||||
|
mailsForOrgInvitation.map(async (el) => {
|
||||||
const token = await tokenService.createTokenForUser({
|
const token = await tokenService.createTokenForUser({
|
||||||
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
|
||||||
userId: invitee.id,
|
userId: el.userId,
|
||||||
orgId
|
orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
let inviteMetadata: string = "";
|
|
||||||
if (projectIds && projectIds?.length > 0) {
|
|
||||||
inviteMetadata = jwt.sign(
|
|
||||||
{
|
|
||||||
type: TokenMetadataType.InviteToProjects,
|
|
||||||
payload: {
|
|
||||||
projectIds,
|
|
||||||
projectRoleSlug: projectRoleSlug!, // Implicitly checked inside transaction if projectRoleSlug is undefined
|
|
||||||
userId: invitee.id,
|
|
||||||
orgId
|
|
||||||
}
|
|
||||||
} satisfies TTokenMetadata,
|
|
||||||
appCfg.AUTH_SECRET,
|
|
||||||
{
|
|
||||||
expiresIn: appCfg.JWT_INVITE_LIFETIME
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
signupTokens.push({
|
signupTokens.push({
|
||||||
email: invitee.email || invitee.username,
|
email: el.email,
|
||||||
link: `${appCfg.SITE_URL}/signupinvite?token=${token}${
|
link: `${appCfg.SITE_URL}/signupinvite?token=${token}&to=${el.email}&organization_id=${org?.id}`
|
||||||
inviteMetadata ? `&metadata=${inviteMetadata}` : ""
|
|
||||||
}&to=${invitee.email || invitee.username}&organization_id=${org?.id}`
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await smtpService.sendMail({
|
return smtpService.sendMail({
|
||||||
template: SmtpTemplates.OrgInvite,
|
template: SmtpTemplates.OrgInvite,
|
||||||
subjectLine: "Infisical organization invitation",
|
subjectLine: "Infisical organization invitation",
|
||||||
recipients: [invitee.email || invitee.username],
|
recipients: [el.email],
|
||||||
substitutions: {
|
substitutions: {
|
||||||
metadata: inviteMetadata,
|
inviterFirstName: el.firstName,
|
||||||
inviterFirstName: user.firstName,
|
inviterUsername: el.email,
|
||||||
inviterUsername: user.username,
|
|
||||||
organizationName: org?.name,
|
organizationName: org?.name,
|
||||||
email: invitee.email || invitee.username,
|
email: el.email,
|
||||||
organizationId: org?.id.toString(),
|
organizationId: org?.id.toString(),
|
||||||
token,
|
token,
|
||||||
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
callback_url: `${appCfg.SITE_URL}/signupinvite`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
})
|
||||||
}
|
);
|
||||||
await licenseService.updateSubscriptionOrgMemberCount(orgId);
|
|
||||||
|
await Promise.allSettled(
|
||||||
|
mailsForProjectInvitaion
|
||||||
|
.filter((el) => Boolean(el.email.length))
|
||||||
|
.map(async (el) => {
|
||||||
|
return smtpService.sendMail({
|
||||||
|
template: SmtpTemplates.WorkspaceInvite,
|
||||||
|
subjectLine: "Infisical project invitation",
|
||||||
|
recipients: el.email,
|
||||||
|
substitutions: {
|
||||||
|
workspaceName: el.projectName,
|
||||||
|
callback_url: `${appCfg.SITE_URL}/login`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
if (!appCfg.isSmtpConfigured) {
|
if (!appCfg.isSmtpConfigured) {
|
||||||
return signupTokens;
|
return { signupTokens, projectMemberships: newProjectMemberships };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { signupTokens: undefined, projectMemberships: newProjectMemberships };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { OrgMembershipRole, ProjectMembershipRole } from "@app/db/schemas";
|
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||||
@@ -26,14 +25,17 @@ export type TDeleteOrgMembershipDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TInviteUserToOrgDTO = {
|
export type TInviteUserToOrgDTO = {
|
||||||
userId: string;
|
actorId: string;
|
||||||
|
actor: ActorType;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
actorOrgId: string | undefined;
|
actorOrgId: string | undefined;
|
||||||
actorAuthMethod: ActorAuthMethod;
|
actorAuthMethod: ActorAuthMethod;
|
||||||
inviteeEmails: string[];
|
inviteeEmails: string[];
|
||||||
organizationRoleSlug: OrgMembershipRole;
|
organizationRoleSlug: string;
|
||||||
projectIds?: string[];
|
projects?: {
|
||||||
projectRoleSlug?: ProjectMembershipRole;
|
id: string;
|
||||||
|
projectRoleSlug?: string[];
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TVerifyUserToOrgDTO = {
|
export type TVerifyUserToOrgDTO = {
|
||||||
|
@@ -26,7 +26,10 @@ export const getBotKeyFnFactory = (
|
|||||||
) => {
|
) => {
|
||||||
const getBotKeyFn = async (projectId: string) => {
|
const getBotKeyFn = async (projectId: string) => {
|
||||||
const project = await projectDAL.findById(projectId);
|
const project = await projectDAL.findById(projectId);
|
||||||
if (!project) throw new BadRequestError({ message: "Project not found during bot lookup." });
|
if (!project)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Project not found during bot lookup. Are you sure you are using the correct project ID?"
|
||||||
|
});
|
||||||
|
|
||||||
if (project.version === 3) {
|
if (project.version === 3) {
|
||||||
return { project, shouldUseSecretV2Bridge: true };
|
return { project, shouldUseSecretV2Bridge: true };
|
||||||
|
@@ -1,190 +0,0 @@
|
|||||||
import { Knex } from "knex";
|
|
||||||
|
|
||||||
import { ProjectMembershipRole, SecretKeyEncoding, TProjectMemberships } from "@app/db/schemas";
|
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
|
||||||
import { groupBy } from "@app/lib/fn";
|
|
||||||
|
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
|
||||||
import { assignWorkspaceKeysToMembers } from "../project/project-fns";
|
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
|
||||||
import { TProjectKeyDALFactory } from "../project-key/project-key-dal";
|
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
|
||||||
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "./project-user-membership-role-dal";
|
|
||||||
|
|
||||||
type TAddMembersToProjectArg = {
|
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "findOrgMembersByUsername">;
|
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectById" | "findProjectGhostUser">;
|
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "insertMany">;
|
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
|
||||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "findUserGroupMembershipsInProject">;
|
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany">;
|
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AddMembersToNonE2EEProjectDTO = {
|
|
||||||
emails: string[];
|
|
||||||
usernames: string[];
|
|
||||||
projectId: string;
|
|
||||||
projectMembershipRole: ProjectMembershipRole;
|
|
||||||
sendEmails?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type AddMembersToNonE2EEProjectOptions = {
|
|
||||||
tx?: Knex;
|
|
||||||
throwOnProjectNotFound?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addMembersToProject = ({
|
|
||||||
orgDAL,
|
|
||||||
projectDAL,
|
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
|
||||||
projectBotDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
projectUserMembershipRoleDAL,
|
|
||||||
smtpService
|
|
||||||
}: TAddMembersToProjectArg) => {
|
|
||||||
// Can create multiple memberships for a singular project, based on user email / username
|
|
||||||
const addMembersToNonE2EEProject = async (
|
|
||||||
{ emails, usernames, projectId, projectMembershipRole, sendEmails }: AddMembersToNonE2EEProjectDTO,
|
|
||||||
options: AddMembersToNonE2EEProjectOptions = { throwOnProjectNotFound: true }
|
|
||||||
) => {
|
|
||||||
const processTransaction = async (tx: Knex) => {
|
|
||||||
const usernamesAndEmails = [...emails, ...usernames];
|
|
||||||
|
|
||||||
const project = await projectDAL.findProjectById(projectId);
|
|
||||||
if (!project) {
|
|
||||||
if (options.throwOnProjectNotFound) {
|
|
||||||
throw new BadRequestError({ message: "Project not found when attempting to add user to project" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const orgMembers = await orgDAL.findOrgMembersByUsername(
|
|
||||||
project.orgId,
|
|
||||||
[...new Set(usernamesAndEmails.map((element) => element.toLowerCase()))],
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (orgMembers.length !== usernamesAndEmails.length)
|
|
||||||
throw new BadRequestError({ message: "Some users are not part of org" });
|
|
||||||
|
|
||||||
if (!orgMembers.length) return [];
|
|
||||||
|
|
||||||
const existingMembers = await projectMembershipDAL.find({
|
|
||||||
projectId,
|
|
||||||
$in: { userId: orgMembers.map(({ user }) => user.id).filter(Boolean) }
|
|
||||||
});
|
|
||||||
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
|
|
||||||
|
|
||||||
const ghostUser = await projectDAL.findProjectGhostUser(projectId);
|
|
||||||
|
|
||||||
if (!ghostUser) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Failed to find sudo user"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId);
|
|
||||||
|
|
||||||
if (!ghostUserLatestKey) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Failed to find sudo user latest key"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const bot = await projectBotDAL.findOne({ projectId });
|
|
||||||
if (!bot) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "Failed to find bot"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const botPrivateKey = infisicalSymmetricDecrypt({
|
|
||||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
|
||||||
iv: bot.iv,
|
|
||||||
tag: bot.tag,
|
|
||||||
ciphertext: bot.encryptedPrivateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const newWsMembers = assignWorkspaceKeysToMembers({
|
|
||||||
decryptKey: ghostUserLatestKey,
|
|
||||||
userPrivateKey: botPrivateKey,
|
|
||||||
members: orgMembers.map((membership) => ({
|
|
||||||
orgMembershipId: membership.id,
|
|
||||||
projectMembershipRole,
|
|
||||||
userPublicKey: membership.user.publicKey
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
|
|
||||||
const members: TProjectMemberships[] = [];
|
|
||||||
|
|
||||||
const userIdsToExcludeForProjectKeyAddition = new Set(
|
|
||||||
await userGroupMembershipDAL.findUserGroupMembershipsInProject(usernamesAndEmails, projectId)
|
|
||||||
);
|
|
||||||
const projectMemberships = await projectMembershipDAL.insertMany(
|
|
||||||
orgMembers.map(({ user }) => ({
|
|
||||||
projectId,
|
|
||||||
userId: user.id
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
await projectUserMembershipRoleDAL.insertMany(
|
|
||||||
projectMemberships.map(({ id }) => ({ projectMembershipId: id, role: projectMembershipRole })),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
members.push(...projectMemberships);
|
|
||||||
|
|
||||||
const encKeyGroupByOrgMembId = groupBy(newWsMembers, (i) => i.orgMembershipId);
|
|
||||||
await projectKeyDAL.insertMany(
|
|
||||||
orgMembers
|
|
||||||
.filter(({ user }) => !userIdsToExcludeForProjectKeyAddition.has(user.id))
|
|
||||||
.map(({ user, id }) => ({
|
|
||||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
|
||||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
|
||||||
senderId: ghostUser.id,
|
|
||||||
receiverId: user.id,
|
|
||||||
projectId
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
if (sendEmails) {
|
|
||||||
const recipients = orgMembers.filter((i) => i.user.email).map((i) => i.user.email as string);
|
|
||||||
|
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
if (recipients.length) {
|
|
||||||
await smtpService.sendMail({
|
|
||||||
template: SmtpTemplates.WorkspaceInvite,
|
|
||||||
subjectLine: "Infisical project invitation",
|
|
||||||
recipients: orgMembers.filter((i) => i.user.email).map((i) => i.user.email as string),
|
|
||||||
substitutions: {
|
|
||||||
workspaceName: project.name,
|
|
||||||
callback_url: `${appCfg.SITE_URL}/login`
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return members;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.tx) {
|
|
||||||
return processTransaction(options.tx);
|
|
||||||
}
|
|
||||||
return projectMembershipDAL.transaction(processTransaction);
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
addMembersToNonE2EEProject
|
|
||||||
};
|
|
||||||
};
|
|
@@ -22,11 +22,9 @@ import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
|||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
||||||
import { addMembersToProject } from "./project-membership-fns";
|
|
||||||
import {
|
import {
|
||||||
ProjectUserMembershipTemporaryMode,
|
ProjectUserMembershipTemporaryMode,
|
||||||
TAddUsersToWorkspaceDTO,
|
TAddUsersToWorkspaceDTO,
|
||||||
TAddUsersToWorkspaceNonE2EEDTO,
|
|
||||||
TDeleteProjectMembershipOldDTO,
|
TDeleteProjectMembershipOldDTO,
|
||||||
TDeleteProjectMembershipsDTO,
|
TDeleteProjectMembershipsDTO,
|
||||||
TGetProjectMembershipByUsernameDTO,
|
TGetProjectMembershipByUsernameDTO,
|
||||||
@@ -44,7 +42,7 @@ type TProjectMembershipServiceFactoryDep = {
|
|||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "find" | "delete">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "find" | "delete">;
|
||||||
userDAL: Pick<TUserDALFactory, "findById" | "findOne" | "findUserByProjectMembershipId" | "find">;
|
userDAL: Pick<TUserDALFactory, "findById" | "findOne" | "findUserByProjectMembershipId" | "find">;
|
||||||
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
userGroupMembershipDAL: TUserGroupMembershipDALFactory;
|
||||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find" | "findOne">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findMembership" | "findOrgMembersByUsername">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership" | "findOrgMembersByUsername">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser" | "transaction" | "findProjectById">;
|
projectDAL: Pick<TProjectDALFactory, "findById" | "findProjectGhostUser" | "transaction" | "findProjectById">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||||
@@ -61,7 +59,6 @@ export const projectMembershipServiceFactory = ({
|
|||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
smtpService,
|
smtpService,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
projectBotDAL,
|
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
projectUserAdditionalPrivilegeDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
@@ -93,15 +90,20 @@ export const projectMembershipServiceFactory = ({
|
|||||||
// projectMembers[0].project
|
// projectMembers[0].project
|
||||||
if (includeGroupMembers) {
|
if (includeGroupMembers) {
|
||||||
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
|
const groupMembers = await groupProjectDAL.findAllProjectGroupMembers(projectId);
|
||||||
|
|
||||||
const allMembers = [
|
const allMembers = [
|
||||||
...projectMembers.map((m) => ({ ...m, isGroupMember: false })),
|
...projectMembers.map((m) => ({ ...m, isGroupMember: false })),
|
||||||
...groupMembers.map((m) => ({ ...m, isGroupMember: true }))
|
...groupMembers.map((m) => ({ ...m, isGroupMember: true }))
|
||||||
];
|
];
|
||||||
|
|
||||||
// Ensure the userId is unique
|
// Ensure the userId is unique
|
||||||
const membersIds = new Set(allMembers.map((entity) => entity.user.id));
|
const uniqueMembers: typeof allMembers = [];
|
||||||
const uniqueMembers = allMembers.filter((entity) => membersIds.has(entity.user.id));
|
const addedUserIds = new Set<string>();
|
||||||
|
allMembers.forEach((member) => {
|
||||||
|
if (!addedUserIds.has(member.user.id)) {
|
||||||
|
uniqueMembers.push(member);
|
||||||
|
addedUserIds.add(member.user.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return uniqueMembers;
|
return uniqueMembers;
|
||||||
}
|
}
|
||||||
@@ -214,52 +216,6 @@ export const projectMembershipServiceFactory = ({
|
|||||||
return orgMembers;
|
return orgMembers;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addUsersToProjectNonE2EE = async ({
|
|
||||||
projectId,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actor,
|
|
||||||
actorOrgId,
|
|
||||||
emails,
|
|
||||||
usernames,
|
|
||||||
sendEmails = true
|
|
||||||
}: TAddUsersToWorkspaceNonE2EEDTO) => {
|
|
||||||
const project = await projectDAL.findById(projectId);
|
|
||||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
|
||||||
|
|
||||||
if (project.version === ProjectVersion.V1) {
|
|
||||||
throw new BadRequestError({ message: "Please upgrade your project on your dashboard" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
projectId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId
|
|
||||||
);
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
|
||||||
|
|
||||||
const members = await addMembersToProject({
|
|
||||||
orgDAL,
|
|
||||||
projectDAL,
|
|
||||||
projectMembershipDAL,
|
|
||||||
projectKeyDAL,
|
|
||||||
userGroupMembershipDAL,
|
|
||||||
projectBotDAL,
|
|
||||||
projectUserMembershipRoleDAL,
|
|
||||||
smtpService
|
|
||||||
}).addMembersToNonE2EEProject({
|
|
||||||
emails,
|
|
||||||
usernames,
|
|
||||||
projectId,
|
|
||||||
projectMembershipRole: ProjectMembershipRole.Member,
|
|
||||||
sendEmails
|
|
||||||
});
|
|
||||||
|
|
||||||
return members;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateProjectMembership = async ({
|
const updateProjectMembership = async ({
|
||||||
actorId,
|
actorId,
|
||||||
actor,
|
actor,
|
||||||
@@ -530,7 +486,6 @@ export const projectMembershipServiceFactory = ({
|
|||||||
getProjectMemberships,
|
getProjectMemberships,
|
||||||
getProjectMembershipByUsername,
|
getProjectMembershipByUsername,
|
||||||
updateProjectMembership,
|
updateProjectMembership,
|
||||||
addUsersToProjectNonE2EE,
|
|
||||||
deleteProjectMemberships,
|
deleteProjectMemberships,
|
||||||
deleteProjectMembership, // TODO: Remove this
|
deleteProjectMembership, // TODO: Remove this
|
||||||
addUsersToProject,
|
addUsersToProject,
|
||||||
|
@@ -53,4 +53,5 @@ export type TAddUsersToWorkspaceNonE2EEDTO = {
|
|||||||
sendEmails?: boolean;
|
sendEmails?: boolean;
|
||||||
emails: string[];
|
emails: string[];
|
||||||
usernames: string[];
|
usernames: string[];
|
||||||
|
roleSlugs?: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
@@ -7,7 +7,8 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import {
|
import {
|
||||||
ProjectPermissionActions,
|
ProjectPermissionActions,
|
||||||
ProjectPermissionSet,
|
ProjectPermissionSet,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub,
|
||||||
|
validateProjectPermissions
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -56,6 +57,9 @@ export const projectRoleServiceFactory = ({
|
|||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Role);
|
||||||
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
|
const existingRole = await projectRoleDAL.findOne({ slug: data.slug, projectId });
|
||||||
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
|
if (existingRole) throw new BadRequestError({ name: "Create Role", message: "Duplicate role" });
|
||||||
|
|
||||||
|
validateProjectPermissions(data.permissions);
|
||||||
|
|
||||||
const role = await projectRoleDAL.create({
|
const role = await projectRoleDAL.create({
|
||||||
...data,
|
...data,
|
||||||
projectId
|
projectId
|
||||||
@@ -120,6 +124,11 @@ export const projectRoleServiceFactory = ({
|
|||||||
if (existingRole && existingRole.id !== roleId)
|
if (existingRole && existingRole.id !== roleId)
|
||||||
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
|
throw new BadRequestError({ name: "Update Role", message: "Duplicate role" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.permissions) {
|
||||||
|
validateProjectPermissions(data.permissions);
|
||||||
|
}
|
||||||
|
|
||||||
const [updatedRole] = await projectRoleDAL.update(
|
const [updatedRole] = await projectRoleDAL.update(
|
||||||
{ id: roleId, projectId },
|
{ id: roleId, projectId },
|
||||||
{
|
{
|
||||||
|
@@ -16,7 +16,7 @@ export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateK
|
|||||||
privateKey: userPrivateKey
|
privateKey: userPrivateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const newWsMembers = members.map(({ orgMembershipId, userPublicKey, projectMembershipRole }) => {
|
const newWsMembers = members.map(({ orgMembershipId, userPublicKey }) => {
|
||||||
const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = encryptAsymmetric(
|
const { ciphertext: inviteeCipherText, nonce: inviteeNonce } = encryptAsymmetric(
|
||||||
plaintextProjectKey,
|
plaintextProjectKey,
|
||||||
userPublicKey,
|
userPublicKey,
|
||||||
@@ -25,7 +25,6 @@ export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateK
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
orgMembershipId,
|
orgMembershipId,
|
||||||
projectRole: projectMembershipRole,
|
|
||||||
workspaceEncryptedKey: inviteeCipherText,
|
workspaceEncryptedKey: inviteeCipherText,
|
||||||
workspaceEncryptedNonce: inviteeNonce
|
workspaceEncryptedNonce: inviteeNonce
|
||||||
};
|
};
|
||||||
|
@@ -300,8 +300,7 @@ export const projectQueueFactory = ({
|
|||||||
members: [
|
members: [
|
||||||
{
|
{
|
||||||
userPublicKey: user.publicKey,
|
userPublicKey: user.publicKey,
|
||||||
orgMembershipId: orgMembership.id,
|
orgMembershipId: orgMembership.id
|
||||||
projectMembershipRole: ProjectMembershipRole.Admin
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@@ -34,6 +34,8 @@ import { TProjectUserMembershipRoleDALFactory } from "../project-membership/proj
|
|||||||
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||||
import { getPredefinedRoles } from "../project-role/project-role-fns";
|
import { getPredefinedRoles } from "../project-role/project-role-fns";
|
||||||
import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TProjectSlackConfigDALFactory } from "../slack/project-slack-config-dal";
|
||||||
|
import { TSlackIntegrationDALFactory } from "../slack/slack-integration-dal";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectDALFactory } from "./project-dal";
|
import { TProjectDALFactory } from "./project-dal";
|
||||||
import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns";
|
import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns";
|
||||||
@@ -43,6 +45,7 @@ import {
|
|||||||
TDeleteProjectDTO,
|
TDeleteProjectDTO,
|
||||||
TGetProjectDTO,
|
TGetProjectDTO,
|
||||||
TGetProjectKmsKey,
|
TGetProjectKmsKey,
|
||||||
|
TGetProjectSlackConfig,
|
||||||
TListProjectAlertsDTO,
|
TListProjectAlertsDTO,
|
||||||
TListProjectCasDTO,
|
TListProjectCasDTO,
|
||||||
TListProjectCertificateTemplatesDTO,
|
TListProjectCertificateTemplatesDTO,
|
||||||
@@ -54,6 +57,7 @@ import {
|
|||||||
TUpdateProjectDTO,
|
TUpdateProjectDTO,
|
||||||
TUpdateProjectKmsDTO,
|
TUpdateProjectKmsDTO,
|
||||||
TUpdateProjectNameDTO,
|
TUpdateProjectNameDTO,
|
||||||
|
TUpdateProjectSlackConfig,
|
||||||
TUpdateProjectVersionLimitDTO,
|
TUpdateProjectVersionLimitDTO,
|
||||||
TUpgradeProjectDTO
|
TUpgradeProjectDTO
|
||||||
} from "./project-types";
|
} from "./project-types";
|
||||||
@@ -76,6 +80,8 @@ type TProjectServiceFactoryDep = {
|
|||||||
identityProjectMembershipRoleDAL: Pick<TIdentityProjectMembershipRoleDALFactory, "create">;
|
identityProjectMembershipRoleDAL: Pick<TIdentityProjectMembershipRoleDALFactory, "create">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "create" | "findProjectGhostUser" | "findOne">;
|
||||||
|
projectSlackConfigDAL: Pick<TProjectSlackConfigDALFactory, "findOne" | "transaction" | "updateById" | "create">;
|
||||||
|
slackIntegrationDAL: Pick<TSlackIntegrationDALFactory, "findById" | "findByIdWithWorkflowIntegrationDetails">;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
certificateDAL: Pick<TCertificateDALFactory, "find" | "countCertificatesInProject">;
|
||||||
@@ -126,7 +132,9 @@ export const projectServiceFactory = ({
|
|||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
keyStore,
|
keyStore,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectBotDAL
|
projectBotDAL,
|
||||||
|
projectSlackConfigDAL,
|
||||||
|
slackIntegrationDAL
|
||||||
}: TProjectServiceFactoryDep) => {
|
}: TProjectServiceFactoryDep) => {
|
||||||
/*
|
/*
|
||||||
* Create workspace. Make user the admin
|
* Create workspace. Make user the admin
|
||||||
@@ -269,8 +277,7 @@ export const projectServiceFactory = ({
|
|||||||
members: [
|
members: [
|
||||||
{
|
{
|
||||||
userPublicKey: user.publicKey,
|
userPublicKey: user.publicKey,
|
||||||
orgMembershipId: orgMembership.id,
|
orgMembershipId: orgMembership.id
|
||||||
projectMembershipRole: ProjectMembershipRole.Admin
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -284,7 +291,7 @@ export const projectServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
await projectUserMembershipRoleDAL.create(
|
await projectUserMembershipRoleDAL.create(
|
||||||
{ projectMembershipId: userProjectMembership.id, role: projectAdmin.projectRole },
|
{ projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin },
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -909,6 +916,113 @@ export const projectServiceFactory = ({
|
|||||||
return { secretManagerKmsKey: kmsKey };
|
return { secretManagerKmsKey: kmsKey };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getProjectSlackConfig = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectId
|
||||||
|
}: TGetProjectSlackConfig) => {
|
||||||
|
const project = await projectDAL.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Project not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings);
|
||||||
|
|
||||||
|
return projectSlackConfigDAL.findOne({
|
||||||
|
projectId: project.id
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateProjectSlackConfig = async ({
|
||||||
|
actorId,
|
||||||
|
actor,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
projectId,
|
||||||
|
slackIntegrationId,
|
||||||
|
isAccessRequestNotificationEnabled,
|
||||||
|
accessRequestChannels,
|
||||||
|
isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels
|
||||||
|
}: TUpdateProjectSlackConfig) => {
|
||||||
|
const project = await projectDAL.findById(projectId);
|
||||||
|
if (!project) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Project not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(slackIntegrationId);
|
||||||
|
if (!slackIntegration) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: "Slack integration not found"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
|
||||||
|
|
||||||
|
if (slackIntegration.orgId !== project.orgId) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Selected slack integration is not in the same organization"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectSlackConfigDAL.transaction(async (tx) => {
|
||||||
|
const slackConfig = await projectSlackConfigDAL.findOne(
|
||||||
|
{
|
||||||
|
projectId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (slackConfig) {
|
||||||
|
return projectSlackConfigDAL.updateById(
|
||||||
|
slackConfig.id,
|
||||||
|
{
|
||||||
|
slackIntegrationId,
|
||||||
|
isAccessRequestNotificationEnabled,
|
||||||
|
accessRequestChannels,
|
||||||
|
isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return projectSlackConfigDAL.create(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
slackIntegrationId,
|
||||||
|
isAccessRequestNotificationEnabled,
|
||||||
|
accessRequestChannels,
|
||||||
|
isSecretRequestNotificationEnabled,
|
||||||
|
secretRequestChannels
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createProject,
|
createProject,
|
||||||
deleteProject,
|
deleteProject,
|
||||||
@@ -929,6 +1043,8 @@ export const projectServiceFactory = ({
|
|||||||
updateProjectKmsKey,
|
updateProjectKmsKey,
|
||||||
getProjectKmsBackup,
|
getProjectKmsBackup,
|
||||||
loadProjectKmsBackup,
|
loadProjectKmsBackup,
|
||||||
getProjectKmsKeys
|
getProjectKmsKeys,
|
||||||
|
getProjectSlackConfig,
|
||||||
|
updateProjectSlackConfig
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { ProjectMembershipRole, TProjectKeys } from "@app/db/schemas";
|
import { TProjectKeys } from "@app/db/schemas";
|
||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||||
@@ -88,7 +88,6 @@ export type AddUserToWsDTO = {
|
|||||||
userPrivateKey: string;
|
userPrivateKey: string;
|
||||||
members: {
|
members: {
|
||||||
orgMembershipId: string;
|
orgMembershipId: string;
|
||||||
projectMembershipRole: ProjectMembershipRole;
|
|
||||||
userPublicKey: string;
|
userPublicKey: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
@@ -123,3 +122,13 @@ export type TLoadProjectKmsBackupDTO = {
|
|||||||
export type TGetProjectKmsKey = TProjectPermission;
|
export type TGetProjectKmsKey = TProjectPermission;
|
||||||
|
|
||||||
export type TListProjectCertificateTemplatesDTO = TProjectPermission;
|
export type TListProjectCertificateTemplatesDTO = TProjectPermission;
|
||||||
|
|
||||||
|
export type TGetProjectSlackConfig = TProjectPermission;
|
||||||
|
|
||||||
|
export type TUpdateProjectSlackConfig = {
|
||||||
|
slackIntegrationId: string;
|
||||||
|
isAccessRequestNotificationEnabled: boolean;
|
||||||
|
accessRequestChannels: string;
|
||||||
|
isSecretRequestNotificationEnabled: boolean;
|
||||||
|
secretRequestChannels: string;
|
||||||
|
} & TProjectPermission;
|
||||||
|
@@ -512,7 +512,11 @@ export const secretImportServiceFactory = ({
|
|||||||
return importedSecrets;
|
return importedSecrets;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" });
|
if (!botKey)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Project bot not found. Please upgrade your project.",
|
||||||
|
name: "bot_not_found_error"
|
||||||
|
});
|
||||||
|
|
||||||
const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
|
const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL });
|
||||||
return importedSecrets.map((el) => ({
|
return importedSecrets.map((el) => ({
|
||||||
|
@@ -444,7 +444,9 @@ export const expandSecretReferencesFactory = ({
|
|||||||
depth: depth + 1
|
depth: depth + 1
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue);
|
if (referedValue) {
|
||||||
|
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const secretReferenceEnvironment = entities[0];
|
const secretReferenceEnvironment = entities[0];
|
||||||
const secretReferencePath = path.join("/", ...entities.slice(1, entities.length - 1));
|
const secretReferencePath = path.join("/", ...entities.slice(1, entities.length - 1));
|
||||||
@@ -463,7 +465,9 @@ export const expandSecretReferencesFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue);
|
if (referedValue) {
|
||||||
|
expandedValue = expandedValue.replaceAll(interpolationSyntax, referedValue);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user