mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 10:26:16 +00:00
Merge branch 'main' of github.com:Infisical/infisical into sid/k8s-operator
This commit is contained in:
Generated
+74
-115
@@ -38,6 +38,7 @@
|
|||||||
"@octokit/core": "^5.2.1",
|
"@octokit/core": "^5.2.1",
|
||||||
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
|
"@octokit/request": "8.4.1",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
"@octopusdeploy/api-client": "^3.4.1",
|
"@octopusdeploy/api-client": "^3.4.1",
|
||||||
@@ -9777,18 +9778,6 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": {
|
|
||||||
"version": "10.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
|
||||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/types": "^13.0.0",
|
|
||||||
"universal-user-agent": "^7.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "22.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
@@ -9835,11 +9824,6 @@
|
|||||||
"node": "14 || >=16.14"
|
"node": "14 || >=16.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-app/node_modules/universal-user-agent": {
|
|
||||||
"version": "7.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
|
||||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-oauth-app": {
|
"node_modules/@octokit/auth-oauth-app": {
|
||||||
"version": "8.1.1",
|
"version": "8.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.1.tgz",
|
||||||
@@ -9855,18 +9839,6 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": {
|
|
||||||
"version": "10.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
|
||||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/types": "^13.0.0",
|
|
||||||
"universal-user-agent": "^7.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "22.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
@@ -9905,11 +9877,6 @@
|
|||||||
"@octokit/openapi-types": "^22.2.0"
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-oauth-app/node_modules/universal-user-agent": {
|
|
||||||
"version": "7.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
|
||||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-oauth-device": {
|
"node_modules/@octokit/auth-oauth-device": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.1.tgz",
|
||||||
@@ -9924,18 +9891,6 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": {
|
|
||||||
"version": "10.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
|
||||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/types": "^13.0.0",
|
|
||||||
"universal-user-agent": "^7.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "22.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
@@ -9974,11 +9929,6 @@
|
|||||||
"@octokit/openapi-types": "^22.2.0"
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-oauth-device/node_modules/universal-user-agent": {
|
|
||||||
"version": "7.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
|
||||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-oauth-user": {
|
"node_modules/@octokit/auth-oauth-user": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.1.tgz",
|
||||||
@@ -9994,18 +9944,6 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": {
|
|
||||||
"version": "10.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
|
||||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/types": "^13.0.0",
|
|
||||||
"universal-user-agent": "^7.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "22.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
@@ -10044,11 +9982,6 @@
|
|||||||
"@octokit/openapi-types": "^22.2.0"
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-oauth-user/node_modules/universal-user-agent": {
|
|
||||||
"version": "7.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
|
||||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/auth-token": {
|
"node_modules/@octokit/auth-token": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||||
@@ -10102,32 +10035,38 @@
|
|||||||
"@octokit/openapi-types": "^24.2.0"
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/universal-user-agent": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/@octokit/endpoint": {
|
"node_modules/@octokit/endpoint": {
|
||||||
"version": "9.0.6",
|
"version": "10.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz",
|
||||||
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
|
"integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^13.1.0",
|
"@octokit/types": "^14.0.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^7.0.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
|
||||||
"version": "24.2.0",
|
"version": "25.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz",
|
||||||
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
"integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
|
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
|
||||||
"version": "13.10.0",
|
"version": "14.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz",
|
||||||
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
"integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/openapi-types": "^24.2.0"
|
"@octokit/openapi-types": "^25.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/graphql": {
|
"node_modules/@octokit/graphql": {
|
||||||
@@ -10159,6 +10098,12 @@
|
|||||||
"@octokit/openapi-types": "^24.2.0"
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/graphql/node_modules/universal-user-agent": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/@octokit/oauth-authorization-url": {
|
"node_modules/@octokit/oauth-authorization-url": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz",
|
||||||
@@ -10181,18 +10126,6 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": {
|
|
||||||
"version": "10.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz",
|
|
||||||
"integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/types": "^13.0.0",
|
|
||||||
"universal-user-agent": "^7.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "22.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
||||||
@@ -10231,11 +10164,6 @@
|
|||||||
"@octokit/openapi-types": "^22.2.0"
|
"@octokit/openapi-types": "^22.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": {
|
|
||||||
"version": "7.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz",
|
|
||||||
"integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q=="
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/openapi-types": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz",
|
||||||
@@ -10376,31 +10304,54 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "24.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||||
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
|
"node_modules/@octokit/request-error/node_modules/@octokit/types": {
|
||||||
"version": "13.6.1",
|
"version": "13.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/openapi-types": "^22.2.0"
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/request/node_modules/@octokit/endpoint": {
|
||||||
|
"version": "9.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
|
||||||
|
"integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^13.1.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
|
"node_modules/@octokit/request/node_modules/@octokit/openapi-types": {
|
||||||
"version": "22.2.0",
|
"version": "24.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||||
"integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg=="
|
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request/node_modules/@octokit/types": {
|
"node_modules/@octokit/request/node_modules/@octokit/types": {
|
||||||
"version": "13.6.1",
|
"version": "13.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
"integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==",
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/openapi-types": "^22.2.0"
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/request/node_modules/universal-user-agent": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/@octokit/rest": {
|
"node_modules/@octokit/rest": {
|
||||||
"version": "20.0.2",
|
"version": "20.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.2.tgz",
|
||||||
@@ -18288,7 +18239,8 @@
|
|||||||
"node_modules/fast-content-type-parse": {
|
"node_modules/fast-content-type-parse": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz",
|
||||||
"integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ=="
|
"integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-copy": {
|
"node_modules/fast-copy": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
@@ -24776,6 +24728,12 @@
|
|||||||
"jsonwebtoken": "^9.0.2"
|
"jsonwebtoken": "^9.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/octokit-auth-probot/node_modules/universal-user-agent": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/odbc": {
|
"node_modules/odbc": {
|
||||||
"version": "2.4.9",
|
"version": "2.4.9",
|
||||||
"resolved": "https://registry.npmjs.org/odbc/-/odbc-2.4.9.tgz",
|
"resolved": "https://registry.npmjs.org/odbc/-/odbc-2.4.9.tgz",
|
||||||
@@ -30705,9 +30663,10 @@
|
|||||||
"integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ=="
|
"integrity": "sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ=="
|
||||||
},
|
},
|
||||||
"node_modules/universal-user-agent": {
|
"node_modules/universal-user-agent": {
|
||||||
"version": "6.0.1",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz",
|
||||||
"integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ=="
|
"integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==",
|
||||||
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
|
|||||||
@@ -158,6 +158,7 @@
|
|||||||
"@octokit/core": "^5.2.1",
|
"@octokit/core": "^5.2.1",
|
||||||
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
"@octokit/plugin-paginate-graphql": "^4.0.1",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
|
"@octokit/request": "8.4.1",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
"@octopusdeploy/api-client": "^3.4.1",
|
"@octopusdeploy/api-client": "^3.4.1",
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ const main = async () => {
|
|||||||
(el) =>
|
(el) =>
|
||||||
!el.tableName.includes("_migrations") &&
|
!el.tableName.includes("_migrations") &&
|
||||||
!el.tableName.includes("audit_logs_") &&
|
!el.tableName.includes("audit_logs_") &&
|
||||||
|
!el.tableName.includes("active_locks") &&
|
||||||
el.tableName !== "intermediate_audit_logs"
|
el.tableName !== "intermediate_audit_logs"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Vendored
+2
@@ -18,6 +18,7 @@ import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/extern
|
|||||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
import { TGithubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||||
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
|
import { TIdentityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template";
|
||||||
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
import { TKmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
|
import { TKmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
|
||||||
@@ -300,6 +301,7 @@ declare module "fastify" {
|
|||||||
reminder: TReminderServiceFactory;
|
reminder: TReminderServiceFactory;
|
||||||
bus: TEventBusService;
|
bus: TEventBusService;
|
||||||
sse: TServerSentEventsService;
|
sse: TServerSentEventsService;
|
||||||
|
identityAuthTemplate: TIdentityAuthTemplateServiceFactory;
|
||||||
};
|
};
|
||||||
// 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
|
||||||
|
|||||||
Vendored
+10
@@ -494,6 +494,11 @@ import {
|
|||||||
TAccessApprovalPoliciesEnvironmentsInsert,
|
TAccessApprovalPoliciesEnvironmentsInsert,
|
||||||
TAccessApprovalPoliciesEnvironmentsUpdate
|
TAccessApprovalPoliciesEnvironmentsUpdate
|
||||||
} from "@app/db/schemas/access-approval-policies-environments";
|
} from "@app/db/schemas/access-approval-policies-environments";
|
||||||
|
import {
|
||||||
|
TIdentityAuthTemplates,
|
||||||
|
TIdentityAuthTemplatesInsert,
|
||||||
|
TIdentityAuthTemplatesUpdate
|
||||||
|
} from "@app/db/schemas/identity-auth-templates";
|
||||||
import {
|
import {
|
||||||
TIdentityLdapAuths,
|
TIdentityLdapAuths,
|
||||||
TIdentityLdapAuthsInsert,
|
TIdentityLdapAuthsInsert,
|
||||||
@@ -878,6 +883,11 @@ declare module "knex/types/tables" {
|
|||||||
TIdentityProjectAdditionalPrivilegeInsert,
|
TIdentityProjectAdditionalPrivilegeInsert,
|
||||||
TIdentityProjectAdditionalPrivilegeUpdate
|
TIdentityProjectAdditionalPrivilegeUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.IdentityAuthTemplate]: KnexOriginal.CompositeTableType<
|
||||||
|
TIdentityAuthTemplates,
|
||||||
|
TIdentityAuthTemplatesInsert,
|
||||||
|
TIdentityAuthTemplatesUpdate
|
||||||
|
>;
|
||||||
|
|
||||||
[TableName.AccessApprovalPolicy]: KnexOriginal.CompositeTableType<
|
[TableName.AccessApprovalPolicy]: KnexOriginal.CompositeTableType<
|
||||||
TAccessApprovalPolicies,
|
TAccessApprovalPolicies,
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.UserEncryptionKey, (table) => {
|
||||||
|
table.text("encryptedPrivateKey").nullable().alter();
|
||||||
|
table.text("publicKey").nullable().alter();
|
||||||
|
table.text("iv").nullable().alter();
|
||||||
|
table.text("tag").nullable().alter();
|
||||||
|
table.text("salt").nullable().alter();
|
||||||
|
table.text("verifier").nullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(): Promise<void> {
|
||||||
|
// do nothing for now to avoid breaking down migrations
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.Reminder, "fromDate"))) {
|
||||||
|
await knex.schema.alterTable(TableName.Reminder, (t) => {
|
||||||
|
t.timestamp("fromDate", { useTz: true }).nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Reminder, "fromDate")) {
|
||||||
|
await knex.schema.alterTable(TableName.Reminder, (t) => {
|
||||||
|
t.dropColumn("fromDate");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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.IdentityAuthTemplate))) {
|
||||||
|
await knex.schema.createTable(TableName.IdentityAuthTemplate, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.binary("templateFields").notNullable();
|
||||||
|
t.uuid("orgId").notNullable();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.string("name", 64).notNullable();
|
||||||
|
t.string("authMethod").notNullable();
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
await createOnUpdateTrigger(knex, TableName.IdentityAuthTemplate);
|
||||||
|
}
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.IdentityLdapAuth, "templateId"))) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
|
||||||
|
t.uuid("templateId").nullable();
|
||||||
|
t.foreign("templateId").references("id").inTable(TableName.IdentityAuthTemplate).onDelete("SET NULL");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.IdentityLdapAuth, "templateId")) {
|
||||||
|
await knex.schema.alterTable(TableName.IdentityLdapAuth, (t) => {
|
||||||
|
t.dropForeign(["templateId"]);
|
||||||
|
t.dropColumn("templateId");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await knex.schema.dropTableIfExists(TableName.IdentityAuthTemplate);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.IdentityAuthTemplate);
|
||||||
|
}
|
||||||
@@ -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 { zodBuffer } from "@app/lib/zod";
|
||||||
|
|
||||||
|
import { TImmutableDBKeys } from "./models";
|
||||||
|
|
||||||
|
export const IdentityAuthTemplatesSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
templateFields: zodBuffer,
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
name: z.string(),
|
||||||
|
authMethod: z.string(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TIdentityAuthTemplates = z.infer<typeof IdentityAuthTemplatesSchema>;
|
||||||
|
export type TIdentityAuthTemplatesInsert = Omit<z.input<typeof IdentityAuthTemplatesSchema>, TImmutableDBKeys>;
|
||||||
|
export type TIdentityAuthTemplatesUpdate = Partial<Omit<z.input<typeof IdentityAuthTemplatesSchema>, TImmutableDBKeys>>;
|
||||||
@@ -25,7 +25,8 @@ export const IdentityLdapAuthsSchema = z.object({
|
|||||||
allowedFields: z.unknown().nullable().optional(),
|
allowedFields: z.unknown().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
accessTokenPeriod: z.coerce.number().default(0)
|
accessTokenPeriod: z.coerce.number().default(0),
|
||||||
|
templateId: z.string().uuid().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
|
export type TIdentityLdapAuths = z.infer<typeof IdentityLdapAuthsSchema>;
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export enum TableName {
|
|||||||
IdentityProjectMembership = "identity_project_memberships",
|
IdentityProjectMembership = "identity_project_memberships",
|
||||||
IdentityProjectMembershipRole = "identity_project_membership_role",
|
IdentityProjectMembershipRole = "identity_project_membership_role",
|
||||||
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
IdentityProjectAdditionalPrivilege = "identity_project_additional_privilege",
|
||||||
|
IdentityAuthTemplate = "identity_auth_templates",
|
||||||
// used by both identity and users
|
// used by both identity and users
|
||||||
IdentityMetadata = "identity_metadata",
|
IdentityMetadata = "identity_metadata",
|
||||||
ResourceMetadata = "resource_metadata",
|
ResourceMetadata = "resource_metadata",
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ export const RemindersSchema = z.object({
|
|||||||
repeatDays: z.number().nullable().optional(),
|
repeatDays: z.number().nullable().optional(),
|
||||||
nextReminderDate: z.date(),
|
nextReminderDate: z.date(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
fromDate: z.date().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TReminders = z.infer<typeof RemindersSchema>;
|
export type TReminders = z.infer<typeof RemindersSchema>;
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ export const UserEncryptionKeysSchema = z.object({
|
|||||||
protectedKey: z.string().nullable().optional(),
|
protectedKey: z.string().nullable().optional(),
|
||||||
protectedKeyIV: z.string().nullable().optional(),
|
protectedKeyIV: z.string().nullable().optional(),
|
||||||
protectedKeyTag: z.string().nullable().optional(),
|
protectedKeyTag: z.string().nullable().optional(),
|
||||||
publicKey: z.string(),
|
publicKey: z.string().nullable().optional(),
|
||||||
encryptedPrivateKey: z.string(),
|
encryptedPrivateKey: z.string().nullable().optional(),
|
||||||
iv: z.string(),
|
iv: z.string().nullable().optional(),
|
||||||
tag: z.string(),
|
tag: z.string().nullable().optional(),
|
||||||
salt: z.string(),
|
salt: z.string().nullable().optional(),
|
||||||
verifier: z.string(),
|
verifier: z.string().nullable().optional(),
|
||||||
userId: z.string().uuid(),
|
userId: z.string().uuid(),
|
||||||
hashedPassword: z.string().nullable().optional(),
|
hashedPassword: z.string().nullable().optional(),
|
||||||
serverEncryptedPrivateKey: z.string().nullable().optional(),
|
serverEncryptedPrivateKey: z.string().nullable().optional(),
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ export const generateUserSrpKeys = async (password: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getUserPrivateKey = async (password: string, user: TUserEncryptionKeys) => {
|
export const getUserPrivateKey = async (password: string, user: TUserEncryptionKeys) => {
|
||||||
|
if (!user.encryptedPrivateKey || !user.iv || !user.tag || !user.salt) {
|
||||||
|
throw new Error("User encrypted private key not found");
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { initEnvConfig } from "@app/lib/config/env";
|
||||||
import { initLogger } from "@app/lib/logger";
|
import { initLogger, logger } from "@app/lib/logger";
|
||||||
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
||||||
|
|
||||||
import { AuthMethod } from "../../services/auth/auth-type";
|
import { AuthMethod } from "../../services/auth/auth-type";
|
||||||
@@ -17,7 +17,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
initLogger();
|
initLogger();
|
||||||
|
|
||||||
const superAdminDAL = superAdminDALFactory(knex);
|
const superAdminDAL = superAdminDALFactory(knex);
|
||||||
await crypto.initialize(superAdminDAL);
|
await initEnvConfig(superAdminDAL, logger);
|
||||||
|
|
||||||
await knex(TableName.SuperAdmin).insert([
|
await knex(TableName.SuperAdmin).insert([
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
@@ -25,6 +25,7 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
{ id: "00000000-0000-0000-0000-000000000000", initialized: true, allowSignUp: true }
|
{ id: "00000000-0000-0000-0000-000000000000", initialized: true, allowSignUp: true }
|
||||||
]);
|
]);
|
||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
|
|
||||||
const [user] = await knex(TableName.Users)
|
const [user] = await knex(TableName.Users)
|
||||||
.insert([
|
.insert([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { initEnvConfig } from "@app/lib/config/env";
|
||||||
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
|
import { crypto, SymmetricKeySize } from "@app/lib/crypto/cryptography";
|
||||||
|
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
||||||
|
import { initLogger, logger } from "@app/lib/logger";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { AuthMethod } from "@app/services/auth/auth-type";
|
||||||
|
import { assignWorkspaceKeysToMembers, createProjectKey } from "@app/services/project/project-fns";
|
||||||
|
import { projectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
|
import { projectMembershipDALFactory } from "@app/services/project-membership/project-membership-dal";
|
||||||
|
import { projectUserMembershipRoleDALFactory } from "@app/services/project-membership/project-user-membership-role-dal";
|
||||||
|
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
||||||
|
import { userDALFactory } from "@app/services/user/user-dal";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectType, SecretEncryptionAlgo, SecretKeyEncoding, TableName } from "../schemas";
|
import {
|
||||||
import { buildUserProjectKey, getUserPrivateKey, seedData1 } from "../seed-data";
|
OrgMembershipRole,
|
||||||
|
OrgMembershipStatus,
|
||||||
|
ProjectMembershipRole,
|
||||||
|
ProjectType,
|
||||||
|
SecretEncryptionAlgo,
|
||||||
|
SecretKeyEncoding,
|
||||||
|
TableName
|
||||||
|
} from "../schemas";
|
||||||
|
import { seedData1 } from "../seed-data";
|
||||||
|
|
||||||
export const DEFAULT_PROJECT_ENVS = [
|
export const DEFAULT_PROJECT_ENVS = [
|
||||||
{ name: "Development", slug: "dev" },
|
{ name: "Development", slug: "dev" },
|
||||||
@@ -11,12 +30,159 @@ export const DEFAULT_PROJECT_ENVS = [
|
|||||||
{ name: "Production", slug: "prod" }
|
{ name: "Production", slug: "prod" }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const createUserWithGhostUser = async (
|
||||||
|
orgId: string,
|
||||||
|
projectId: string,
|
||||||
|
userId: string,
|
||||||
|
userOrgMembershipId: string,
|
||||||
|
knex: Knex
|
||||||
|
) => {
|
||||||
|
const projectKeyDAL = projectKeyDALFactory(knex);
|
||||||
|
const userDAL = userDALFactory(knex);
|
||||||
|
const projectMembershipDAL = projectMembershipDALFactory(knex);
|
||||||
|
const projectUserMembershipRoleDAL = projectUserMembershipRoleDALFactory(knex);
|
||||||
|
|
||||||
|
const email = `sudo-${alphaNumericNanoId(16)}-${orgId}@infisical.com`; // We add a nanoid because the email is unique. And we have to create a new ghost user each time, so we can have access to the private key.
|
||||||
|
|
||||||
|
const password = crypto.randomBytes(128).toString("hex");
|
||||||
|
|
||||||
|
const [ghostUser] = await knex(TableName.Users)
|
||||||
|
.insert({
|
||||||
|
isGhost: true,
|
||||||
|
authMethods: [AuthMethod.EMAIL],
|
||||||
|
username: email,
|
||||||
|
email,
|
||||||
|
isAccepted: true
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
const encKeys = await generateUserSrpKeys(email, password);
|
||||||
|
|
||||||
|
await knex(TableName.UserEncryptionKey)
|
||||||
|
.insert({ userId: ghostUser.id, encryptionVersion: 2, publicKey: encKeys.publicKey })
|
||||||
|
.onConflict("userId")
|
||||||
|
.merge();
|
||||||
|
|
||||||
|
await knex(TableName.OrgMembership)
|
||||||
|
.insert({
|
||||||
|
orgId,
|
||||||
|
userId: ghostUser.id,
|
||||||
|
role: OrgMembershipRole.Admin,
|
||||||
|
status: OrgMembershipStatus.Accepted,
|
||||||
|
isActive: true
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
const [projectMembership] = await knex(TableName.ProjectMembership)
|
||||||
|
.insert({
|
||||||
|
userId: ghostUser.id,
|
||||||
|
projectId
|
||||||
|
})
|
||||||
|
.returning("*");
|
||||||
|
|
||||||
|
await knex(TableName.ProjectUserMembershipRole).insert({
|
||||||
|
projectMembershipId: projectMembership.id,
|
||||||
|
role: ProjectMembershipRole.Admin
|
||||||
|
});
|
||||||
|
|
||||||
|
const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({
|
||||||
|
publicKey: encKeys.publicKey,
|
||||||
|
privateKey: encKeys.plainPrivateKey
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex(TableName.ProjectKeys).insert({
|
||||||
|
projectId,
|
||||||
|
receiverId: ghostUser.id,
|
||||||
|
encryptedKey: encryptedProjectKey,
|
||||||
|
nonce: encryptedProjectKeyIv,
|
||||||
|
senderId: ghostUser.id
|
||||||
|
});
|
||||||
|
|
||||||
|
const { iv, tag, ciphertext, encoding, algorithm } = crypto
|
||||||
|
.encryption()
|
||||||
|
.symmetric()
|
||||||
|
.encryptWithRootEncryptionKey(encKeys.plainPrivateKey);
|
||||||
|
|
||||||
|
await knex(TableName.ProjectBot).insert({
|
||||||
|
name: "Infisical Bot (Ghost)",
|
||||||
|
projectId,
|
||||||
|
tag,
|
||||||
|
iv,
|
||||||
|
encryptedProjectKey,
|
||||||
|
encryptedProjectKeyNonce: encryptedProjectKeyIv,
|
||||||
|
encryptedPrivateKey: ciphertext,
|
||||||
|
isActive: true,
|
||||||
|
publicKey: encKeys.publicKey,
|
||||||
|
senderId: ghostUser.id,
|
||||||
|
algorithm,
|
||||||
|
keyEncoding: encoding
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.id, projectId, knex);
|
||||||
|
|
||||||
|
if (!latestKey) {
|
||||||
|
throw new Error("Latest key not found for user");
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await userDAL.findUserEncKeyByUserId(userId, knex);
|
||||||
|
|
||||||
|
if (!user || !user.publicKey) {
|
||||||
|
throw new Error("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const [projectAdmin] = assignWorkspaceKeysToMembers({
|
||||||
|
decryptKey: latestKey,
|
||||||
|
userPrivateKey: encKeys.plainPrivateKey,
|
||||||
|
members: [
|
||||||
|
{
|
||||||
|
userPublicKey: user.publicKey,
|
||||||
|
orgMembershipId: userOrgMembershipId
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a membership for the user
|
||||||
|
const userProjectMembership = await projectMembershipDAL.create(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
userId: user.id
|
||||||
|
},
|
||||||
|
knex
|
||||||
|
);
|
||||||
|
await projectUserMembershipRoleDAL.create(
|
||||||
|
{ projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin },
|
||||||
|
knex
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a project key for the user
|
||||||
|
await projectKeyDAL.create(
|
||||||
|
{
|
||||||
|
encryptedKey: projectAdmin.workspaceEncryptedKey,
|
||||||
|
nonce: projectAdmin.workspaceEncryptedNonce,
|
||||||
|
senderId: ghostUser.id,
|
||||||
|
receiverId: user.id,
|
||||||
|
projectId
|
||||||
|
},
|
||||||
|
knex
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
user: ghostUser,
|
||||||
|
keys: encKeys
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export async function seed(knex: Knex): Promise<void> {
|
export async function seed(knex: Knex): Promise<void> {
|
||||||
// Deletes ALL existing entries
|
// Deletes ALL existing entries
|
||||||
await knex(TableName.Project).del();
|
await knex(TableName.Project).del();
|
||||||
await knex(TableName.Environment).del();
|
await knex(TableName.Environment).del();
|
||||||
await knex(TableName.SecretFolder).del();
|
await knex(TableName.SecretFolder).del();
|
||||||
|
|
||||||
|
initLogger();
|
||||||
|
|
||||||
|
const superAdminDAL = superAdminDALFactory(knex);
|
||||||
|
await initEnvConfig(superAdminDAL, logger);
|
||||||
|
|
||||||
const [project] = await knex(TableName.Project)
|
const [project] = await knex(TableName.Project)
|
||||||
.insert({
|
.insert({
|
||||||
name: seedData1.project.name,
|
name: seedData1.project.name,
|
||||||
@@ -29,29 +195,24 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
})
|
})
|
||||||
.returning("*");
|
.returning("*");
|
||||||
|
|
||||||
const projectMembership = await knex(TableName.ProjectMembership)
|
const userOrgMembership = await knex(TableName.OrgMembership)
|
||||||
.insert({
|
.where({
|
||||||
projectId: project.id,
|
orgId: seedData1.organization.id,
|
||||||
userId: seedData1.id
|
userId: seedData1.id
|
||||||
})
|
})
|
||||||
.returning("*");
|
.first();
|
||||||
await knex(TableName.ProjectUserMembershipRole).insert({
|
|
||||||
role: ProjectMembershipRole.Admin,
|
|
||||||
projectMembershipId: projectMembership[0].id
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if (!userOrgMembership) {
|
||||||
|
throw new Error("User org membership not found");
|
||||||
|
}
|
||||||
const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first();
|
const user = await knex(TableName.UserEncryptionKey).where({ userId: seedData1.id }).first();
|
||||||
if (!user) throw new Error("User not found");
|
if (!user) throw new Error("User not found");
|
||||||
|
|
||||||
const userPrivateKey = await getUserPrivateKey(seedData1.password, user);
|
if (!user.publicKey) {
|
||||||
const projectKey = buildUserProjectKey(userPrivateKey, user.publicKey);
|
throw new Error("User public key not found");
|
||||||
await knex(TableName.ProjectKeys).insert({
|
}
|
||||||
projectId: project.id,
|
|
||||||
nonce: projectKey.nonce,
|
await createUserWithGhostUser(seedData1.organization.id, project.id, seedData1.id, userOrgMembership.id, knex);
|
||||||
encryptedKey: projectKey.ciphertext,
|
|
||||||
receiverId: seedData1.id,
|
|
||||||
senderId: seedData1.id
|
|
||||||
});
|
|
||||||
|
|
||||||
// create default environments and default folders
|
// create default environments and default folders
|
||||||
const envs = await knex(TableName.Environment)
|
const envs = await knex(TableName.Environment)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { initEnvConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
|
import { initLogger, logger } from "@app/lib/logger";
|
||||||
|
import { superAdminDALFactory } from "@app/services/super-admin/super-admin-dal";
|
||||||
|
|
||||||
import { IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
|
import { IdentityAuthMethod, OrgMembershipRole, ProjectMembershipRole, TableName } from "../schemas";
|
||||||
import { seedData1 } from "../seed-data";
|
import { seedData1 } from "../seed-data";
|
||||||
@@ -10,6 +13,11 @@ export async function seed(knex: Knex): Promise<void> {
|
|||||||
await knex(TableName.Identity).del();
|
await knex(TableName.Identity).del();
|
||||||
await knex(TableName.IdentityOrgMembership).del();
|
await knex(TableName.IdentityOrgMembership).del();
|
||||||
|
|
||||||
|
initLogger();
|
||||||
|
|
||||||
|
const superAdminDAL = superAdminDALFactory(knex);
|
||||||
|
await initEnvConfig(superAdminDAL, logger);
|
||||||
|
|
||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
await knex(TableName.Identity).insert([
|
await knex(TableName.Identity).insert([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,391 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { IdentityAuthTemplatesSchema } from "@app/db/schemas/identity-auth-templates";
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import {
|
||||||
|
IdentityAuthTemplateMethod,
|
||||||
|
TEMPLATE_SUCCESS_MESSAGES,
|
||||||
|
TEMPLATE_VALIDATION_MESSAGES
|
||||||
|
} from "@app/ee/services/identity-auth-template/identity-auth-template-enums";
|
||||||
|
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 ldapTemplateFieldsSchema = z.object({
|
||||||
|
url: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.URL_REQUIRED),
|
||||||
|
bindDN: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.BIND_DN_REQUIRED),
|
||||||
|
bindPass: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.BIND_PASSWORD_REQUIRED),
|
||||||
|
searchBase: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.LDAP.SEARCH_BASE_REQUIRED),
|
||||||
|
ldapCaCertificate: z.string().trim().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerIdentityTemplateRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "Create identity auth template",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
body: z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_REQUIRED)
|
||||||
|
.max(64, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_MAX_LENGTH),
|
||||||
|
authMethod: z.nativeEnum(IdentityAuthTemplateMethod),
|
||||||
|
templateFields: ldapTemplateFieldsSchema
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: IdentityAuthTemplatesSchema.extend({
|
||||||
|
templateFields: z.record(z.string(), z.unknown())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const template = await server.services.identityAuthTemplate.createTemplate({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
name: req.body.name,
|
||||||
|
authMethod: req.body.authMethod,
|
||||||
|
templateFields: req.body.templateFields
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_CREATE,
|
||||||
|
metadata: {
|
||||||
|
templateId: template.id,
|
||||||
|
name: template.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "PATCH",
|
||||||
|
url: "/:templateId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "Update identity auth template",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
templateId: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_ID_REQUIRED)
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
name: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_REQUIRED)
|
||||||
|
.max(64, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_NAME_MAX_LENGTH)
|
||||||
|
.optional(),
|
||||||
|
templateFields: ldapTemplateFieldsSchema.partial().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: IdentityAuthTemplatesSchema.extend({
|
||||||
|
templateFields: z.record(z.string(), z.unknown())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const template = await server.services.identityAuthTemplate.updateTemplate({
|
||||||
|
templateId: req.params.templateId,
|
||||||
|
name: req.body.name,
|
||||||
|
templateFields: req.body.templateFields,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_UPDATE,
|
||||||
|
metadata: {
|
||||||
|
templateId: template.id,
|
||||||
|
name: template.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:templateId",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "Delete identity auth template",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
templateId: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_ID_REQUIRED)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const template = await server.services.identityAuthTemplate.deleteTemplate({
|
||||||
|
templateId: req.params.templateId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_DELETE,
|
||||||
|
metadata: {
|
||||||
|
templateId: template.id,
|
||||||
|
name: template.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { message: TEMPLATE_SUCCESS_MESSAGES.DELETED };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:templateId",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "Get identity auth template by ID",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
templateId: z.string().min(1, TEMPLATE_VALIDATION_MESSAGES.TEMPLATE_ID_REQUIRED)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: IdentityAuthTemplatesSchema.extend({
|
||||||
|
templateFields: ldapTemplateFieldsSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const template = await server.services.identityAuthTemplate.getTemplate({
|
||||||
|
templateId: req.params.templateId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/search",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "List identity auth templates",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
limit: z.coerce.number().positive().max(100).default(5).optional(),
|
||||||
|
offset: z.coerce.number().min(0).default(0).optional(),
|
||||||
|
search: z.string().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
templates: IdentityAuthTemplatesSchema.extend({
|
||||||
|
templateFields: ldapTemplateFieldsSchema
|
||||||
|
}).array(),
|
||||||
|
totalCount: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { templates, totalCount } = await server.services.identityAuthTemplate.listTemplates({
|
||||||
|
limit: req.query.limit,
|
||||||
|
offset: req.query.offset,
|
||||||
|
search: req.query.search,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { templates, totalCount };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "Get identity auth templates by authentication method",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
querystring: z.object({
|
||||||
|
authMethod: z.nativeEnum(IdentityAuthTemplateMethod)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: IdentityAuthTemplatesSchema.extend({
|
||||||
|
templateFields: ldapTemplateFieldsSchema
|
||||||
|
}).array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const templates = await server.services.identityAuthTemplate.getTemplatesByAuthMethod({
|
||||||
|
authMethod: req.query.authMethod,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:templateId/usage",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "Get template usage by template ID",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
templateId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
identityId: z.string(),
|
||||||
|
identityName: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const templates = await server.services.identityAuthTemplate.findTemplateUsages({
|
||||||
|
templateId: req.params.templateId,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:templateId/delete-usage",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
description: "Unlink identity auth template usage",
|
||||||
|
security: [
|
||||||
|
{
|
||||||
|
bearerAuth: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
params: z.object({
|
||||||
|
templateId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
identityIds: z.string().array()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z
|
||||||
|
.object({
|
||||||
|
authId: z.string(),
|
||||||
|
identityId: z.string(),
|
||||||
|
identityName: z.string()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const templates = await server.services.identityAuthTemplate.unlinkTemplateUsage({
|
||||||
|
templateId: req.params.templateId,
|
||||||
|
identityIds: req.body.identityIds,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return templates;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ import { registerGatewayRouter } from "./gateway-router";
|
|||||||
import { registerGithubOrgSyncRouter } from "./github-org-sync-router";
|
import { registerGithubOrgSyncRouter } from "./github-org-sync-router";
|
||||||
import { registerGroupRouter } from "./group-router";
|
import { registerGroupRouter } from "./group-router";
|
||||||
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
import { registerIdentityProjectAdditionalPrivilegeRouter } from "./identity-project-additional-privilege-router";
|
||||||
|
import { registerIdentityTemplateRouter } from "./identity-template-router";
|
||||||
import { registerKmipRouter } from "./kmip-router";
|
import { registerKmipRouter } from "./kmip-router";
|
||||||
import { registerKmipSpecRouter } from "./kmip-spec-router";
|
import { registerKmipSpecRouter } from "./kmip-spec-router";
|
||||||
import { registerLdapRouter } from "./ldap-router";
|
import { registerLdapRouter } from "./ldap-router";
|
||||||
@@ -125,6 +126,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await server.register(registerExternalKmsRouter, {
|
await server.register(registerExternalKmsRouter, {
|
||||||
prefix: "/external-kms"
|
prefix: "/external-kms"
|
||||||
});
|
});
|
||||||
|
await server.register(registerIdentityTemplateRouter, { prefix: "/identity-templates" });
|
||||||
|
|
||||||
await server.register(registerProjectTemplateRouter, { prefix: "/project-templates" });
|
await server.register(registerProjectTemplateRouter, { prefix: "/project-templates" });
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// weird commonjs-related error in the CI requires us to do the import like this
|
// weird commonjs-related error in the CI requires us to do the import like this
|
||||||
import knex from "knex";
|
import knex from "knex";
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TAuditLogs } from "@app/db/schemas";
|
import { TableName, TAuditLogs } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
|
import { DatabaseError, GatewayTimeoutError } from "@app/lib/errors";
|
||||||
import { ormify, selectAllTableCols, TOrmify } from "@app/lib/knex";
|
import { ormify, selectAllTableCols, TOrmify } from "@app/lib/knex";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -150,43 +152,70 @@ export const auditLogDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
// delete all audit log that have expired
|
// delete all audit log that have expired
|
||||||
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => {
|
const pruneAuditLog: TAuditLogDALFactory["pruneAuditLog"] = async (tx) => {
|
||||||
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
|
const runPrune = async (dbClient: knex.Knex) => {
|
||||||
const MAX_RETRY_ON_FAILURE = 3;
|
const AUDIT_LOG_PRUNE_BATCH_SIZE = 10000;
|
||||||
|
const MAX_RETRY_ON_FAILURE = 3;
|
||||||
|
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
let deletedAuditLogIds: { id: string }[] = [];
|
let deletedAuditLogIds: { id: string }[] = [];
|
||||||
let numberOfRetryOnFailure = 0;
|
let numberOfRetryOnFailure = 0;
|
||||||
let isRetrying = false;
|
let isRetrying = false;
|
||||||
|
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: audit log started`);
|
||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
const findExpiredLogSubQuery = (tx || db)(TableName.AuditLog)
|
const findExpiredLogSubQuery = dbClient(TableName.AuditLog)
|
||||||
.where("expiresAt", "<", today)
|
.where("expiresAt", "<", today)
|
||||||
.where("createdAt", "<", today) // to use audit log partition
|
.where("createdAt", "<", today) // to use audit log partition
|
||||||
.orderBy(`${TableName.AuditLog}.createdAt`, "desc")
|
.orderBy(`${TableName.AuditLog}.createdAt`, "desc")
|
||||||
.select("id")
|
.select("id")
|
||||||
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
|
.limit(AUDIT_LOG_PRUNE_BATCH_SIZE);
|
||||||
|
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
deletedAuditLogIds = await (tx || db)(TableName.AuditLog)
|
deletedAuditLogIds = await dbClient(TableName.AuditLog)
|
||||||
.whereIn("id", findExpiredLogSubQuery)
|
.whereIn("id", findExpiredLogSubQuery)
|
||||||
.del()
|
.del()
|
||||||
.returning("id");
|
.returning("id");
|
||||||
numberOfRetryOnFailure = 0; // reset
|
numberOfRetryOnFailure = 0; // reset
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
numberOfRetryOnFailure += 1;
|
numberOfRetryOnFailure += 1;
|
||||||
logger.error(error, "Failed to delete audit log on pruning");
|
logger.error(error, "Failed to delete audit log on pruning");
|
||||||
} finally {
|
} finally {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
setTimeout(resolve, 10); // time to breathe for db
|
setTimeout(resolve, 10); // time to breathe for db
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
isRetrying = numberOfRetryOnFailure > 0;
|
isRetrying = numberOfRetryOnFailure > 0;
|
||||||
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
|
} while (deletedAuditLogIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
|
||||||
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
|
logger.info(`${QueueName.DailyResourceCleanUp}: audit log completed`);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
await runPrune(tx);
|
||||||
|
} else {
|
||||||
|
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
|
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
|
||||||
|
await runPrune(trx);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return { ...auditLogOrm, pruneAuditLog, find };
|
const create: TAuditLogDALFactory["create"] = async (tx) => {
|
||||||
|
const config = getConfig();
|
||||||
|
|
||||||
|
if (config.DISABLE_AUDIT_LOG_STORAGE) {
|
||||||
|
return {
|
||||||
|
...tx,
|
||||||
|
id: uuidv4(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return auditLogOrm.create(tx);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...auditLogOrm, create, pruneAuditLog, find };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -161,6 +161,9 @@ export enum EventType {
|
|||||||
CREATE_IDENTITY = "create-identity",
|
CREATE_IDENTITY = "create-identity",
|
||||||
UPDATE_IDENTITY = "update-identity",
|
UPDATE_IDENTITY = "update-identity",
|
||||||
DELETE_IDENTITY = "delete-identity",
|
DELETE_IDENTITY = "delete-identity",
|
||||||
|
MACHINE_IDENTITY_AUTH_TEMPLATE_CREATE = "machine-identity-auth-template-create",
|
||||||
|
MACHINE_IDENTITY_AUTH_TEMPLATE_UPDATE = "machine-identity-auth-template-update",
|
||||||
|
MACHINE_IDENTITY_AUTH_TEMPLATE_DELETE = "machine-identity-auth-template-delete",
|
||||||
LOGIN_IDENTITY_UNIVERSAL_AUTH = "login-identity-universal-auth",
|
LOGIN_IDENTITY_UNIVERSAL_AUTH = "login-identity-universal-auth",
|
||||||
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
|
ADD_IDENTITY_UNIVERSAL_AUTH = "add-identity-universal-auth",
|
||||||
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
|
UPDATE_IDENTITY_UNIVERSAL_AUTH = "update-identity-universal-auth",
|
||||||
@@ -830,6 +833,30 @@ interface LoginIdentityUniversalAuthEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MachineIdentityAuthTemplateCreateEvent {
|
||||||
|
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_CREATE;
|
||||||
|
metadata: {
|
||||||
|
templateId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MachineIdentityAuthTemplateUpdateEvent {
|
||||||
|
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_UPDATE;
|
||||||
|
metadata: {
|
||||||
|
templateId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MachineIdentityAuthTemplateDeleteEvent {
|
||||||
|
type: EventType.MACHINE_IDENTITY_AUTH_TEMPLATE_DELETE;
|
||||||
|
metadata: {
|
||||||
|
templateId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface AddIdentityUniversalAuthEvent {
|
interface AddIdentityUniversalAuthEvent {
|
||||||
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH;
|
type: EventType.ADD_IDENTITY_UNIVERSAL_AUTH;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -1325,6 +1352,7 @@ interface AddIdentityLdapAuthEvent {
|
|||||||
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
allowedFields?: TAllowedFields[];
|
allowedFields?: TAllowedFields[];
|
||||||
url: string;
|
url: string;
|
||||||
|
templateId?: string | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1338,6 +1366,7 @@ interface UpdateIdentityLdapAuthEvent {
|
|||||||
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
accessTokenTrustedIps?: Array<TIdentityTrustedIp>;
|
||||||
allowedFields?: TAllowedFields[];
|
allowedFields?: TAllowedFields[];
|
||||||
url?: string;
|
url?: string;
|
||||||
|
templateId?: string | null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3439,6 +3468,9 @@ export type Event =
|
|||||||
| UpdateIdentityEvent
|
| UpdateIdentityEvent
|
||||||
| DeleteIdentityEvent
|
| DeleteIdentityEvent
|
||||||
| LoginIdentityUniversalAuthEvent
|
| LoginIdentityUniversalAuthEvent
|
||||||
|
| MachineIdentityAuthTemplateCreateEvent
|
||||||
|
| MachineIdentityAuthTemplateUpdateEvent
|
||||||
|
| MachineIdentityAuthTemplateDeleteEvent
|
||||||
| AddIdentityUniversalAuthEvent
|
| AddIdentityUniversalAuthEvent
|
||||||
| UpdateIdentityUniversalAuthEvent
|
| UpdateIdentityUniversalAuthEvent
|
||||||
| DeleteIdentityUniversalAuthEvent
|
| DeleteIdentityUniversalAuthEvent
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
import { ProjectVersion, SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, ScimRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -65,6 +65,18 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
const userKeysSet = new Set(keys.map((k) => `${k.projectId}-${k.receiverId}`));
|
const userKeysSet = new Set(keys.map((k) => `${k.projectId}-${k.receiverId}`));
|
||||||
|
|
||||||
for await (const projectId of projectIds) {
|
for await (const projectId of projectIds) {
|
||||||
|
const project = await projectDAL.findById(projectId, tx);
|
||||||
|
if (!project) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find project with ID '${projectId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (project.version !== ProjectVersion.V1 && project.version !== ProjectVersion.V2) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const usersToAddProjectKeyFor = users.filter((u) => !userKeysSet.has(`${projectId}-${u.userId}`));
|
const usersToAddProjectKeyFor = users.filter((u) => !userKeysSet.has(`${projectId}-${u.userId}`));
|
||||||
|
|
||||||
if (usersToAddProjectKeyFor.length) {
|
if (usersToAddProjectKeyFor.length) {
|
||||||
@@ -86,6 +98,12 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ghostUserLatestKey.sender.publicKey) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find project owner's public key in project with ID '${projectId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
||||||
|
|
||||||
if (!bot) {
|
if (!bot) {
|
||||||
@@ -112,6 +130,12 @@ const addAcceptedUsersToGroup = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectKeysToAdd = usersToAddProjectKeyFor.map((user) => {
|
const projectKeysToAdd = usersToAddProjectKeyFor.map((user) => {
|
||||||
|
if (!user.publicKey) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find user's public key in project with ID '${projectId}'`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { ciphertext: encryptedKey, nonce } = crypto
|
const { ciphertext: encryptedKey, nonce } = crypto
|
||||||
.encryption()
|
.encryption()
|
||||||
.asymmetric()
|
.asymmetric()
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ type TGroupServiceFactoryDep = {
|
|||||||
TUserGroupMembershipDALFactory,
|
TUserGroupMembershipDALFactory,
|
||||||
"findOne" | "delete" | "filterProjectsByUserMembership" | "transaction" | "insertMany" | "find"
|
"findOne" | "delete" | "filterProjectsByUserMembership" | "transaction" | "insertMany" | "find"
|
||||||
>;
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "findLatestProjectKey" | "insertMany">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission" | "getOrgPermissionByRole">;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export type TAddUsersToGroup = {
|
|||||||
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
userGroupMembershipDAL: Pick<TUserGroupMembershipDALFactory, "find" | "transaction" | "insertMany">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
tx: Knex;
|
tx: Knex;
|
||||||
};
|
};
|
||||||
@@ -78,7 +78,7 @@ export type TAddUsersToGroupByUserIds = {
|
|||||||
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
orgDAL: Pick<TOrgDALFactory, "findMembership">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
@@ -102,7 +102,7 @@ export type TConvertPendingGroupAdditionsToGroupMemberships = {
|
|||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/* eslint-disable no-case-declarations */
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { buildFindFilter, ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
import { IdentityAuthTemplateMethod } from "./identity-auth-template-enums";
|
||||||
|
|
||||||
|
export type TIdentityAuthTemplateDALFactory = ReturnType<typeof identityAuthTemplateDALFactory>;
|
||||||
|
|
||||||
|
export const identityAuthTemplateDALFactory = (db: TDbClient) => {
|
||||||
|
const identityAuthTemplateOrm = ormify(db, TableName.IdentityAuthTemplate);
|
||||||
|
|
||||||
|
const findByOrgId = async (
|
||||||
|
orgId: string,
|
||||||
|
{ limit, offset, search, tx }: { limit?: number; offset?: number; search?: string; tx?: Knex } = {}
|
||||||
|
) => {
|
||||||
|
let query = (tx || db.replicaNode())(TableName.IdentityAuthTemplate).where({ orgId });
|
||||||
|
let countQuery = (tx || db.replicaNode())(TableName.IdentityAuthTemplate).where({ orgId });
|
||||||
|
|
||||||
|
if (search) {
|
||||||
|
const searchFilter = `%${search.toLowerCase()}%`;
|
||||||
|
query = query.whereRaw("LOWER(name) LIKE ?", [searchFilter]);
|
||||||
|
countQuery = countQuery.whereRaw("LOWER(name) LIKE ?", [searchFilter]);
|
||||||
|
}
|
||||||
|
|
||||||
|
query = query.orderBy("createdAt", "desc");
|
||||||
|
|
||||||
|
if (limit !== undefined) {
|
||||||
|
query = query.limit(limit);
|
||||||
|
}
|
||||||
|
if (offset !== undefined) {
|
||||||
|
query = query.offset(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await query;
|
||||||
|
|
||||||
|
const [{ count }] = (await countQuery.count("* as count")) as [{ count: string | number }];
|
||||||
|
|
||||||
|
return { docs, totalCount: Number(count) };
|
||||||
|
};
|
||||||
|
|
||||||
|
const findByAuthMethod = async (authMethod: string, orgId: string, tx?: Knex) => {
|
||||||
|
const query = (tx || db.replicaNode())(TableName.IdentityAuthTemplate)
|
||||||
|
.where({ authMethod, orgId })
|
||||||
|
.orderBy("createdAt", "desc");
|
||||||
|
const docs = await query;
|
||||||
|
return docs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const findTemplateUsages = async (templateId: string, authMethod: string, tx?: Knex) => {
|
||||||
|
switch (authMethod) {
|
||||||
|
case IdentityAuthTemplateMethod.LDAP:
|
||||||
|
const query = (tx || db.replicaNode())(TableName.IdentityLdapAuth)
|
||||||
|
.join(TableName.Identity, `${TableName.IdentityLdapAuth}.identityId`, `${TableName.Identity}.id`)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
.where(buildFindFilter({ templateId }, TableName.IdentityLdapAuth))
|
||||||
|
.select(
|
||||||
|
db.ref("identityId").withSchema(TableName.IdentityLdapAuth),
|
||||||
|
db.ref("name").withSchema(TableName.Identity).as("identityName")
|
||||||
|
);
|
||||||
|
const docs = await query;
|
||||||
|
return docs;
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const findByIdAndOrgId = async (id: string, orgId: string, tx?: Knex) => {
|
||||||
|
const query = (tx || db.replicaNode())(TableName.IdentityAuthTemplate).where({ id, orgId });
|
||||||
|
const doc = await query;
|
||||||
|
return doc?.[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...identityAuthTemplateOrm,
|
||||||
|
findByOrgId,
|
||||||
|
findByAuthMethod,
|
||||||
|
findTemplateUsages,
|
||||||
|
findByIdAndOrgId
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export enum IdentityAuthTemplateMethod {
|
||||||
|
LDAP = "ldap"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TEMPLATE_VALIDATION_MESSAGES = {
|
||||||
|
TEMPLATE_NAME_REQUIRED: "Template name is required",
|
||||||
|
TEMPLATE_NAME_MAX_LENGTH: "Template name must be at most 64 characters long",
|
||||||
|
AUTH_METHOD_REQUIRED: "Auth method is required",
|
||||||
|
TEMPLATE_ID_REQUIRED: "Template ID is required",
|
||||||
|
LDAP: {
|
||||||
|
URL_REQUIRED: "LDAP URL is required",
|
||||||
|
BIND_DN_REQUIRED: "Bind DN is required",
|
||||||
|
BIND_PASSWORD_REQUIRED: "Bind password is required",
|
||||||
|
SEARCH_BASE_REQUIRED: "Search base is required"
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const TEMPLATE_SUCCESS_MESSAGES = {
|
||||||
|
CREATED: "Template created successfully",
|
||||||
|
UPDATED: "Template updated successfully",
|
||||||
|
DELETED: "Template deleted successfully"
|
||||||
|
} as const;
|
||||||
@@ -0,0 +1,454 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
|
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
|
import {
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions,
|
||||||
|
OrgPermissionSubjects
|
||||||
|
} from "@app/ee/services/permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TIdentityLdapAuthDALFactory } from "@app/services/identity-ldap-auth/identity-ldap-auth-dal";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
|
import { TIdentityAuthTemplateDALFactory } from "./identity-auth-template-dal";
|
||||||
|
import { IdentityAuthTemplateMethod } from "./identity-auth-template-enums";
|
||||||
|
import {
|
||||||
|
TDeleteIdentityAuthTemplateDTO,
|
||||||
|
TFindTemplateUsagesDTO,
|
||||||
|
TGetIdentityAuthTemplateDTO,
|
||||||
|
TGetTemplatesByAuthMethodDTO,
|
||||||
|
TLdapTemplateFields,
|
||||||
|
TListIdentityAuthTemplatesDTO,
|
||||||
|
TUnlinkTemplateUsageDTO
|
||||||
|
} from "./identity-auth-template-types";
|
||||||
|
|
||||||
|
type TIdentityAuthTemplateServiceFactoryDep = {
|
||||||
|
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
|
||||||
|
identityLdapAuthDAL: TIdentityLdapAuthDALFactory;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey" | "encryptWithInputKey" | "decryptWithInputKey">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIdentityAuthTemplateServiceFactory = ReturnType<typeof identityAuthTemplateServiceFactory>;
|
||||||
|
|
||||||
|
export const identityAuthTemplateServiceFactory = ({
|
||||||
|
identityAuthTemplateDAL,
|
||||||
|
identityLdapAuthDAL,
|
||||||
|
permissionService,
|
||||||
|
kmsService,
|
||||||
|
licenseService,
|
||||||
|
auditLogService
|
||||||
|
}: TIdentityAuthTemplateServiceFactoryDep) => {
|
||||||
|
// Plan check
|
||||||
|
const $checkPlan = async (orgId: string) => {
|
||||||
|
const plan = await licenseService.getPlan(orgId);
|
||||||
|
if (!plan.machineIdentityAuthTemplates)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to use identity auth template due to plan restriction. Upgrade plan to access machine identity auth templates."
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const createTemplate = async ({
|
||||||
|
name,
|
||||||
|
authMethod,
|
||||||
|
templateFields,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
authMethod: string;
|
||||||
|
templateFields: Record<string, unknown>;
|
||||||
|
} & Omit<TOrgPermission, "orgId">) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
const template = await identityAuthTemplateDAL.create({
|
||||||
|
name,
|
||||||
|
authMethod,
|
||||||
|
templateFields: encryptor({ plainText: Buffer.from(JSON.stringify(templateFields)) }).cipherTextBlob,
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...template, templateFields };
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTemplate = async ({
|
||||||
|
templateId,
|
||||||
|
name,
|
||||||
|
templateFields,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: {
|
||||||
|
templateId: string;
|
||||||
|
name?: string;
|
||||||
|
templateFields?: Record<string, unknown>;
|
||||||
|
} & Omit<TOrgPermission, "orgId">) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
|
||||||
|
if (!template) {
|
||||||
|
throw new NotFoundError({ message: "Template not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
template.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: template.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
let finalTemplateFields: Record<string, unknown> = {};
|
||||||
|
|
||||||
|
const updatedTemplate = await identityAuthTemplateDAL.transaction(async (tx) => {
|
||||||
|
const authTemplate = await identityAuthTemplateDAL.updateById(
|
||||||
|
templateId,
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
...(templateFields && {
|
||||||
|
templateFields: encryptor({ plainText: Buffer.from(JSON.stringify(templateFields)) }).cipherTextBlob
|
||||||
|
})
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (templateFields && template.authMethod === IdentityAuthTemplateMethod.LDAP) {
|
||||||
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: template.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentTemplateFields = JSON.parse(
|
||||||
|
decryptor({ cipherTextBlob: template.templateFields }).toString()
|
||||||
|
) as TLdapTemplateFields;
|
||||||
|
|
||||||
|
const mergedTemplateFields: TLdapTemplateFields = { ...currentTemplateFields, ...templateFields };
|
||||||
|
finalTemplateFields = mergedTemplateFields;
|
||||||
|
const ldapUpdateData: {
|
||||||
|
url?: string;
|
||||||
|
searchBase?: string;
|
||||||
|
encryptedBindDN?: Buffer;
|
||||||
|
encryptedBindPass?: Buffer;
|
||||||
|
encryptedLdapCaCertificate?: Buffer;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
if ("url" in templateFields) {
|
||||||
|
ldapUpdateData.url = mergedTemplateFields.url;
|
||||||
|
}
|
||||||
|
if ("searchBase" in templateFields) {
|
||||||
|
ldapUpdateData.searchBase = mergedTemplateFields.searchBase;
|
||||||
|
}
|
||||||
|
if ("bindDN" in templateFields) {
|
||||||
|
ldapUpdateData.encryptedBindDN = encryptor({
|
||||||
|
plainText: Buffer.from(mergedTemplateFields.bindDN)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
}
|
||||||
|
if ("bindPass" in templateFields) {
|
||||||
|
ldapUpdateData.encryptedBindPass = encryptor({
|
||||||
|
plainText: Buffer.from(mergedTemplateFields.bindPass)
|
||||||
|
}).cipherTextBlob;
|
||||||
|
}
|
||||||
|
if ("ldapCaCertificate" in templateFields) {
|
||||||
|
ldapUpdateData.encryptedLdapCaCertificate = encryptor({
|
||||||
|
plainText: Buffer.from(mergedTemplateFields.ldapCaCertificate || "")
|
||||||
|
}).cipherTextBlob;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(ldapUpdateData).length > 0) {
|
||||||
|
const updatedLdapAuths = await identityLdapAuthDAL.update({ templateId }, ldapUpdateData, tx);
|
||||||
|
await Promise.all(
|
||||||
|
updatedLdapAuths.map(async (updatedLdapAuth) => {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
actor: {
|
||||||
|
type: ActorType.PLATFORM,
|
||||||
|
metadata: {}
|
||||||
|
},
|
||||||
|
orgId: actorOrgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_IDENTITY_LDAP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: updatedLdapAuth.identityId,
|
||||||
|
templateId: template.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return authTemplate;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...updatedTemplate, templateFields: finalTemplateFields };
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteTemplate = async ({
|
||||||
|
templateId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TDeleteIdentityAuthTemplateDTO) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
|
||||||
|
if (!template) {
|
||||||
|
throw new NotFoundError({ message: "Template not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
template.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletedTemplate = await identityAuthTemplateDAL.transaction(async (tx) => {
|
||||||
|
// Remove template reference from identityLdapAuth records
|
||||||
|
const updatedLdapAuths = await identityLdapAuthDAL.update({ templateId }, { templateId: null }, tx);
|
||||||
|
await Promise.all(
|
||||||
|
updatedLdapAuths.map(async (updatedLdapAuth) => {
|
||||||
|
await auditLogService.createAuditLog({
|
||||||
|
actor: {
|
||||||
|
type: ActorType.PLATFORM,
|
||||||
|
metadata: {}
|
||||||
|
},
|
||||||
|
orgId: actorOrgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.UPDATE_IDENTITY_LDAP_AUTH,
|
||||||
|
metadata: {
|
||||||
|
identityId: updatedLdapAuth.identityId,
|
||||||
|
templateId: template.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete the template
|
||||||
|
const [deletedTpl] = await identityAuthTemplateDAL.delete({ id: templateId }, tx);
|
||||||
|
return deletedTpl;
|
||||||
|
});
|
||||||
|
|
||||||
|
return deletedTemplate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTemplate = async ({
|
||||||
|
templateId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetIdentityAuthTemplateDTO) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
|
||||||
|
if (!template) {
|
||||||
|
throw new NotFoundError({ message: "Template not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
template.orgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: template.orgId
|
||||||
|
});
|
||||||
|
const decryptedTemplateFields = decryptor({ cipherTextBlob: template.templateFields }).toString();
|
||||||
|
return {
|
||||||
|
...template,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
templateFields: JSON.parse(decryptedTemplateFields)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const listTemplates = async ({
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
search,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TListIdentityAuthTemplatesDTO) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const { docs, totalCount } = await identityAuthTemplateDAL.findByOrgId(actorOrgId, { limit, offset, search });
|
||||||
|
|
||||||
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
totalCount,
|
||||||
|
templates: docs.map((doc) => ({
|
||||||
|
...doc,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
templateFields: JSON.parse(decryptor({ cipherTextBlob: doc.templateFields }).toString())
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTemplatesByAuthMethod = async ({
|
||||||
|
authMethod,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TGetTemplatesByAuthMethodDTO) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const docs = await identityAuthTemplateDAL.findByAuthMethod(authMethod, actorOrgId);
|
||||||
|
|
||||||
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: actorOrgId
|
||||||
|
});
|
||||||
|
return docs.map((doc) => ({
|
||||||
|
...doc,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
templateFields: JSON.parse(decryptor({ cipherTextBlob: doc.templateFields }).toString())
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const findTemplateUsages = async ({
|
||||||
|
templateId,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TFindTemplateUsagesDTO) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
|
||||||
|
if (!template) {
|
||||||
|
throw new NotFoundError({ message: "Template not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const docs = await identityAuthTemplateDAL.findTemplateUsages(templateId, template.authMethod);
|
||||||
|
return docs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unlinkTemplateUsage = async ({
|
||||||
|
templateId,
|
||||||
|
identityIds,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actor,
|
||||||
|
actorOrgId
|
||||||
|
}: TUnlinkTemplateUsageDTO) => {
|
||||||
|
await $checkPlan(actorOrgId);
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId
|
||||||
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
|
const template = await identityAuthTemplateDAL.findByIdAndOrgId(templateId, actorOrgId);
|
||||||
|
if (!template) {
|
||||||
|
throw new NotFoundError({ message: "Template not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (template.authMethod) {
|
||||||
|
case IdentityAuthTemplateMethod.LDAP:
|
||||||
|
await identityLdapAuthDAL.update({ $in: { identityId: identityIds }, templateId }, { templateId: null });
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createTemplate,
|
||||||
|
updateTemplate,
|
||||||
|
deleteTemplate,
|
||||||
|
getTemplate,
|
||||||
|
listTemplates,
|
||||||
|
getTemplatesByAuthMethod,
|
||||||
|
findTemplateUsages,
|
||||||
|
unlinkTemplateUsage
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
|
import { IdentityAuthTemplateMethod } from "./identity-auth-template-enums";
|
||||||
|
|
||||||
|
// Method-specific template field types
|
||||||
|
export type TLdapTemplateFields = {
|
||||||
|
url: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
searchBase: string;
|
||||||
|
ldapCaCertificate?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Union type for all template field types
|
||||||
|
export type TTemplateFieldsByMethod = {
|
||||||
|
[IdentityAuthTemplateMethod.LDAP]: TLdapTemplateFields;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generic base types that use conditional types for type safety
|
||||||
|
export type TCreateIdentityAuthTemplateDTO = {
|
||||||
|
name: string;
|
||||||
|
authMethod: IdentityAuthTemplateMethod;
|
||||||
|
templateFields: TTemplateFieldsByMethod[IdentityAuthTemplateMethod];
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUpdateIdentityAuthTemplateDTO = {
|
||||||
|
templateId: string;
|
||||||
|
name?: string;
|
||||||
|
templateFields?: Partial<TTemplateFieldsByMethod[IdentityAuthTemplateMethod]>;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TDeleteIdentityAuthTemplateDTO = {
|
||||||
|
templateId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetIdentityAuthTemplateDTO = {
|
||||||
|
templateId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TListIdentityAuthTemplatesDTO = {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
search?: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetTemplatesByAuthMethodDTO = {
|
||||||
|
authMethod: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TFindTemplateUsagesDTO = {
|
||||||
|
templateId: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TUnlinkTemplateUsageDTO = {
|
||||||
|
templateId: string;
|
||||||
|
identityIds: string[];
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
// Specific LDAP types for convenience
|
||||||
|
export type TCreateLdapTemplateDTO = TCreateIdentityAuthTemplateDTO;
|
||||||
|
export type TUpdateLdapTemplateDTO = TUpdateIdentityAuthTemplateDTO;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export type { TIdentityAuthTemplateDALFactory } from "./identity-auth-template-dal";
|
||||||
|
export { identityAuthTemplateDALFactory } from "./identity-auth-template-dal";
|
||||||
|
export * from "./identity-auth-template-enums";
|
||||||
|
export type { TIdentityAuthTemplateServiceFactory } from "./identity-auth-template-service";
|
||||||
|
export { identityAuthTemplateServiceFactory } from "./identity-auth-template-service";
|
||||||
|
export type * from "./identity-auth-template-types";
|
||||||
@@ -55,7 +55,7 @@ type TLdapConfigServiceFactoryDep = {
|
|||||||
groupDAL: Pick<TGroupDALFactory, "find" | "findOne">;
|
groupDAL: Pick<TGroupDALFactory, "find" | "findOne">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
userGroupMembershipDAL: Pick<
|
userGroupMembershipDAL: Pick<
|
||||||
TUserGroupMembershipDALFactory,
|
TUserGroupMembershipDALFactory,
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
caCrl: false,
|
caCrl: false,
|
||||||
sshHostGroups: false,
|
sshHostGroups: false,
|
||||||
enterpriseSecretSyncs: false,
|
enterpriseSecretSyncs: false,
|
||||||
enterpriseAppConnections: false
|
enterpriseAppConnections: false,
|
||||||
|
machineIdentityAuthTemplates: false
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
enterpriseSecretSyncs: false,
|
enterpriseSecretSyncs: false,
|
||||||
enterpriseAppConnections: false,
|
enterpriseAppConnections: false,
|
||||||
fips: false,
|
fips: false,
|
||||||
eventSubscriptions: false
|
eventSubscriptions: false,
|
||||||
|
machineIdentityAuthTemplates: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export const setupLicenseRequestWithStore = (
|
export const setupLicenseRequestWithStore = (
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export type TFeatureSet = {
|
|||||||
secretScanning: false;
|
secretScanning: false;
|
||||||
enterpriseSecretSyncs: false;
|
enterpriseSecretSyncs: false;
|
||||||
enterpriseAppConnections: false;
|
enterpriseAppConnections: false;
|
||||||
|
machineIdentityAuthTemplates: false;
|
||||||
fips: false;
|
fips: false;
|
||||||
eventSubscriptions: false;
|
eventSubscriptions: false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ type TOidcConfigServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany" | "delete">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ export enum OrgPermissionKmipActions {
|
|||||||
Setup = "setup"
|
Setup = "setup"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionMachineIdentityAuthTemplateActions {
|
||||||
|
ListTemplates = "list-templates",
|
||||||
|
EditTemplates = "edit-templates",
|
||||||
|
CreateTemplates = "create-templates",
|
||||||
|
DeleteTemplates = "delete-templates",
|
||||||
|
UnlinkTemplates = "unlink-templates",
|
||||||
|
AttachTemplates = "attach-templates"
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
AccessAllProjects = "access-all-projects"
|
AccessAllProjects = "access-all-projects"
|
||||||
}
|
}
|
||||||
@@ -88,6 +97,7 @@ export enum OrgPermissionSubjects {
|
|||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
Kms = "kms",
|
Kms = "kms",
|
||||||
AdminConsole = "organization-admin-console",
|
AdminConsole = "organization-admin-console",
|
||||||
|
MachineIdentityAuthTemplate = "machine-identity-auth-template",
|
||||||
AuditLogs = "audit-logs",
|
AuditLogs = "audit-logs",
|
||||||
ProjectTemplates = "project-templates",
|
ProjectTemplates = "project-templates",
|
||||||
AppConnections = "app-connections",
|
AppConnections = "app-connections",
|
||||||
@@ -126,6 +136,7 @@ export type OrgPermissionSet =
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
| [OrgPermissionAdminConsoleAction, OrgPermissionSubjects.AdminConsole]
|
||||||
|
| [OrgPermissionMachineIdentityAuthTemplateActions, OrgPermissionSubjects.MachineIdentityAuthTemplate]
|
||||||
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
|
| [OrgPermissionKmipActions, OrgPermissionSubjects.Kmip]
|
||||||
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
|
| [OrgPermissionSecretShareAction, OrgPermissionSubjects.SecretShare];
|
||||||
|
|
||||||
@@ -237,6 +248,14 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z
|
||||||
|
.literal(OrgPermissionSubjects.MachineIdentityAuthTemplate)
|
||||||
|
.describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionMachineIdentityAuthTemplateActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(OrgPermissionSubjects.Gateway).describe("The entity this permission pertains to."),
|
subject: z.literal(OrgPermissionSubjects.Gateway).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionGatewayActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionGatewayActions).describe(
|
||||||
@@ -350,6 +369,25 @@ const buildAdminPermission = () => {
|
|||||||
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
|
// the proxy assignment is temporary in order to prevent "more privilege" error during role assignment to MI
|
||||||
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
can(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
||||||
|
|
||||||
|
can(OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionSubjects.MachineIdentityAuthTemplate);
|
||||||
|
can(OrgPermissionMachineIdentityAuthTemplateActions.EditTemplates, OrgPermissionSubjects.MachineIdentityAuthTemplate);
|
||||||
|
can(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.CreateTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.DeleteTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare);
|
can(OrgPermissionSecretShareAction.ManageSettings, OrgPermissionSubjects.SecretShare);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
@@ -385,6 +423,16 @@ const buildMemberPermission = () => {
|
|||||||
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
|
can(OrgPermissionGatewayActions.CreateGateways, OrgPermissionSubjects.Gateway);
|
||||||
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
|
can(OrgPermissionGatewayActions.AttachGateways, OrgPermissionSubjects.Gateway);
|
||||||
|
|
||||||
|
can(OrgPermissionMachineIdentityAuthTemplateActions.ListTemplates, OrgPermissionSubjects.MachineIdentityAuthTemplate);
|
||||||
|
can(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.UnlinkTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
can(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
|
||||||
return rules;
|
return rules;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ type TScimServiceFactoryDep = {
|
|||||||
TOrgMembershipDALFactory,
|
TOrgMembershipDALFactory,
|
||||||
"find" | "findOne" | "create" | "updateById" | "findById" | "update"
|
"find" | "findOne" | "create" | "updateById" | "findById" | "update"
|
||||||
>;
|
>;
|
||||||
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser">;
|
projectDAL: Pick<TProjectDALFactory, "find" | "findProjectGhostUser" | "findById">;
|
||||||
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find" | "delete" | "findProjectMembershipsByUserId">;
|
||||||
groupDAL: Pick<
|
groupDAL: Pick<
|
||||||
TGroupDALFactory,
|
TGroupDALFactory,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const baseSecretScanningDataSourceQuery = ({
|
|||||||
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
||||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||||
|
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||||
db
|
db
|
||||||
@@ -82,6 +83,7 @@ const expandSecretScanningDataSource = <
|
|||||||
connectionUpdatedAt,
|
connectionUpdatedAt,
|
||||||
connectionVersion,
|
connectionVersion,
|
||||||
connectionIsPlatformManagedCredentials,
|
connectionIsPlatformManagedCredentials,
|
||||||
|
connectionGatewayId,
|
||||||
...el
|
...el
|
||||||
} = dataSource;
|
} = dataSource;
|
||||||
|
|
||||||
@@ -100,7 +102,8 @@ const expandSecretScanningDataSource = <
|
|||||||
createdAt: connectionCreatedAt,
|
createdAt: connectionCreatedAt,
|
||||||
updatedAt: connectionUpdatedAt,
|
updatedAt: connectionUpdatedAt,
|
||||||
version: connectionVersion,
|
version: connectionVersion,
|
||||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||||
|
gatewayId: connectionGatewayId
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "@app/services/
|
|||||||
|
|
||||||
export enum ApiDocsTags {
|
export enum ApiDocsTags {
|
||||||
Identities = "Identities",
|
Identities = "Identities",
|
||||||
|
IdentityTemplates = "Identity Templates",
|
||||||
TokenAuth = "Token Auth",
|
TokenAuth = "Token Auth",
|
||||||
UniversalAuth = "Universal Auth",
|
UniversalAuth = "Universal Auth",
|
||||||
GcpAuth = "GCP Auth",
|
GcpAuth = "GCP Auth",
|
||||||
@@ -215,6 +216,7 @@ export const LDAP_AUTH = {
|
|||||||
password: "The password of the LDAP user to login."
|
password: "The password of the LDAP user to login."
|
||||||
},
|
},
|
||||||
ATTACH: {
|
ATTACH: {
|
||||||
|
templateId: "The ID of the identity auth template to attach the configuration onto.",
|
||||||
identityId: "The ID of the identity to attach the configuration onto.",
|
identityId: "The ID of the identity to attach the configuration onto.",
|
||||||
url: "The URL of the LDAP server.",
|
url: "The URL of the LDAP server.",
|
||||||
allowedFields:
|
allowedFields:
|
||||||
@@ -241,7 +243,8 @@ export const LDAP_AUTH = {
|
|||||||
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
accessTokenTTL: "The new lifetime for an access token in seconds.",
|
||||||
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
accessTokenMaxTTL: "The new maximum lifetime for an access token in seconds.",
|
||||||
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
accessTokenNumUsesLimit: "The new maximum number of times that an access token can be used.",
|
||||||
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from."
|
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
|
||||||
|
templateId: "The ID of the identity auth template to update the configuration to."
|
||||||
},
|
},
|
||||||
RETRIEVE: {
|
RETRIEVE: {
|
||||||
identityId: "The ID of the identity to retrieve the configuration for."
|
identityId: "The ID of the identity to retrieve the configuration for."
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ const envSchema = z
|
|||||||
AUDIT_LOGS_DB_ROOT_CERT: zpStr(
|
AUDIT_LOGS_DB_ROOT_CERT: zpStr(
|
||||||
z.string().describe("Postgres database base64-encoded CA cert for Audit logs").optional()
|
z.string().describe("Postgres database base64-encoded CA cert for Audit logs").optional()
|
||||||
),
|
),
|
||||||
|
DISABLE_AUDIT_LOG_STORAGE: zodStrBool.default("false").optional().describe("Disable audit log storage"),
|
||||||
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
|
MAX_LEASE_LIMIT: z.coerce.number().default(10000),
|
||||||
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
DB_ROOT_CERT: zpStr(z.string().describe("Postgres database base64-encoded CA cert").optional()),
|
||||||
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
|
DB_HOST: zpStr(z.string().describe("Postgres database host").optional()),
|
||||||
@@ -482,6 +483,15 @@ export const overwriteSchema: {
|
|||||||
fields: { key: keyof TEnvConfig; description?: string }[];
|
fields: { key: keyof TEnvConfig; description?: string }[];
|
||||||
};
|
};
|
||||||
} = {
|
} = {
|
||||||
|
auditLogs: {
|
||||||
|
name: "Audit Logs",
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: "DISABLE_AUDIT_LOG_STORAGE",
|
||||||
|
description: "Disable audit log storage"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
aws: {
|
aws: {
|
||||||
name: "AWS",
|
name: "AWS",
|
||||||
fields: [
|
fields: [
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ type DecryptedIntegrationAuths = z.infer<typeof DecryptedIntegrationAuthsSchema>
|
|||||||
|
|
||||||
type TLatestKey = TProjectKeys & {
|
type TLatestKey = TProjectKeys & {
|
||||||
sender: {
|
sender: {
|
||||||
publicKey: string;
|
publicKey?: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -91,6 +91,10 @@ const getDecryptedValues = (data: Array<{ ciphertext: string; iv: string; tag: s
|
|||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
export const decryptSecrets = (encryptedSecrets: TSecrets[], privateKey: string, latestKey: TLatestKey) => {
|
export const decryptSecrets = (encryptedSecrets: TSecrets[], privateKey: string, latestKey: TLatestKey) => {
|
||||||
|
if (!latestKey.sender.publicKey) {
|
||||||
|
throw new Error("Latest key sender public key not found");
|
||||||
|
}
|
||||||
|
|
||||||
const key = crypto.encryption().asymmetric().decrypt({
|
const key = crypto.encryption().asymmetric().decrypt({
|
||||||
ciphertext: latestKey.encryptedKey,
|
ciphertext: latestKey.encryptedKey,
|
||||||
nonce: latestKey.nonce,
|
nonce: latestKey.nonce,
|
||||||
@@ -143,6 +147,10 @@ export const decryptSecretVersions = (
|
|||||||
privateKey: string,
|
privateKey: string,
|
||||||
latestKey: TLatestKey
|
latestKey: TLatestKey
|
||||||
) => {
|
) => {
|
||||||
|
if (!latestKey.sender.publicKey) {
|
||||||
|
throw new Error("Latest key sender public key not found");
|
||||||
|
}
|
||||||
|
|
||||||
const key = crypto.encryption().asymmetric().decrypt({
|
const key = crypto.encryption().asymmetric().decrypt({
|
||||||
ciphertext: latestKey.encryptedKey,
|
ciphertext: latestKey.encryptedKey,
|
||||||
nonce: latestKey.nonce,
|
nonce: latestKey.nonce,
|
||||||
@@ -195,6 +203,10 @@ export const decryptSecretApprovals = (
|
|||||||
privateKey: string,
|
privateKey: string,
|
||||||
latestKey: TLatestKey
|
latestKey: TLatestKey
|
||||||
) => {
|
) => {
|
||||||
|
if (!latestKey.sender.publicKey) {
|
||||||
|
throw new Error("Latest key sender public key not found");
|
||||||
|
}
|
||||||
|
|
||||||
const key = crypto.encryption().asymmetric().decrypt({
|
const key = crypto.encryption().asymmetric().decrypt({
|
||||||
ciphertext: latestKey.encryptedKey,
|
ciphertext: latestKey.encryptedKey,
|
||||||
nonce: latestKey.nonce,
|
nonce: latestKey.nonce,
|
||||||
@@ -247,6 +259,10 @@ export const decryptIntegrationAuths = (
|
|||||||
privateKey: string,
|
privateKey: string,
|
||||||
latestKey: TLatestKey
|
latestKey: TLatestKey
|
||||||
) => {
|
) => {
|
||||||
|
if (!latestKey.sender.publicKey) {
|
||||||
|
throw new Error("Latest key sender public key not found");
|
||||||
|
}
|
||||||
|
|
||||||
const key = crypto.encryption().asymmetric().decrypt({
|
const key = crypto.encryption().asymmetric().decrypt({
|
||||||
ciphertext: latestKey.encryptedKey,
|
ciphertext: latestKey.encryptedKey,
|
||||||
nonce: latestKey.nonce,
|
nonce: latestKey.nonce,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import jsrp from "jsrp";
|
|||||||
import { TUserEncryptionKeys } from "@app/db/schemas";
|
import { TUserEncryptionKeys } from "@app/db/schemas";
|
||||||
import { UserEncryption } from "@app/services/user/user-types";
|
import { UserEncryption } from "@app/services/user/user-types";
|
||||||
|
|
||||||
|
import { BadRequestError } from "../errors";
|
||||||
import { crypto, SymmetricKeySize } from "./cryptography";
|
import { crypto, SymmetricKeySize } from "./cryptography";
|
||||||
|
|
||||||
export const generateSrpServerKey = async (salt: string, verifier: string) => {
|
export const generateSrpServerKey = async (salt: string, verifier: string) => {
|
||||||
@@ -127,6 +128,10 @@ export const getUserPrivateKey = async (
|
|||||||
>
|
>
|
||||||
) => {
|
) => {
|
||||||
if (user.encryptionVersion === UserEncryption.V1) {
|
if (user.encryptionVersion === UserEncryption.V1) {
|
||||||
|
if (!user.encryptedPrivateKey || !user.iv || !user.tag || !user.salt) {
|
||||||
|
throw new BadRequestError({ message: "User encrypted private key not found" });
|
||||||
|
}
|
||||||
|
|
||||||
return crypto
|
return crypto
|
||||||
.encryption()
|
.encryption()
|
||||||
.symmetric()
|
.symmetric()
|
||||||
@@ -138,12 +143,25 @@ export const getUserPrivateKey = async (
|
|||||||
keySize: SymmetricKeySize.Bits128
|
keySize: SymmetricKeySize.Bits128
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// still used for legacy things
|
||||||
if (
|
if (
|
||||||
user.encryptionVersion === UserEncryption.V2 &&
|
user.encryptionVersion === UserEncryption.V2 &&
|
||||||
user.protectedKey &&
|
user.protectedKey &&
|
||||||
user.protectedKeyIV &&
|
user.protectedKeyIV &&
|
||||||
user.protectedKeyTag
|
user.protectedKeyTag
|
||||||
) {
|
) {
|
||||||
|
if (
|
||||||
|
!user.salt ||
|
||||||
|
!user.protectedKey ||
|
||||||
|
!user.protectedKeyIV ||
|
||||||
|
!user.protectedKeyTag ||
|
||||||
|
!user.encryptedPrivateKey ||
|
||||||
|
!user.iv ||
|
||||||
|
!user.tag
|
||||||
|
) {
|
||||||
|
throw new BadRequestError({ message: "User encrypted private key not found" });
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -179,6 +179,8 @@ import { identityAccessTokenDALFactory } from "@app/services/identity-access-tok
|
|||||||
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
import { identityAccessTokenServiceFactory } from "@app/services/identity-access-token/identity-access-token-service";
|
||||||
import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal";
|
import { identityAliCloudAuthDALFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-dal";
|
||||||
import { identityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
|
import { identityAliCloudAuthServiceFactory } from "@app/services/identity-alicloud-auth/identity-alicloud-auth-service";
|
||||||
|
import { identityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-dal";
|
||||||
|
import { identityAuthTemplateServiceFactory } from "@app/ee/services/identity-auth-template/identity-auth-template-service";
|
||||||
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
|
import { identityAwsAuthDALFactory } from "@app/services/identity-aws-auth/identity-aws-auth-dal";
|
||||||
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
import { identityAwsAuthServiceFactory } from "@app/services/identity-aws-auth/identity-aws-auth-service";
|
||||||
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
|
import { identityAzureAuthDALFactory } from "@app/services/identity-azure-auth/identity-azure-auth-dal";
|
||||||
@@ -394,6 +396,7 @@ export const registerRoutes = async (
|
|||||||
const identityProjectDAL = identityProjectDALFactory(db);
|
const identityProjectDAL = identityProjectDALFactory(db);
|
||||||
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
|
const identityProjectMembershipRoleDAL = identityProjectMembershipRoleDALFactory(db);
|
||||||
const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db);
|
const identityProjectAdditionalPrivilegeDAL = identityProjectAdditionalPrivilegeDALFactory(db);
|
||||||
|
const identityAuthTemplateDAL = identityAuthTemplateDALFactory(db);
|
||||||
|
|
||||||
const identityTokenAuthDAL = identityTokenAuthDALFactory(db);
|
const identityTokenAuthDAL = identityTokenAuthDALFactory(db);
|
||||||
const identityUaDAL = identityUaDALFactory(db);
|
const identityUaDAL = identityUaDALFactory(db);
|
||||||
@@ -772,7 +775,6 @@ export const registerRoutes = async (
|
|||||||
orgRoleDAL,
|
orgRoleDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
projectBotDAL,
|
|
||||||
incidentContactDAL,
|
incidentContactDAL,
|
||||||
tokenService,
|
tokenService,
|
||||||
projectUserAdditionalPrivilegeDAL,
|
projectUserAdditionalPrivilegeDAL,
|
||||||
@@ -847,7 +849,6 @@ export const registerRoutes = async (
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
userDAL,
|
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectMembershipDAL
|
projectMembershipDAL
|
||||||
@@ -1135,11 +1136,9 @@ export const registerRoutes = async (
|
|||||||
projectBotService,
|
projectBotService,
|
||||||
identityProjectDAL,
|
identityProjectDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
projectKeyDAL,
|
|
||||||
userDAL,
|
userDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
orgService,
|
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
@@ -1159,7 +1158,6 @@ export const registerRoutes = async (
|
|||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
keyStore,
|
keyStore,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectBotDAL,
|
|
||||||
certificateTemplateDAL,
|
certificateTemplateDAL,
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
slackIntegrationDAL,
|
slackIntegrationDAL,
|
||||||
@@ -1461,6 +1459,15 @@ export const registerRoutes = async (
|
|||||||
identityMetadataDAL
|
identityMetadataDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const identityAuthTemplateService = identityAuthTemplateServiceFactory({
|
||||||
|
identityAuthTemplateDAL,
|
||||||
|
identityLdapAuthDAL,
|
||||||
|
permissionService,
|
||||||
|
kmsService,
|
||||||
|
licenseService,
|
||||||
|
auditLogService
|
||||||
|
});
|
||||||
|
|
||||||
const identityAccessTokenService = identityAccessTokenServiceFactory({
|
const identityAccessTokenService = identityAccessTokenServiceFactory({
|
||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
@@ -1604,7 +1611,8 @@ export const registerRoutes = async (
|
|||||||
identityAccessTokenDAL,
|
identityAccessTokenDAL,
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
identityDAL
|
identityDAL,
|
||||||
|
identityAuthTemplateDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const dynamicSecretProviders = buildDynamicSecretProviders({
|
const dynamicSecretProviders = buildDynamicSecretProviders({
|
||||||
@@ -2008,6 +2016,7 @@ export const registerRoutes = async (
|
|||||||
webhook: webhookService,
|
webhook: webhookService,
|
||||||
serviceToken: serviceTokenService,
|
serviceToken: serviceTokenService,
|
||||||
identity: identityService,
|
identity: identityService,
|
||||||
|
identityAuthTemplate: identityAuthTemplateService,
|
||||||
identityAccessToken: identityAccessTokenService,
|
identityAccessToken: identityAccessTokenService,
|
||||||
identityProject: identityProjectService,
|
identityProject: identityProjectService,
|
||||||
identityTokenAuth: identityTokenAuthService,
|
identityTokenAuth: identityTokenAuthService,
|
||||||
@@ -2144,7 +2153,8 @@ export const registerRoutes = async (
|
|||||||
inviteOnlySignup: z.boolean().optional(),
|
inviteOnlySignup: z.boolean().optional(),
|
||||||
redisConfigured: z.boolean().optional(),
|
redisConfigured: z.boolean().optional(),
|
||||||
secretScanningConfigured: z.boolean().optional(),
|
secretScanningConfigured: z.boolean().optional(),
|
||||||
samlDefaultOrgSlug: z.string().optional()
|
samlDefaultOrgSlug: z.string().optional(),
|
||||||
|
auditLogStorageDisabled: z.boolean().optional()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2171,7 +2181,8 @@ export const registerRoutes = async (
|
|||||||
inviteOnlySignup: Boolean(serverCfg.allowSignUp),
|
inviteOnlySignup: Boolean(serverCfg.allowSignUp),
|
||||||
redisConfigured: cfg.isRedisConfigured,
|
redisConfigured: cfg.isRedisConfigured,
|
||||||
secretScanningConfigured: cfg.isSecretScanningConfigured,
|
secretScanningConfigured: cfg.isSecretScanningConfigured,
|
||||||
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug
|
samlDefaultOrgSlug: cfg.samlDefaultOrgSlug,
|
||||||
|
auditLogStorageDisabled: Boolean(cfg.DISABLE_AUDIT_LOG_STORAGE)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -464,6 +464,42 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/user-management/users",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
userIds: z.string().array()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
users: UsersSchema.pick({
|
||||||
|
username: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
email: true,
|
||||||
|
id: true
|
||||||
|
}).array()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: (req, res, done) => {
|
||||||
|
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
|
||||||
|
verifySuperAdmin(req, res, done);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const users = await server.services.superAdmin.deleteUsers(req.body.userIds);
|
||||||
|
|
||||||
|
return {
|
||||||
|
users
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
url: "/user-management/users/:userId/admin-access",
|
url: "/user-management/users/:userId/admin-access",
|
||||||
|
|||||||
@@ -200,49 +200,104 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
|
|||||||
params: z.object({
|
params: z.object({
|
||||||
identityId: z.string().trim().describe(LDAP_AUTH.ATTACH.identityId)
|
identityId: z.string().trim().describe(LDAP_AUTH.ATTACH.identityId)
|
||||||
}),
|
}),
|
||||||
body: z
|
body: z.union([
|
||||||
.object({
|
// Template-based configuration
|
||||||
url: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.url),
|
z
|
||||||
bindDN: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindDN),
|
.object({
|
||||||
bindPass: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.bindPass),
|
templateId: z.string().trim().describe(LDAP_AUTH.ATTACH.templateId),
|
||||||
searchBase: z.string().trim().min(1).describe(LDAP_AUTH.ATTACH.searchBase),
|
searchFilter: z
|
||||||
searchFilter: z
|
.string()
|
||||||
.string()
|
.trim()
|
||||||
.trim()
|
.min(1)
|
||||||
.min(1)
|
.default("(uid={{username}})")
|
||||||
.default("(uid={{username}})")
|
.refine(isValidLdapFilter, "Invalid LDAP search filter")
|
||||||
.refine(isValidLdapFilter, "Invalid LDAP search filter")
|
.describe(LDAP_AUTH.ATTACH.searchFilter),
|
||||||
.describe(LDAP_AUTH.ATTACH.searchFilter),
|
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
|
||||||
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
|
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
|
||||||
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
|
accessTokenTrustedIps: z
|
||||||
accessTokenTrustedIps: z
|
.object({
|
||||||
.object({
|
ipAddress: z.string().trim()
|
||||||
ipAddress: z.string().trim()
|
})
|
||||||
})
|
.array()
|
||||||
.array()
|
.min(1)
|
||||||
.min(1)
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
|
accessTokenTTL: z
|
||||||
accessTokenTTL: z
|
.number()
|
||||||
.number()
|
.int()
|
||||||
.int()
|
.min(0)
|
||||||
.min(0)
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.default(2592000)
|
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
|
||||||
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
|
accessTokenMaxTTL: z
|
||||||
accessTokenMaxTTL: z
|
.number()
|
||||||
.number()
|
.int()
|
||||||
.int()
|
.min(1)
|
||||||
.min(1)
|
.max(315360000)
|
||||||
.max(315360000)
|
.default(2592000)
|
||||||
.default(2592000)
|
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
|
accessTokenNumUsesLimit: z
|
||||||
accessTokenNumUsesLimit: z.number().int().min(0).default(0).describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
|
.number()
|
||||||
})
|
.int()
|
||||||
.refine(
|
.min(0)
|
||||||
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
.default(0)
|
||||||
"Access Token TTL cannot be greater than Access Token Max TTL."
|
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
),
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
),
|
||||||
|
|
||||||
|
// Manual configuration
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
url: z.string().trim().describe(LDAP_AUTH.ATTACH.url),
|
||||||
|
bindDN: z.string().trim().describe(LDAP_AUTH.ATTACH.bindDN),
|
||||||
|
bindPass: z.string().trim().describe(LDAP_AUTH.ATTACH.bindPass),
|
||||||
|
searchBase: z.string().trim().describe(LDAP_AUTH.ATTACH.searchBase),
|
||||||
|
searchFilter: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.default("(uid={{username}})")
|
||||||
|
.refine(isValidLdapFilter, "Invalid LDAP search filter")
|
||||||
|
.describe(LDAP_AUTH.ATTACH.searchFilter),
|
||||||
|
allowedFields: AllowedFieldsSchema.array().optional().describe(LDAP_AUTH.ATTACH.allowedFields),
|
||||||
|
ldapCaCertificate: z.string().trim().optional().describe(LDAP_AUTH.ATTACH.ldapCaCertificate),
|
||||||
|
accessTokenTrustedIps: z
|
||||||
|
.object({
|
||||||
|
ipAddress: z.string().trim()
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.min(1)
|
||||||
|
.default([{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }])
|
||||||
|
.describe(LDAP_AUTH.ATTACH.accessTokenTrustedIps),
|
||||||
|
accessTokenTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(LDAP_AUTH.ATTACH.accessTokenTTL),
|
||||||
|
accessTokenMaxTTL: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(1)
|
||||||
|
.max(315360000)
|
||||||
|
.default(2592000)
|
||||||
|
.describe(LDAP_AUTH.ATTACH.accessTokenMaxTTL),
|
||||||
|
accessTokenNumUsesLimit: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.default(0)
|
||||||
|
.describe(LDAP_AUTH.ATTACH.accessTokenNumUsesLimit)
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(val) => val.accessTokenTTL <= val.accessTokenMaxTTL,
|
||||||
|
"Access Token TTL cannot be greater than Access Token Max TTL."
|
||||||
|
)
|
||||||
|
]),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
identityLdapAuth: IdentityLdapAuthsSchema.omit({
|
identityLdapAuth: IdentityLdapAuthsSchema.omit({
|
||||||
@@ -275,7 +330,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
|
|||||||
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
|
accessTokenMaxTTL: identityLdapAuth.accessTokenMaxTTL,
|
||||||
accessTokenTTL: identityLdapAuth.accessTokenTTL,
|
accessTokenTTL: identityLdapAuth.accessTokenTTL,
|
||||||
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
|
||||||
allowedFields: req.body.allowedFields
|
allowedFields: req.body.allowedFields,
|
||||||
|
templateId: identityLdapAuth.templateId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -309,6 +365,7 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
|
|||||||
bindDN: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindDN),
|
bindDN: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindDN),
|
||||||
bindPass: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindPass),
|
bindPass: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.bindPass),
|
||||||
searchBase: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.searchBase),
|
searchBase: z.string().trim().min(1).optional().describe(LDAP_AUTH.UPDATE.searchBase),
|
||||||
|
templateId: z.string().trim().optional().describe(LDAP_AUTH.UPDATE.templateId),
|
||||||
searchFilter: z
|
searchFilter: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -376,7 +433,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
|
|||||||
accessTokenTTL: identityLdapAuth.accessTokenTTL,
|
accessTokenTTL: identityLdapAuth.accessTokenTTL,
|
||||||
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit: identityLdapAuth.accessTokenNumUsesLimit,
|
||||||
accessTokenTrustedIps: identityLdapAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
accessTokenTrustedIps: identityLdapAuth.accessTokenTrustedIps as TIdentityTrustedIp[],
|
||||||
allowedFields: req.body.allowedFields
|
allowedFields: req.body.allowedFields,
|
||||||
|
templateId: identityLdapAuth.templateId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -413,7 +471,8 @@ export const registerIdentityLdapAuthRouter = async (server: FastifyZodProvider)
|
|||||||
}).extend({
|
}).extend({
|
||||||
bindDN: z.string(),
|
bindDN: z.string(),
|
||||||
bindPass: z.string(),
|
bindPass: z.string(),
|
||||||
ldapCaCertificate: z.string().optional()
|
ldapCaCertificate: z.string().optional(),
|
||||||
|
templateId: z.string().optional().nullable()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
lastName: true,
|
lastName: true,
|
||||||
id: true,
|
id: true,
|
||||||
superAdmin: true
|
superAdmin: true
|
||||||
}).merge(z.object({ publicKey: z.string().nullable() }))
|
}).merge(z.object({ publicKey: z.string().nullable().optional() }))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.omit({ createdAt: true, updatedAt: true })
|
.omit({ createdAt: true, updatedAt: true })
|
||||||
|
|||||||
@@ -9,73 +9,6 @@ import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
|||||||
import { UserEncryption } from "@app/services/user/user-types";
|
import { UserEncryption } from "@app/services/user/user-types";
|
||||||
|
|
||||||
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/srp1",
|
|
||||||
config: {
|
|
||||||
rateLimit: authRateLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
clientPublicKey: z.string().trim()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
serverPublicKey: z.string(),
|
|
||||||
salt: z.string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
handler: async (req) => {
|
|
||||||
const { salt, serverPublicKey } = await server.services.password.generateServerPubKey(
|
|
||||||
req.permission.id,
|
|
||||||
req.body.clientPublicKey
|
|
||||||
);
|
|
||||||
return { salt, serverPublicKey };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/change-password",
|
|
||||||
config: {
|
|
||||||
rateLimit: authRateLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
clientProof: z.string().trim(),
|
|
||||||
protectedKey: z.string().trim(),
|
|
||||||
protectedKeyIV: z.string().trim(),
|
|
||||||
protectedKeyTag: z.string().trim(),
|
|
||||||
encryptedPrivateKey: z.string().trim(),
|
|
||||||
encryptedPrivateKeyIV: z.string().trim(),
|
|
||||||
encryptedPrivateKeyTag: z.string().trim(),
|
|
||||||
salt: z.string().trim(),
|
|
||||||
verifier: z.string().trim(),
|
|
||||||
password: z.string().trim()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
message: z.string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
handler: async (req, res) => {
|
|
||||||
const appCfg = getConfig();
|
|
||||||
await server.services.password.changePassword({ ...req.body, userId: req.permission.id });
|
|
||||||
|
|
||||||
void res.cookie("jid", "", {
|
|
||||||
httpOnly: true,
|
|
||||||
path: "/",
|
|
||||||
sameSite: "strict",
|
|
||||||
secure: appCfg.HTTPS_ENABLED
|
|
||||||
});
|
|
||||||
return { message: "Successfully changed password" };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/email/password-reset",
|
url: "/email/password-reset",
|
||||||
@@ -131,41 +64,6 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "POST",
|
|
||||||
url: "/backup-private-key",
|
|
||||||
config: {
|
|
||||||
rateLimit: authRateLimit
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
|
||||||
schema: {
|
|
||||||
body: z.object({
|
|
||||||
clientProof: z.string().trim(),
|
|
||||||
encryptedPrivateKey: z.string().trim(),
|
|
||||||
iv: z.string().trim(),
|
|
||||||
tag: z.string().trim(),
|
|
||||||
salt: z.string().trim(),
|
|
||||||
verifier: z.string().trim()
|
|
||||||
}),
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
message: z.string(),
|
|
||||||
backupPrivateKey: BackupPrivateKeySchema.omit({ verifier: true })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handler: async (req) => {
|
|
||||||
const token = validateSignUpAuthorization(req.headers.authorization as string, "", false)!;
|
|
||||||
const backupPrivateKey = await server.services.password.createBackupPrivateKey({
|
|
||||||
...req.body,
|
|
||||||
userId: token.userId
|
|
||||||
});
|
|
||||||
if (!backupPrivateKey) throw new Error("Failed to create backup key");
|
|
||||||
|
|
||||||
return { message: "Successfully updated backup private key", backupPrivateKey };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/backup-private-key",
|
url: "/backup-private-key",
|
||||||
@@ -257,14 +155,6 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
schema: {
|
schema: {
|
||||||
body: z.object({
|
body: z.object({
|
||||||
protectedKey: z.string().trim(),
|
|
||||||
protectedKeyIV: z.string().trim(),
|
|
||||||
protectedKeyTag: z.string().trim(),
|
|
||||||
encryptedPrivateKey: z.string().trim(),
|
|
||||||
encryptedPrivateKeyIV: z.string().trim(),
|
|
||||||
encryptedPrivateKeyTag: z.string().trim(),
|
|
||||||
salt: z.string().trim(),
|
|
||||||
verifier: z.string().trim(),
|
|
||||||
password: z.string().trim(),
|
password: z.string().trim(),
|
||||||
token: z.string().trim()
|
token: z.string().trim()
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
publicKeys: z
|
publicKeys: z
|
||||||
.object({
|
.object({
|
||||||
publicKey: z.string().optional(),
|
publicKey: z.string().nullable().optional(),
|
||||||
userId: z.string()
|
userId: z.string()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
|
|||||||
message: z.string().trim().max(1024).optional(),
|
message: z.string().trim().max(1024).optional(),
|
||||||
repeatDays: z.number().min(1).nullable().optional(),
|
repeatDays: z.number().min(1).nullable().optional(),
|
||||||
nextReminderDate: z.string().datetime().nullable().optional(),
|
nextReminderDate: z.string().datetime().nullable().optional(),
|
||||||
|
fromDate: z.string().datetime().nullable().optional(),
|
||||||
recipients: z.string().array().optional()
|
recipients: z.string().array().optional()
|
||||||
})
|
})
|
||||||
.refine((data) => {
|
.refine((data) => {
|
||||||
@@ -45,6 +46,7 @@ export const registerSecretReminderRouter = async (server: FastifyZodProvider) =
|
|||||||
message: req.body.message,
|
message: req.body.message,
|
||||||
repeatDays: req.body.repeatDays,
|
repeatDays: req.body.repeatDays,
|
||||||
nextReminderDate: req.body.nextReminderDate,
|
nextReminderDate: req.body.nextReminderDate,
|
||||||
|
fromDate: req.body.fromDate,
|
||||||
recipients: req.body.recipients
|
recipients: req.body.recipients
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
|
import { UsersSchema } from "@app/db/schemas";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -19,23 +19,9 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
|||||||
schema: {
|
schema: {
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
user: UsersSchema.merge(
|
user: UsersSchema.extend({
|
||||||
UserEncryptionKeysSchema.pick({
|
encryptionVersion: z.number()
|
||||||
clientPublicKey: true,
|
})
|
||||||
serverPrivateKey: true,
|
|
||||||
encryptionVersion: true,
|
|
||||||
protectedKey: true,
|
|
||||||
protectedKeyIV: true,
|
|
||||||
protectedKeyTag: true,
|
|
||||||
publicKey: true,
|
|
||||||
encryptedPrivateKey: true,
|
|
||||||
iv: true,
|
|
||||||
tag: true,
|
|
||||||
salt: true,
|
|
||||||
verifier: true,
|
|
||||||
userId: true
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -94,26 +80,6 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
server.route({
|
|
||||||
method: "GET",
|
|
||||||
url: "/private-key",
|
|
||||||
config: {
|
|
||||||
rateLimit: readLimit
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
response: {
|
|
||||||
200: z.object({
|
|
||||||
privateKey: z.string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }),
|
|
||||||
handler: async (req) => {
|
|
||||||
const privateKey = await server.services.user.getUserPrivateKey(req.permission.id);
|
|
||||||
return { privateKey };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:userId/unlock",
|
url: "/:userId/unlock",
|
||||||
|
|||||||
@@ -97,13 +97,13 @@ export const registerMfaRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
encryptionVersion: z.number().default(1).nullable().optional(),
|
encryptionVersion: z.number().default(1).nullable().optional(),
|
||||||
protectedKey: z.string().nullable(),
|
protectedKey: z.string().nullish(),
|
||||||
protectedKeyIV: z.string().nullable(),
|
protectedKeyIV: z.string().nullish(),
|
||||||
protectedKeyTag: z.string().nullable(),
|
protectedKeyTag: z.string().nullish(),
|
||||||
publicKey: z.string(),
|
publicKey: z.string().nullish(),
|
||||||
encryptedPrivateKey: z.string(),
|
encryptedPrivateKey: z.string().nullish(),
|
||||||
iv: z.string(),
|
iv: z.string().nullish(),
|
||||||
tag: z.string(),
|
tag: z.string().nullish(),
|
||||||
token: z.string()
|
token: z.string()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
firstName: true,
|
firstName: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
id: true
|
id: true
|
||||||
}).extend({ publicKey: z.string().nullable() })
|
}).extend({ publicKey: z.string().nullish() })
|
||||||
}).omit({ createdAt: true, updatedAt: true })
|
}).omit({ createdAt: true, updatedAt: true })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { validatePasswordResetAuthorization } from "@app/services/auth/auth-fns";
|
import { validatePasswordResetAuthorization } from "@app/services/auth/auth-fns";
|
||||||
@@ -41,13 +42,38 @@ export const registerPasswordRouter = async (server: FastifyZodProvider) => {
|
|||||||
rateLimit: authRateLimit
|
rateLimit: authRateLimit
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }),
|
onRequest: verifyAuth([AuthMode.JWT], { requireOrg: false }),
|
||||||
handler: async (req) => {
|
handler: async (req, res) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
await server.services.password.resetPasswordV2({
|
await server.services.password.resetPasswordV2({
|
||||||
type: ResetPasswordV2Type.LoggedInReset,
|
type: ResetPasswordV2Type.LoggedInReset,
|
||||||
userId: req.permission.id,
|
userId: req.permission.id,
|
||||||
newPassword: req.body.newPassword,
|
newPassword: req.body.newPassword,
|
||||||
oldPassword: req.body.oldPassword
|
oldPassword: req.body.oldPassword
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void res.cookie("jid", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED
|
||||||
|
});
|
||||||
|
|
||||||
|
void res.cookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
void res.cookie("aod", "", {
|
||||||
|
httpOnly: false,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "lax",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: ProjectKeysSchema.merge(
|
200: ProjectKeysSchema.merge(
|
||||||
z.object({
|
z.object({
|
||||||
sender: z.object({
|
sender: z.object({
|
||||||
publicKey: z.string()
|
publicKey: z.string().optional()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
serverPublicKey: z.string(),
|
serverPublicKey: z.string().nullish(),
|
||||||
salt: z.string()
|
salt: z.string().nullish()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -124,14 +124,14 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
encryptionVersion: z.number().default(1).nullable().optional(),
|
encryptionVersion: z.number().default(1).nullish(),
|
||||||
protectedKey: z.string().nullable(),
|
protectedKey: z.string().nullish(),
|
||||||
protectedKeyIV: z.string().nullable(),
|
protectedKeyIV: z.string().nullish(),
|
||||||
protectedKeyTag: z.string().nullable(),
|
protectedKeyTag: z.string().nullish(),
|
||||||
publicKey: z.string(),
|
publicKey: z.string().nullish(),
|
||||||
encryptedPrivateKey: z.string(),
|
encryptedPrivateKey: z.string().nullish(),
|
||||||
iv: z.string(),
|
iv: z.string().nullish(),
|
||||||
tag: z.string(),
|
tag: z.string().nullish(),
|
||||||
token: z.string()
|
token: z.string()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -181,4 +181,59 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// New login route that doesn't use SRP
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/login",
|
||||||
|
config: {
|
||||||
|
rateLimit: authRateLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
email: z.string().trim(),
|
||||||
|
password: z.string().trim(),
|
||||||
|
providerAuthToken: z.string().trim().optional(),
|
||||||
|
captchaToken: z.string().trim().optional()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
accessToken: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const userAgent = req.headers["user-agent"];
|
||||||
|
if (!userAgent) throw new Error("user agent header is required");
|
||||||
|
|
||||||
|
const { tokens } = await server.services.login.login({
|
||||||
|
email: req.body.email,
|
||||||
|
password: req.body.password,
|
||||||
|
ip: req.realIp,
|
||||||
|
userAgent,
|
||||||
|
providerAuthToken: req.body.providerAuthToken,
|
||||||
|
captchaToken: req.body.captchaToken
|
||||||
|
});
|
||||||
|
const appCfg = getConfig();
|
||||||
|
|
||||||
|
void res.setCookie("jid", tokens.refreshToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED
|
||||||
|
});
|
||||||
|
|
||||||
|
addAuthOriginDomainCookie(res);
|
||||||
|
|
||||||
|
void res.cookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
return { accessToken: tokens.accessToken };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,15 +98,6 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
email: z.string().trim(),
|
email: z.string().trim(),
|
||||||
firstName: z.string().trim(),
|
firstName: z.string().trim(),
|
||||||
lastName: z.string().trim().optional(),
|
lastName: z.string().trim().optional(),
|
||||||
protectedKey: z.string().trim(),
|
|
||||||
protectedKeyIV: z.string().trim(),
|
|
||||||
protectedKeyTag: z.string().trim(),
|
|
||||||
publicKey: z.string().trim(),
|
|
||||||
encryptedPrivateKey: z.string().trim(),
|
|
||||||
encryptedPrivateKeyIV: z.string().trim(),
|
|
||||||
encryptedPrivateKeyTag: z.string().trim(),
|
|
||||||
salt: z.string().trim(),
|
|
||||||
verifier: z.string().trim(),
|
|
||||||
providerAuthToken: z.string().trim().optional().nullish(),
|
providerAuthToken: z.string().trim().optional().nullish(),
|
||||||
attributionSource: z.string().trim().optional(),
|
attributionSource: z.string().trim().optional(),
|
||||||
password: z.string()
|
password: z.string()
|
||||||
@@ -189,15 +180,6 @@ export const registerSignupRouter = async (server: FastifyZodProvider) => {
|
|||||||
password: z.string(),
|
password: z.string(),
|
||||||
firstName: z.string().trim(),
|
firstName: z.string().trim(),
|
||||||
lastName: z.string().trim().optional(),
|
lastName: z.string().trim().optional(),
|
||||||
protectedKey: z.string().trim(),
|
|
||||||
protectedKeyIV: z.string().trim(),
|
|
||||||
protectedKeyTag: z.string().trim(),
|
|
||||||
publicKey: z.string().trim(),
|
|
||||||
encryptedPrivateKey: z.string().trim(),
|
|
||||||
encryptedPrivateKeyIV: z.string().trim(),
|
|
||||||
encryptedPrivateKeyTag: z.string().trim(),
|
|
||||||
salt: z.string().trim(),
|
|
||||||
verifier: z.string().trim(),
|
|
||||||
tokenMetadata: z.string().optional()
|
tokenMetadata: z.string().optional()
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createAppAuth } from "@octokit/auth-app";
|
import { createAppAuth } from "@octokit/auth-app";
|
||||||
|
import { request } from "@octokit/request";
|
||||||
import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
|
import { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
import https from "https";
|
import https from "https";
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
@@ -12,7 +13,6 @@ import { GatewayProxyProtocol, withGatewayProxy } from "@app/lib/gateway";
|
|||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
import { blockLocalAndPrivateIpAddresses } from "@app/lib/validator";
|
||||||
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
import { getAppConnectionMethodName } from "@app/services/app-connection/app-connection-fns";
|
||||||
import { IntegrationUrls } from "@app/services/integration-auth/integration-list";
|
|
||||||
|
|
||||||
import { AppConnection } from "../app-connection-enums";
|
import { AppConnection } from "../app-connection-enums";
|
||||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
import { GitHubConnectionMethod } from "./github-connection-enums";
|
||||||
@@ -30,6 +30,23 @@ export const getGitHubConnectionListItem = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getGitHubInstanceApiUrl = async (config: {
|
||||||
|
credentials: Pick<TGitHubConnectionConfig["credentials"], "host" | "instanceType">;
|
||||||
|
}) => {
|
||||||
|
const host = config.credentials.host || "github.com";
|
||||||
|
|
||||||
|
await blockLocalAndPrivateIpAddresses(`https://${host}`);
|
||||||
|
|
||||||
|
let apiBase: string;
|
||||||
|
if (config.credentials.instanceType === "server") {
|
||||||
|
apiBase = `${host}/api/v3`;
|
||||||
|
} else {
|
||||||
|
apiBase = `api.${host}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiBase;
|
||||||
|
};
|
||||||
|
|
||||||
export const requestWithGitHubGateway = async <T>(
|
export const requestWithGitHubGateway = async <T>(
|
||||||
appConnection: { gatewayId?: string | null },
|
appConnection: { gatewayId?: string | null },
|
||||||
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
gatewayService: Pick<TGatewayServiceFactory, "fnGetGatewayClientTlsByGatewayId">,
|
||||||
@@ -73,7 +90,10 @@ export const requestWithGitHubGateway = async <T>(
|
|||||||
return await httpRequest.request(finalRequestConfig);
|
return await httpRequest.request(finalRequestConfig);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const axiosError = error as AxiosError;
|
const axiosError = error as AxiosError;
|
||||||
logger.error("Error during GitHub gateway request:", axiosError.message, axiosError.response?.data);
|
logger.error(
|
||||||
|
{ message: axiosError.message, data: axiosError.response?.data },
|
||||||
|
"Error during GitHub gateway request:"
|
||||||
|
);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -112,7 +132,10 @@ export const getGitHubAppAuthToken = async (appConnection: TGitHubConnection) =>
|
|||||||
const appAuth = createAppAuth({
|
const appAuth = createAppAuth({
|
||||||
appId,
|
appId,
|
||||||
privateKey: appPrivateKey,
|
privateKey: appPrivateKey,
|
||||||
installationId: appConnection.credentials.installationId
|
installationId: appConnection.credentials.installationId,
|
||||||
|
request: request.defaults({
|
||||||
|
baseUrl: `https://${await getGitHubInstanceApiUrl(appConnection)}`
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const { token } = await appAuth({ type: "installation" });
|
const { token } = await appAuth({ type: "installation" });
|
||||||
@@ -141,7 +164,7 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
|||||||
|
|
||||||
const token =
|
const token =
|
||||||
method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection);
|
method === GitHubConnectionMethod.OAuth ? credentials.accessToken : await getGitHubAppAuthToken(appConnection);
|
||||||
let url: string | null = `https://api.${credentials.host || "github.com"}${path}`;
|
let url: string | null = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`;
|
||||||
let results: T[] = [];
|
let results: T[] = [];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
@@ -325,6 +348,8 @@ export const validateGitHubConnectionCredentials = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
|
logger.error(e, "Unable to verify GitHub connection");
|
||||||
|
|
||||||
if (e instanceof BadRequestError) {
|
if (e instanceof BadRequestError) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@@ -355,7 +380,7 @@ export const validateGitHubConnectionCredentials = async (
|
|||||||
};
|
};
|
||||||
}[];
|
}[];
|
||||||
}>(config, gatewayService, {
|
}>(config, gatewayService, {
|
||||||
url: IntegrationUrls.GITHUB_USER_INSTALLATIONS.replace("api.github.com", `api.${host}`),
|
url: `https://${await getGitHubInstanceApiUrl(config)}/user/installations`,
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
Authorization: `Bearer ${tokenResp.data.access_token}`,
|
Authorization: `Bearer ${tokenResp.data.access_token}`,
|
||||||
@@ -377,11 +402,15 @@ export const validateGitHubConnectionCredentials = async (
|
|||||||
switch (method) {
|
switch (method) {
|
||||||
case GitHubConnectionMethod.App:
|
case GitHubConnectionMethod.App:
|
||||||
return {
|
return {
|
||||||
installationId: credentials.installationId
|
installationId: credentials.installationId,
|
||||||
|
instanceType: credentials.instanceType,
|
||||||
|
host: credentials.host
|
||||||
};
|
};
|
||||||
case GitHubConnectionMethod.OAuth:
|
case GitHubConnectionMethod.OAuth:
|
||||||
return {
|
return {
|
||||||
accessToken: tokenResp.data.access_token
|
accessToken: tokenResp.data.access_token,
|
||||||
|
instanceType: credentials.instanceType,
|
||||||
|
host: credentials.host
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
throw new InternalServerError({
|
throw new InternalServerError({
|
||||||
|
|||||||
@@ -10,26 +10,59 @@ import {
|
|||||||
|
|
||||||
import { GitHubConnectionMethod } from "./github-connection-enums";
|
import { GitHubConnectionMethod } from "./github-connection-enums";
|
||||||
|
|
||||||
export const GitHubConnectionOAuthInputCredentialsSchema = z.object({
|
export const GitHubConnectionOAuthInputCredentialsSchema = z.union([
|
||||||
code: z.string().trim().min(1, "OAuth code required"),
|
z.object({
|
||||||
host: z.string().trim().optional()
|
code: z.string().trim().min(1, "OAuth code required"),
|
||||||
});
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().trim().min(1, "Host is required for server instance type")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
code: z.string().trim().min(1, "OAuth code required"),
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
export const GitHubConnectionAppInputCredentialsSchema = z.object({
|
export const GitHubConnectionAppInputCredentialsSchema = z.union([
|
||||||
code: z.string().trim().min(1, "GitHub App code required"),
|
z.object({
|
||||||
installationId: z.string().min(1, "GitHub App Installation ID required"),
|
code: z.string().trim().min(1, "GitHub App code required"),
|
||||||
host: z.string().trim().optional()
|
installationId: z.string().min(1, "GitHub App Installation ID required"),
|
||||||
});
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().trim().min(1, "Host is required for server instance type")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
code: z.string().trim().min(1, "GitHub App code required"),
|
||||||
|
installationId: z.string().min(1, "GitHub App Installation ID required"),
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
export const GitHubConnectionOAuthOutputCredentialsSchema = z.object({
|
export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([
|
||||||
accessToken: z.string(),
|
z.object({
|
||||||
host: z.string().trim().optional()
|
accessToken: z.string(),
|
||||||
});
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().trim().min(1)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
accessToken: z.string(),
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
export const GitHubConnectionAppOutputCredentialsSchema = z.object({
|
export const GitHubConnectionAppOutputCredentialsSchema = z.union([
|
||||||
installationId: z.string(),
|
z.object({
|
||||||
host: z.string().trim().optional()
|
installationId: z.string(),
|
||||||
});
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().trim().min(1)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
installationId: z.string(),
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
z.object({
|
z.object({
|
||||||
@@ -84,11 +117,17 @@ export const GitHubConnectionSchema = z.intersection(
|
|||||||
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
||||||
BaseGitHubConnectionSchema.extend({
|
BaseGitHubConnectionSchema.extend({
|
||||||
method: z.literal(GitHubConnectionMethod.App),
|
method: z.literal(GitHubConnectionMethod.App),
|
||||||
credentials: GitHubConnectionAppOutputCredentialsSchema.pick({})
|
credentials: z.object({
|
||||||
|
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||||
|
host: z.string().optional()
|
||||||
|
})
|
||||||
}),
|
}),
|
||||||
BaseGitHubConnectionSchema.extend({
|
BaseGitHubConnectionSchema.extend({
|
||||||
method: z.literal(GitHubConnectionMethod.OAuth),
|
method: z.literal(GitHubConnectionMethod.OAuth),
|
||||||
credentials: GitHubConnectionOAuthOutputCredentialsSchema.pick({})
|
credentials: z.object({
|
||||||
|
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||||
|
host: z.string().optional()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
|
import { TUsers } from "@app/db/schemas";
|
||||||
|
import { isAuthMethodSaml } from "@app/ee/services/permission/permission-fns";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { request } from "@app/lib/config/request";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { AuthModeProviderJwtTokenPayload, AuthModeProviderSignUpTokenPayload, AuthTokenType } from "./auth-type";
|
import {
|
||||||
|
AuthMethod,
|
||||||
|
AuthModeProviderJwtTokenPayload,
|
||||||
|
AuthModeProviderSignUpTokenPayload,
|
||||||
|
AuthTokenType
|
||||||
|
} from "./auth-type";
|
||||||
|
|
||||||
export const validateProviderAuthToken = (providerToken: string, username?: string) => {
|
export const validateProviderAuthToken = (providerToken: string, username?: string) => {
|
||||||
if (!providerToken) throw new UnauthorizedError();
|
if (!providerToken) throw new UnauthorizedError();
|
||||||
@@ -97,3 +105,50 @@ export const enforceUserLockStatus = (isLocked: boolean, temporaryLockDateEnd?:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const verifyCaptcha = async (user: TUsers, captchaToken?: string) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
if (
|
||||||
|
user.consecutiveFailedPasswordAttempts &&
|
||||||
|
user.consecutiveFailedPasswordAttempts >= 10 &&
|
||||||
|
Boolean(appCfg.CAPTCHA_SECRET)
|
||||||
|
) {
|
||||||
|
if (!captchaToken) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
name: "Captcha Required",
|
||||||
|
message: "Accomplish the required captcha by logging in via Web"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate captcha token
|
||||||
|
const response = await request.postForm<{ success: boolean }>("https://api.hcaptcha.com/siteverify", {
|
||||||
|
response: captchaToken,
|
||||||
|
secret: appCfg.CAPTCHA_SECRET
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.data.success) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
name: "Invalid Captcha"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAuthMethodAndOrgId = (email: string, providerAuthToken?: string) => {
|
||||||
|
let authMethod = AuthMethod.EMAIL;
|
||||||
|
let organizationId: string | undefined;
|
||||||
|
|
||||||
|
if (providerAuthToken) {
|
||||||
|
const decodedProviderToken = validateProviderAuthToken(providerAuthToken, email);
|
||||||
|
|
||||||
|
authMethod = decodedProviderToken.authMethod;
|
||||||
|
if (
|
||||||
|
(isAuthMethodSaml(authMethod) || [AuthMethod.LDAP, AuthMethod.OIDC].includes(authMethod)) &&
|
||||||
|
decodedProviderToken.orgId
|
||||||
|
) {
|
||||||
|
organizationId = decodedProviderToken.orgId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { authMethod, organizationId };
|
||||||
|
};
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { OrgMembershipRole, OrgMembershipStatus, TableName, TUsers, UserDeviceSc
|
|||||||
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
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 { request } from "@app/lib/config/request";
|
|
||||||
import { crypto, generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
import { crypto, generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
||||||
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
||||||
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
@@ -22,7 +21,8 @@ import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
|||||||
import { LoginMethod } from "../super-admin/super-admin-types";
|
import { LoginMethod } from "../super-admin/super-admin-types";
|
||||||
import { TTotpServiceFactory } from "../totp/totp-service";
|
import { TTotpServiceFactory } from "../totp/totp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { enforceUserLockStatus, validateProviderAuthToken } from "./auth-fns";
|
import { UserEncryption } from "../user/user-types";
|
||||||
|
import { enforceUserLockStatus, getAuthMethodAndOrgId, validateProviderAuthToken, verifyCaptcha } from "./auth-fns";
|
||||||
import {
|
import {
|
||||||
TLoginClientProofDTO,
|
TLoginClientProofDTO,
|
||||||
TLoginGenServerPublicKeyDTO,
|
TLoginGenServerPublicKeyDTO,
|
||||||
@@ -208,6 +208,10 @@ export const authLoginServiceFactory = ({
|
|||||||
throw new Error("Failed to find user");
|
throw new Error("Failed to find user");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!userEnc.salt || !userEnc.verifier) {
|
||||||
|
throw new BadRequestError({ message: "Salt or verifier not found" });
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
serverCfg.enabledLoginMethods &&
|
serverCfg.enabledLoginMethods &&
|
||||||
!serverCfg.enabledLoginMethods.includes(LoginMethod.EMAIL) &&
|
!serverCfg.enabledLoginMethods.includes(LoginMethod.EMAIL) &&
|
||||||
@@ -247,8 +251,6 @@ export const authLoginServiceFactory = ({
|
|||||||
captchaToken,
|
captchaToken,
|
||||||
password
|
password
|
||||||
}: TLoginClientProofDTO) => {
|
}: TLoginClientProofDTO) => {
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
// akhilmhdh: case sensitive email resolution
|
// akhilmhdh: case sensitive email resolution
|
||||||
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
||||||
username: email
|
username: email
|
||||||
@@ -259,44 +261,11 @@ export const authLoginServiceFactory = ({
|
|||||||
const user = await userDAL.findById(userEnc.userId);
|
const user = await userDAL.findById(userEnc.userId);
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
|
|
||||||
let authMethod = AuthMethod.EMAIL;
|
const { authMethod, organizationId } = getAuthMethodAndOrgId(email, providerAuthToken);
|
||||||
let organizationId: string | undefined;
|
await verifyCaptcha(user, captchaToken);
|
||||||
|
|
||||||
if (providerAuthToken) {
|
if (!userEnc.salt || !userEnc.verifier) {
|
||||||
const decodedProviderToken = validateProviderAuthToken(providerAuthToken, email);
|
throw new BadRequestError({ message: "Salt or verifier not found" });
|
||||||
|
|
||||||
authMethod = decodedProviderToken.authMethod;
|
|
||||||
if (
|
|
||||||
(isAuthMethodSaml(authMethod) || [AuthMethod.LDAP, AuthMethod.OIDC].includes(authMethod)) &&
|
|
||||||
decodedProviderToken.orgId
|
|
||||||
) {
|
|
||||||
organizationId = decodedProviderToken.orgId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
user.consecutiveFailedPasswordAttempts &&
|
|
||||||
user.consecutiveFailedPasswordAttempts >= 10 &&
|
|
||||||
Boolean(appCfg.CAPTCHA_SECRET)
|
|
||||||
) {
|
|
||||||
if (!captchaToken) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
name: "Captcha Required",
|
|
||||||
message: "Accomplish the required captcha by logging in via Web"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate captcha token
|
|
||||||
const response = await request.postForm<{ success: boolean }>("https://api.hcaptcha.com/siteverify", {
|
|
||||||
response: captchaToken,
|
|
||||||
secret: appCfg.CAPTCHA_SECRET
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.data.success) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
name: "Invalid Captcha"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?");
|
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?");
|
||||||
@@ -371,6 +340,80 @@ export const authLoginServiceFactory = ({
|
|||||||
return { token, user: userEnc } as const;
|
return { token, user: userEnc } as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const login = async ({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
ip,
|
||||||
|
userAgent,
|
||||||
|
providerAuthToken,
|
||||||
|
captchaToken
|
||||||
|
}: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
providerAuthToken?: string;
|
||||||
|
captchaToken?: string;
|
||||||
|
}) => {
|
||||||
|
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
||||||
|
username: email
|
||||||
|
});
|
||||||
|
const userEnc =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
||||||
|
|
||||||
|
if (!userEnc) throw new BadRequestError({ message: "User not found" });
|
||||||
|
|
||||||
|
if (userEnc.encryptionVersion !== UserEncryption.V2) {
|
||||||
|
throw new BadRequestError({ message: "Legacy encryption scheme not supported", name: "LegacyEncryptionScheme" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userEnc.hashedPassword) {
|
||||||
|
if (userEnc.authMethods?.includes(AuthMethod.EMAIL)) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Legacy encryption scheme not supported",
|
||||||
|
name: "LegacyEncryptionScheme"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "No password found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { authMethod, organizationId } = getAuthMethodAndOrgId(email, providerAuthToken);
|
||||||
|
await verifyCaptcha(userEnc, captchaToken);
|
||||||
|
|
||||||
|
if (!(await crypto.hashing().compareHash(password, userEnc.hashedPassword))) {
|
||||||
|
await userDAL.update(
|
||||||
|
{ id: userEnc.userId },
|
||||||
|
{
|
||||||
|
$incr: {
|
||||||
|
consecutiveFailedPasswordAttempts: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "Invalid username or email" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await generateUserTokens({
|
||||||
|
user: {
|
||||||
|
...userEnc,
|
||||||
|
id: userEnc.userId
|
||||||
|
},
|
||||||
|
ip,
|
||||||
|
userAgent,
|
||||||
|
authMethod,
|
||||||
|
organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokens: {
|
||||||
|
accessToken: token.access,
|
||||||
|
refreshToken: token.refresh
|
||||||
|
},
|
||||||
|
user: userEnc
|
||||||
|
} as const;
|
||||||
|
};
|
||||||
|
|
||||||
const selectOrganization = async ({
|
const selectOrganization = async ({
|
||||||
userAgent,
|
userAgent,
|
||||||
authJwtToken,
|
authJwtToken,
|
||||||
@@ -862,6 +905,7 @@ export const authLoginServiceFactory = ({
|
|||||||
resendMfaToken,
|
resendMfaToken,
|
||||||
verifyMfaToken,
|
verifyMfaToken,
|
||||||
selectOrganization,
|
selectOrganization,
|
||||||
generateUserTokens
|
generateUserTokens,
|
||||||
|
login
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import { SecretEncryptionAlgo, SecretKeyEncoding } from "@app/db/schemas";
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
|
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { generateUserSrpKeys } from "@app/lib/crypto/srp";
|
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
@@ -16,8 +13,6 @@ import { UserEncryption } from "../user/user-types";
|
|||||||
import { TAuthDALFactory } from "./auth-dal";
|
import { TAuthDALFactory } from "./auth-dal";
|
||||||
import {
|
import {
|
||||||
ResetPasswordV2Type,
|
ResetPasswordV2Type,
|
||||||
TChangePasswordDTO,
|
|
||||||
TCreateBackupPrivateKeyDTO,
|
|
||||||
TResetPasswordV2DTO,
|
TResetPasswordV2DTO,
|
||||||
TResetPasswordViaBackupKeyDTO,
|
TResetPasswordViaBackupKeyDTO,
|
||||||
TSetupPasswordViaBackupKeyDTO
|
TSetupPasswordViaBackupKeyDTO
|
||||||
@@ -40,79 +35,6 @@ export const authPaswordServiceFactory = ({
|
|||||||
smtpService,
|
smtpService,
|
||||||
totpConfigDAL
|
totpConfigDAL
|
||||||
}: TAuthPasswordServiceFactoryDep) => {
|
}: TAuthPasswordServiceFactoryDep) => {
|
||||||
/*
|
|
||||||
* Pre setup for pass change with srp protocol
|
|
||||||
* Gets srp server user salt and server public key
|
|
||||||
*/
|
|
||||||
const generateServerPubKey = async (userId: string, clientPublicKey: string) => {
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUserId(userId);
|
|
||||||
if (!userEnc) throw new Error("Failed to find user");
|
|
||||||
|
|
||||||
const serverSrpKey = await generateSrpServerKey(userEnc.salt, userEnc.verifier);
|
|
||||||
const userEncKeys = await userDAL.updateUserEncryptionByUserId(userEnc.userId, {
|
|
||||||
clientPublicKey,
|
|
||||||
serverPrivateKey: serverSrpKey.privateKey
|
|
||||||
});
|
|
||||||
if (!userEncKeys) throw new Error("Failed to update encryption key");
|
|
||||||
return { salt: userEncKeys.salt, serverPublicKey: serverSrpKey.pubKey };
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Change password to new pass
|
|
||||||
* */
|
|
||||||
const changePassword = async ({
|
|
||||||
userId,
|
|
||||||
clientProof,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
tokenVersionId,
|
|
||||||
password
|
|
||||||
}: TChangePasswordDTO) => {
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUserId(userId);
|
|
||||||
if (!userEnc) throw new Error("Failed to find user");
|
|
||||||
|
|
||||||
await userDAL.updateUserEncryptionByUserId(userEnc.userId, {
|
|
||||||
serverPrivateKey: null,
|
|
||||||
clientPublicKey: null
|
|
||||||
});
|
|
||||||
if (!userEnc.serverPrivateKey || !userEnc.clientPublicKey) throw new Error("Failed to authenticate. Try again?");
|
|
||||||
const isValidClientProof = await srpCheckClientProof(
|
|
||||||
userEnc.salt,
|
|
||||||
userEnc.verifier,
|
|
||||||
userEnc.serverPrivateKey,
|
|
||||||
userEnc.clientPublicKey,
|
|
||||||
clientProof
|
|
||||||
);
|
|
||||||
if (!isValidClientProof) throw new Error("Failed to authenticate. Try again?");
|
|
||||||
|
|
||||||
const appCfg = getConfig();
|
|
||||||
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
|
|
||||||
await userDAL.updateUserEncryptionByUserId(userId, {
|
|
||||||
encryptionVersion: 2,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv: encryptedPrivateKeyIV,
|
|
||||||
tag: encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
serverPrivateKey: null,
|
|
||||||
clientPublicKey: null,
|
|
||||||
hashedPassword
|
|
||||||
});
|
|
||||||
|
|
||||||
if (tokenVersionId) {
|
|
||||||
await tokenService.clearTokenSessionById(userEnc.userId, tokenVersionId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Email password reset flow via email. Step 1 send email
|
* Email password reset flow via email. Step 1 send email
|
||||||
*/
|
*/
|
||||||
@@ -193,6 +115,10 @@ export const authPaswordServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!user.authMethods?.includes(AuthMethod.EMAIL)) {
|
if (!user.authMethods?.includes(AuthMethod.EMAIL)) {
|
||||||
|
logger.error(
|
||||||
|
{ authMethods: user.authMethods },
|
||||||
|
"Unable to reset password, no email authentication method is configured"
|
||||||
|
);
|
||||||
throw new BadRequestError({ message: "Unable to reset password, no email authentication method is configured" });
|
throw new BadRequestError({ message: "Unable to reset password, no email authentication method is configured" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,58 +137,17 @@ export const authPaswordServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newHashedPassword = await crypto.hashing().createHash(newPassword, cfg.SALT_ROUNDS);
|
if (user.encryptionVersion !== UserEncryption.V2) {
|
||||||
|
|
||||||
// we need to get the original private key first for v2
|
|
||||||
let privateKey: string;
|
|
||||||
if (
|
|
||||||
user.serverEncryptedPrivateKey &&
|
|
||||||
user.serverEncryptedPrivateKeyTag &&
|
|
||||||
user.serverEncryptedPrivateKeyIV &&
|
|
||||||
user.serverEncryptedPrivateKeyEncoding &&
|
|
||||||
user.encryptionVersion === UserEncryption.V2
|
|
||||||
) {
|
|
||||||
privateKey = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.decryptWithRootEncryptionKey({
|
|
||||||
iv: user.serverEncryptedPrivateKeyIV,
|
|
||||||
tag: user.serverEncryptedPrivateKeyTag,
|
|
||||||
ciphertext: user.serverEncryptedPrivateKey,
|
|
||||||
keyEncoding: user.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Cannot reset password without current credentials or recovery method",
|
message: "Cannot reset password without current credentials or recovery method",
|
||||||
name: "Reset password"
|
name: "Reset password"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const encKeys = await generateUserSrpKeys(user.username, newPassword, {
|
const newHashedPassword = await crypto.hashing().createHash(newPassword, cfg.SALT_ROUNDS);
|
||||||
publicKey: user.publicKey,
|
|
||||||
privateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const { tag, iv, ciphertext, encoding } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(privateKey);
|
|
||||||
|
|
||||||
await userDAL.updateUserEncryptionByUserId(userId, {
|
await userDAL.updateUserEncryptionByUserId(userId, {
|
||||||
hashedPassword: newHashedPassword,
|
hashedPassword: newHashedPassword
|
||||||
|
|
||||||
// srp params
|
|
||||||
salt: encKeys.salt,
|
|
||||||
verifier: encKeys.verifier,
|
|
||||||
|
|
||||||
protectedKey: encKeys.protectedKey,
|
|
||||||
protectedKeyIV: encKeys.protectedKeyIV,
|
|
||||||
protectedKeyTag: encKeys.protectedKeyTag,
|
|
||||||
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
|
||||||
iv: encKeys.encryptedPrivateKeyIV,
|
|
||||||
tag: encKeys.encryptedPrivateKeyTag,
|
|
||||||
|
|
||||||
serverEncryptedPrivateKey: ciphertext,
|
|
||||||
serverEncryptedPrivateKeyIV: iv,
|
|
||||||
serverEncryptedPrivateKeyTag: tag,
|
|
||||||
serverEncryptedPrivateKeyEncoding: encoding
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await tokenService.revokeAllMySessions(userId);
|
await tokenService.revokeAllMySessions(userId);
|
||||||
@@ -313,66 +198,6 @@ export const authPaswordServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
* backup key creation to give user's their access back when lost their password
|
|
||||||
* this also needs to do the generateServerPubKey function to be executed first
|
|
||||||
* then only client proof can be verified
|
|
||||||
* */
|
|
||||||
const createBackupPrivateKey = async ({
|
|
||||||
clientProof,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
userId
|
|
||||||
}: TCreateBackupPrivateKeyDTO) => {
|
|
||||||
const userEnc = await userDAL.findUserEncKeyByUserId(userId);
|
|
||||||
if (!userEnc || (userEnc && !userEnc.isAccepted)) {
|
|
||||||
throw new Error("Failed to find user");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userEnc.clientPublicKey || !userEnc.serverPrivateKey) throw new Error("failed to create backup key");
|
|
||||||
const isValidClientProff = await srpCheckClientProof(
|
|
||||||
userEnc.salt,
|
|
||||||
userEnc.verifier,
|
|
||||||
userEnc.serverPrivateKey,
|
|
||||||
userEnc.clientPublicKey,
|
|
||||||
clientProof
|
|
||||||
);
|
|
||||||
if (!isValidClientProff) throw new Error("failed to create backup key");
|
|
||||||
const backup = await authDAL.transaction(async (tx) => {
|
|
||||||
const backupKey = await authDAL.upsertBackupKey(
|
|
||||||
userEnc.userId,
|
|
||||||
{
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv,
|
|
||||||
tag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
algorithm: SecretEncryptionAlgo.AES_256_GCM,
|
|
||||||
keyEncoding: SecretKeyEncoding.UTF8
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
await userDAL.updateUserEncryptionByUserId(
|
|
||||||
userEnc.userId,
|
|
||||||
{
|
|
||||||
serverPrivateKey: null,
|
|
||||||
clientPublicKey: null
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
return backupKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
return backup;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return user back up
|
|
||||||
* */
|
|
||||||
const getBackupPrivateKeyOfUser = async (userId: string) => {
|
const getBackupPrivateKeyOfUser = async (userId: string) => {
|
||||||
const user = await userDAL.findUserEncKeyByUserId(userId);
|
const user = await userDAL.findUserEncKeyByUserId(userId);
|
||||||
if (!user || (user && !user.isAccepted)) {
|
if (!user || (user && !user.isAccepted)) {
|
||||||
@@ -416,21 +241,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupPassword = async (
|
const setupPassword = async ({ password, token }: TSetupPasswordViaBackupKeyDTO, actor: OrgServiceActor) => {
|
||||||
{
|
|
||||||
encryptedPrivateKey,
|
|
||||||
protectedKeyTag,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
password,
|
|
||||||
token
|
|
||||||
}: TSetupPasswordViaBackupKeyDTO,
|
|
||||||
actor: OrgServiceActor
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
await tokenService.validateTokenForUser({
|
await tokenService.validateTokenForUser({
|
||||||
type: TokenType.TOKEN_EMAIL_PASSWORD_SETUP,
|
type: TokenType.TOKEN_EMAIL_PASSWORD_SETUP,
|
||||||
@@ -466,15 +277,7 @@ export const authPaswordServiceFactory = ({
|
|||||||
await userDAL.updateUserEncryptionByUserId(
|
await userDAL.updateUserEncryptionByUserId(
|
||||||
actor.id,
|
actor.id,
|
||||||
{
|
{
|
||||||
encryptionVersion: 2,
|
encryptionVersion: UserEncryption.V2,
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv: encryptedPrivateKeyIV,
|
|
||||||
tag: encryptedPrivateKeyTag,
|
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
hashedPassword,
|
hashedPassword,
|
||||||
serverPrivateKey: null,
|
serverPrivateKey: null,
|
||||||
clientPublicKey: null
|
clientPublicKey: null
|
||||||
@@ -487,12 +290,9 @@ export const authPaswordServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generateServerPubKey,
|
|
||||||
changePassword,
|
|
||||||
resetPasswordByBackupKey,
|
resetPasswordByBackupKey,
|
||||||
sendPasswordResetEmail,
|
sendPasswordResetEmail,
|
||||||
verifyPasswordResetEmail,
|
verifyPasswordResetEmail,
|
||||||
createBackupPrivateKey,
|
|
||||||
getBackupPrivateKeyOfUser,
|
getBackupPrivateKeyOfUser,
|
||||||
sendPasswordSetupEmail,
|
sendPasswordSetupEmail,
|
||||||
setupPassword,
|
setupPassword,
|
||||||
|
|||||||
@@ -1,18 +1,3 @@
|
|||||||
export type TChangePasswordDTO = {
|
|
||||||
userId: string;
|
|
||||||
clientProof: string;
|
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
tokenVersionId?: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum ResetPasswordV2Type {
|
export enum ResetPasswordV2Type {
|
||||||
Recovery = "recovery",
|
Recovery = "recovery",
|
||||||
LoggedInReset = "logged-in-reset"
|
LoggedInReset = "logged-in-reset"
|
||||||
@@ -39,14 +24,6 @@ export type TResetPasswordViaBackupKeyDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TSetupPasswordViaBackupKeyDTO = {
|
export type TSetupPasswordViaBackupKeyDTO = {
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
password: string;
|
password: string;
|
||||||
token: string;
|
token: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { OrgMembershipStatus, SecretKeyEncoding, TableName } from "@app/db/schemas";
|
import { OrgMembershipStatus, 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 { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { generateUserSrpKeys, getUserPrivateKey } from "@app/lib/crypto/srp";
|
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { getMinExpiresIn } from "@app/lib/fn";
|
import { getMinExpiresIn } from "@app/lib/fn";
|
||||||
import { isDisposableEmail } from "@app/lib/validator";
|
import { isDisposableEmail } from "@app/lib/validator";
|
||||||
@@ -41,7 +40,7 @@ type TAuthSignupDep = {
|
|||||||
| "findUserGroupMembershipsInProject"
|
| "findUserGroupMembershipsInProject"
|
||||||
>;
|
>;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "findLatestProjectKey" | "insertMany">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findProjectById">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectGhostUser" | "findProjectById" | "findById">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
groupProjectDAL: Pick<TGroupProjectDALFactory, "find">;
|
||||||
orgService: Pick<TOrgServiceFactory, "createOrganization" | "findOrganizationById">;
|
orgService: Pick<TOrgServiceFactory, "createOrganization" | "findOrganizationById">;
|
||||||
@@ -147,17 +146,8 @@ export const authSignupServiceFactory = ({
|
|||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
salt,
|
|
||||||
verifier,
|
|
||||||
publicKey,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
organizationName,
|
organizationName,
|
||||||
// attributionSource,
|
// attributionSource,
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
ip,
|
ip,
|
||||||
userAgent,
|
userAgent,
|
||||||
authorization,
|
authorization,
|
||||||
@@ -191,98 +181,18 @@ export const authSignupServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
|
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
|
||||||
const privateKey = await getUserPrivateKey(password, {
|
|
||||||
salt,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv: encryptedPrivateKeyIV,
|
|
||||||
tag: encryptedPrivateKeyTag,
|
|
||||||
encryptionVersion: UserEncryption.V2
|
|
||||||
});
|
|
||||||
const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(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 systemGeneratedUserEncryptionKey = await userDAL.findUserEncKeyByUserId(us.id, tx);
|
|
||||||
let userEncKey;
|
|
||||||
|
|
||||||
// below condition is true means this is system generated credentials
|
const userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||||
// the private key is actually system generated password
|
us.id,
|
||||||
// thus we will re-encrypt the system generated private key with the new password
|
{
|
||||||
// akhilmhdh: you may find this like why? The reason is simple we are moving away from e2ee and these are pieces of it
|
encryptionVersion: UserEncryption.V2,
|
||||||
// without a dummy key in place some things will break and backward compatiability too. 2025 we will be removing all these things
|
hashedPassword
|
||||||
if (
|
},
|
||||||
systemGeneratedUserEncryptionKey &&
|
tx
|
||||||
!systemGeneratedUserEncryptionKey.hashedPassword &&
|
);
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey &&
|
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag &&
|
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV &&
|
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding
|
|
||||||
) {
|
|
||||||
// get server generated password
|
|
||||||
const serverGeneratedPassword = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.decryptWithRootEncryptionKey({
|
|
||||||
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 (
|
||||||
@@ -400,19 +310,10 @@ export const authSignupServiceFactory = ({
|
|||||||
const completeAccountInvite = async ({
|
const completeAccountInvite = async ({
|
||||||
email,
|
email,
|
||||||
ip,
|
ip,
|
||||||
salt,
|
|
||||||
password,
|
password,
|
||||||
verifier,
|
|
||||||
firstName,
|
firstName,
|
||||||
publicKey,
|
|
||||||
userAgent,
|
userAgent,
|
||||||
lastName,
|
lastName,
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
encryptedPrivateKeyIV,
|
|
||||||
encryptedPrivateKeyTag,
|
|
||||||
authorization
|
authorization
|
||||||
}: TCompleteAccountInviteDTO) => {
|
}: TCompleteAccountInviteDTO) => {
|
||||||
const sanitizedEmail = email.trim().toLowerCase();
|
const sanitizedEmail = email.trim().toLowerCase();
|
||||||
@@ -437,94 +338,17 @@ export const authSignupServiceFactory = ({
|
|||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
|
const hashedPassword = await crypto.hashing().createHash(password, appCfg.SALT_ROUNDS);
|
||||||
const privateKey = await getUserPrivateKey(password, {
|
|
||||||
salt,
|
|
||||||
protectedKey,
|
|
||||||
protectedKeyIV,
|
|
||||||
protectedKeyTag,
|
|
||||||
encryptedPrivateKey,
|
|
||||||
iv: encryptedPrivateKeyIV,
|
|
||||||
tag: encryptedPrivateKeyTag,
|
|
||||||
encryptionVersion: 2
|
|
||||||
});
|
|
||||||
const { tag, encoding, ciphertext, iv } = crypto.encryption().symmetric().encryptWithRootEncryptionKey(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 systemGeneratedUserEncryptionKey = await userDAL.findUserEncKeyByUserId(us.id, tx);
|
const userEncKey = await userDAL.upsertUserEncryptionKey(
|
||||||
let userEncKey;
|
us.id,
|
||||||
// this means this is system generated credentials
|
{
|
||||||
// now replace the private key
|
encryptionVersion: 2,
|
||||||
if (
|
hashedPassword
|
||||||
systemGeneratedUserEncryptionKey &&
|
},
|
||||||
!systemGeneratedUserEncryptionKey.hashedPassword &&
|
tx
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey &&
|
);
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag &&
|
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV &&
|
|
||||||
systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding
|
|
||||||
) {
|
|
||||||
// get server generated password
|
|
||||||
const serverGeneratedPassword = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.decryptWithRootEncryptionKey({
|
|
||||||
iv: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyIV,
|
|
||||||
tag: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyTag,
|
|
||||||
ciphertext: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKey,
|
|
||||||
keyEncoding: systemGeneratedUserEncryptionKey.serverEncryptedPrivateKeyEncoding as SecretKeyEncoding
|
|
||||||
});
|
|
||||||
const serverGeneratedPrivateKey = await getUserPrivateKey(serverGeneratedPassword, {
|
|
||||||
...systemGeneratedUserEncryptionKey
|
|
||||||
});
|
|
||||||
const encKeys = await generateUserSrpKeys(sanitizedEmail, password, {
|
|
||||||
publicKey: systemGeneratedUserEncryptionKey.publicKey,
|
|
||||||
privateKey: serverGeneratedPrivateKey
|
|
||||||
});
|
|
||||||
// now reencrypt server generated key with user provided password
|
|
||||||
userEncKey = await userDAL.upsertUserEncryptionKey(
|
|
||||||
us.id,
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedMembersips = await orgDAL.updateMembership(
|
const updatedMembersips = await orgDAL.updateMembership(
|
||||||
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
|
{ inviteEmail: sanitizedEmail, status: OrgMembershipStatus.Invited },
|
||||||
|
|||||||
@@ -3,15 +3,6 @@ export type TCompleteAccountSignupDTO = {
|
|||||||
password: string;
|
password: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
publicKey: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
organizationName?: string;
|
organizationName?: string;
|
||||||
providerAuthToken?: string | null;
|
providerAuthToken?: string | null;
|
||||||
attributionSource?: string | undefined;
|
attributionSource?: string | undefined;
|
||||||
@@ -26,15 +17,6 @@ export type TCompleteAccountInviteDTO = {
|
|||||||
password: string;
|
password: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
protectedKey: string;
|
|
||||||
protectedKeyIV: string;
|
|
||||||
protectedKeyTag: string;
|
|
||||||
publicKey: string;
|
|
||||||
encryptedPrivateKey: string;
|
|
||||||
encryptedPrivateKeyIV: string;
|
|
||||||
encryptedPrivateKeyTag: string;
|
|
||||||
salt: string;
|
|
||||||
verifier: string;
|
|
||||||
ip: string;
|
ip: string;
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
authorization: string;
|
authorization: string;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { isValidIp } from "@app/lib/ip";
|
import { isValidIp } from "@app/lib/ip";
|
||||||
import { isFQDN } from "@app/lib/validator/validate-url";
|
import { isFQDN } from "@app/lib/validator/validate-url";
|
||||||
|
import { TAltNameMapping, TAltNameType } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
const isValidDate = (dateString: string) => {
|
const isValidDate = (dateString: string) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
@@ -15,10 +16,15 @@ export const validateAltNameField = z
|
|||||||
.trim()
|
.trim()
|
||||||
.refine(
|
.refine(
|
||||||
(name) => {
|
(name) => {
|
||||||
return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name);
|
return (
|
||||||
|
isFQDN(name, { allow_wildcard: true, require_tld: false }) ||
|
||||||
|
z.string().url().safeParse(name).success ||
|
||||||
|
z.string().email().safeParse(name).success ||
|
||||||
|
isValidIp(name)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: "SAN must be a valid hostname, email address, or IP address"
|
message: "SAN must be a valid hostname, email address, IP address or URL"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -39,10 +45,31 @@ export const validateAltNamesField = z
|
|||||||
if (data === "") return true;
|
if (data === "") return true;
|
||||||
// Split and validate each alt name
|
// Split and validate each alt name
|
||||||
return data.split(", ").every((name) => {
|
return data.split(", ").every((name) => {
|
||||||
return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name);
|
return (
|
||||||
|
isFQDN(name, { allow_wildcard: true, require_tld: false }) ||
|
||||||
|
z.string().url().safeParse(name).success ||
|
||||||
|
z.string().email().safeParse(name).success ||
|
||||||
|
isValidIp(name)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: "Each alt name must be a valid hostname or email address"
|
message: "Each alt name must be a valid hostname, email address, IP address or URL"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const validateAndMapAltNameType = (name: string): TAltNameMapping | null => {
|
||||||
|
if (isFQDN(name, { allow_wildcard: true, require_tld: false })) {
|
||||||
|
return { type: TAltNameType.DNS, value: name };
|
||||||
|
}
|
||||||
|
if (z.string().url().safeParse(name).success) {
|
||||||
|
return { type: TAltNameType.URL, value: name };
|
||||||
|
}
|
||||||
|
if (z.string().email().safeParse(name).success) {
|
||||||
|
return { type: TAltNameType.EMAIL, value: name };
|
||||||
|
}
|
||||||
|
if (isValidIp(name)) {
|
||||||
|
return { type: TAltNameType.IP, value: name };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|||||||
+13
-21
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TCertificateTemplates, TPkiSubscribers } from "@app/db/schemas";
|
import { TCertificateTemplates, TPkiSubscribers } from "@app/db/schemas";
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
@@ -9,7 +8,6 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { isFQDN } from "@app/lib/validator/validate-url";
|
|
||||||
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
||||||
@@ -17,7 +15,8 @@ import {
|
|||||||
CertExtendedKeyUsage,
|
CertExtendedKeyUsage,
|
||||||
CertKeyAlgorithm,
|
CertKeyAlgorithm,
|
||||||
CertKeyUsage,
|
CertKeyUsage,
|
||||||
CertStatus
|
CertStatus,
|
||||||
|
TAltNameMapping
|
||||||
} from "@app/services/certificate/certificate-types";
|
} from "@app/services/certificate/certificate-types";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -34,6 +33,7 @@ import {
|
|||||||
} from "../certificate-authority-fns";
|
} from "../certificate-authority-fns";
|
||||||
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
||||||
import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types";
|
import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types";
|
||||||
|
import { validateAndMapAltNameType } from "../certificate-authority-validators";
|
||||||
|
|
||||||
type TInternalCertificateAuthorityFnsDeps = {
|
type TInternalCertificateAuthorityFnsDeps = {
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">;
|
||||||
@@ -152,19 +152,15 @@ export const InternalCertificateAuthorityFns = ({
|
|||||||
extensions.push(extendedKeyUsagesExtension);
|
extensions.push(extendedKeyUsagesExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
|
|
||||||
if (subscriber.subjectAlternativeNames?.length) {
|
if (subscriber.subjectAlternativeNames?.length) {
|
||||||
altNamesArray = subscriber.subjectAlternativeNames.map((altName) => {
|
altNamesArray = subscriber.subjectAlternativeNames.map((altName) => {
|
||||||
if (z.string().email().safeParse(altName).success) {
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
return { type: "email", value: altName };
|
if (!altNameType) {
|
||||||
|
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
|
||||||
return { type: "dns", value: altName };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
@@ -418,19 +414,15 @@ export const InternalCertificateAuthorityFns = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let altNamesArray: { type: "email" | "dns"; value: string }[] = [];
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
|
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
altNamesArray = altNames.split(",").map((altName) => {
|
altNamesArray = altNames.split(",").map((altName) => {
|
||||||
if (z.string().email().safeParse(altName).success) {
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
return { type: "email", value: altName };
|
if (!altNameType) {
|
||||||
|
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
|
||||||
return { type: "dns", value: altName };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
|||||||
+29
-52
@@ -2,7 +2,6 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
|
import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
@@ -18,7 +17,6 @@ import { crypto } from "@app/lib/crypto/cryptography";
|
|||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { isFQDN } from "@app/lib/validator/validate-url";
|
|
||||||
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
@@ -34,7 +32,8 @@ import {
|
|||||||
CertExtendedKeyUsageOIDToName,
|
CertExtendedKeyUsageOIDToName,
|
||||||
CertKeyAlgorithm,
|
CertKeyAlgorithm,
|
||||||
CertKeyUsage,
|
CertKeyUsage,
|
||||||
CertStatus
|
CertStatus,
|
||||||
|
TAltNameMapping
|
||||||
} from "../../certificate/certificate-types";
|
} 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";
|
||||||
@@ -69,6 +68,7 @@ import {
|
|||||||
TSignIntermediateDTO,
|
TSignIntermediateDTO,
|
||||||
TUpdateCaDTO
|
TUpdateCaDTO
|
||||||
} from "./internal-certificate-authority-types";
|
} from "./internal-certificate-authority-types";
|
||||||
|
import { validateAndMapAltNameType } from "../certificate-authority-validators";
|
||||||
|
|
||||||
type TInternalCertificateAuthorityServiceFactoryDep = {
|
type TInternalCertificateAuthorityServiceFactoryDep = {
|
||||||
certificateAuthorityDAL: Pick<
|
certificateAuthorityDAL: Pick<
|
||||||
@@ -1364,34 +1364,18 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let altNamesArray: {
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
type: "email" | "dns";
|
|
||||||
value: string;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
altNamesArray = altNames
|
altNamesArray = altNames
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((name) => name.trim())
|
.map((name) => name.trim())
|
||||||
.map((altName) => {
|
.map((altName): TAltNameMapping => {
|
||||||
// check if the altName is a valid email
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
if (z.string().email().safeParse(altName).success) {
|
if (!altNameType) {
|
||||||
return {
|
throw new Error(`Invalid altName: ${altName}`);
|
||||||
type: "email",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
// check if the altName is a valid hostname
|
|
||||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
|
||||||
return {
|
|
||||||
type: "dns",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
|
|
||||||
throw new Error(`Invalid altName: ${altName}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
@@ -1766,34 +1750,22 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let altNamesFromCsr: string = "";
|
let altNamesFromCsr: string = "";
|
||||||
let altNamesArray: {
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
type: "email" | "dns";
|
|
||||||
value: string;
|
|
||||||
}[] = [];
|
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
altNamesArray = altNames
|
altNamesArray = altNames
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((name) => name.trim())
|
.map((name) => name.trim())
|
||||||
.map((altName) => {
|
.map((altName): TAltNameMapping => {
|
||||||
// check if the altName is a valid email
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
if (z.string().email().safeParse(altName).success) {
|
if (!altNameType) {
|
||||||
return {
|
throw new Error(`Invalid altName: ${altName}`);
|
||||||
type: "email",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
// check if the altName is a valid hostname
|
|
||||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
|
||||||
return {
|
|
||||||
type: "dns",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
|
|
||||||
throw new Error(`Invalid altName: ${altName}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
} else {
|
} else {
|
||||||
// attempt to read from CSR if altNames is not explicitly provided
|
// attempt to read from CSR if altNames is not explicitly provided
|
||||||
const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17");
|
const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17");
|
||||||
@@ -1801,11 +1773,16 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
const sanNames = new x509.GeneralNames(sanExtension.value);
|
const sanNames = new x509.GeneralNames(sanExtension.value);
|
||||||
|
|
||||||
altNamesArray = sanNames.items
|
altNamesArray = sanNames.items
|
||||||
.filter((value) => value.type === "email" || value.type === "dns")
|
.filter(
|
||||||
.map((name) => ({
|
(value) => value.type === "email" || value.type === "dns" || value.type === "url" || value.type === "ip"
|
||||||
type: name.type as "email" | "dns",
|
)
|
||||||
value: name.value
|
.map((name): TAltNameMapping => {
|
||||||
}));
|
const altNameType = validateAndMapAltNameType(name.value);
|
||||||
|
if (!altNameType) {
|
||||||
|
throw new Error(`Invalid altName from CSR: ${name.value}`);
|
||||||
|
}
|
||||||
|
return altNameType;
|
||||||
|
});
|
||||||
|
|
||||||
altNamesFromCsr = sanNames.items.map((item) => item.value).join(",");
|
altNamesFromCsr = sanNames.items.map((item) => item.value).join(",");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,3 +104,14 @@ export type TGetCertificateCredentialsDTO = {
|
|||||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum TAltNameType {
|
||||||
|
EMAIL = "email",
|
||||||
|
DNS = "dns",
|
||||||
|
IP = "ip",
|
||||||
|
URL = "url"
|
||||||
|
}
|
||||||
|
export type TAltNameMapping = {
|
||||||
|
type: TAltNameType;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ActionProjectType, ProjectMembershipRole, SecretKeyEncoding, TGroups } from "@app/db/schemas";
|
import { ActionProjectType, ProjectMembershipRole, ProjectVersion, SecretKeyEncoding, TGroups } from "@app/db/schemas";
|
||||||
import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types";
|
import { TListProjectGroupUsersDTO } from "@app/ee/services/group/group-types";
|
||||||
import {
|
import {
|
||||||
constructPermissionErrorMessage,
|
constructPermissionErrorMessage,
|
||||||
@@ -188,7 +188,7 @@ export const groupProjectServiceFactory = ({
|
|||||||
// other groups that are in the project
|
// other groups that are in the project
|
||||||
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group!.id, project.id, tx);
|
const groupMembers = await userGroupMembershipDAL.findGroupMembersNotInProject(group!.id, project.id, tx);
|
||||||
|
|
||||||
if (groupMembers.length) {
|
if (groupMembers.length && (project.version === ProjectVersion.V1 || project.version === ProjectVersion.V2)) {
|
||||||
const ghostUser = await projectDAL.findProjectGhostUser(project.id, tx);
|
const ghostUser = await projectDAL.findProjectGhostUser(project.id, tx);
|
||||||
|
|
||||||
if (!ghostUser) {
|
if (!ghostUser) {
|
||||||
@@ -205,6 +205,12 @@ export const groupProjectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!ghostUserLatestKey.sender.publicKey) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find project owner's latest key in project with name ${project.name}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const bot = await projectBotDAL.findOne({ projectId: project.id }, tx);
|
const bot = await projectBotDAL.findOne({ projectId: project.id }, tx);
|
||||||
|
|
||||||
if (!bot) {
|
if (!bot) {
|
||||||
@@ -231,6 +237,12 @@ export const groupProjectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const projectKeyData = groupMembers.map(({ user: { publicKey, id } }) => {
|
const projectKeyData = groupMembers.map(({ user: { publicKey, id } }) => {
|
||||||
|
if (!publicKey) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Failed to find user's public key in project with name ${project.name}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { ciphertext: encryptedKey, nonce } = crypto
|
const { ciphertext: encryptedKey, nonce } = crypto
|
||||||
.encryption()
|
.encryption()
|
||||||
.asymmetric()
|
.asymmetric()
|
||||||
|
|||||||
@@ -2,9 +2,14 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { IdentityAuthMethod } from "@app/db/schemas";
|
import { IdentityAuthMethod } from "@app/db/schemas";
|
||||||
|
import { TIdentityAuthTemplateDALFactory } from "@app/ee/services/identity-auth-template";
|
||||||
import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
|
import { testLDAPConfig } from "@app/ee/services/ldap-config/ldap-fns";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { OrgPermissionIdentityActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import {
|
||||||
|
OrgPermissionIdentityActions,
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions,
|
||||||
|
OrgPermissionSubjects
|
||||||
|
} from "@app/ee/services/permission/org-permission";
|
||||||
import {
|
import {
|
||||||
constructPermissionErrorMessage,
|
constructPermissionErrorMessage,
|
||||||
validatePrivilegeChangeOperation
|
validatePrivilegeChangeOperation
|
||||||
@@ -44,6 +49,7 @@ type TIdentityLdapAuthServiceFactoryDep = {
|
|||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
kmsService: TKmsServiceFactory;
|
kmsService: TKmsServiceFactory;
|
||||||
identityDAL: TIdentityDALFactory;
|
identityDAL: TIdentityDALFactory;
|
||||||
|
identityAuthTemplateDAL: TIdentityAuthTemplateDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
|
export type TIdentityLdapAuthServiceFactory = ReturnType<typeof identityLdapAuthServiceFactory>;
|
||||||
@@ -55,7 +61,8 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
permissionService,
|
permissionService,
|
||||||
kmsService
|
kmsService,
|
||||||
|
identityAuthTemplateDAL
|
||||||
}: TIdentityLdapAuthServiceFactoryDep) => {
|
}: TIdentityLdapAuthServiceFactoryDep) => {
|
||||||
const getLdapConfig = async (identityId: string) => {
|
const getLdapConfig = async (identityId: string) => {
|
||||||
const identity = await identityDAL.findOne({ id: identityId });
|
const identity = await identityDAL.findOne({ id: identityId });
|
||||||
@@ -173,6 +180,7 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
|
|
||||||
const attachLdapAuth = async ({
|
const attachLdapAuth = async ({
|
||||||
identityId,
|
identityId,
|
||||||
|
templateId,
|
||||||
url,
|
url,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
@@ -213,6 +221,14 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Create, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
if (templateId) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
|
||||||
if (!plan.ldap) {
|
if (!plan.ldap) {
|
||||||
@@ -241,33 +257,55 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
|
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
|
||||||
|
|
||||||
const identityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
|
const identityLdapAuth = await identityLdapAuthDAL.transaction(async (tx) => {
|
||||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
orgId: identityMembershipOrg.orgId
|
orgId: identityMembershipOrg.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const template = templateId
|
||||||
|
? await identityAuthTemplateDAL.findByIdAndOrgId(templateId, identityMembershipOrg.orgId)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
let ldapConfig: { bindDN: string; bindPass: string; searchBase: string; url: string; ldapCaCertificate?: string };
|
||||||
|
if (template) {
|
||||||
|
ldapConfig = JSON.parse(decryptor({ cipherTextBlob: template.templateFields }).toString());
|
||||||
|
} else {
|
||||||
|
if (!bindDN || !bindPass || !searchBase || !url) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Invalid request. Missing bind DN, bind pass, search base, or URL."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ldapConfig = {
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
url,
|
||||||
|
ldapCaCertificate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedBindPass } = encryptor({
|
const { cipherTextBlob: encryptedBindPass } = encryptor({
|
||||||
plainText: Buffer.from(bindPass)
|
plainText: Buffer.from(ldapConfig.bindPass)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedBindDN } = encryptor({
|
||||||
|
plainText: Buffer.from(ldapConfig.bindDN)
|
||||||
});
|
});
|
||||||
|
|
||||||
let encryptedLdapCaCertificate: Buffer | undefined;
|
let encryptedLdapCaCertificate: Buffer | undefined;
|
||||||
if (ldapCaCertificate) {
|
if (ldapConfig.ldapCaCertificate) {
|
||||||
const { cipherTextBlob: encryptedCertificate } = encryptor({
|
const { cipherTextBlob: encryptedCertificate } = encryptor({
|
||||||
plainText: Buffer.from(ldapCaCertificate)
|
plainText: Buffer.from(ldapConfig.ldapCaCertificate)
|
||||||
});
|
});
|
||||||
|
|
||||||
encryptedLdapCaCertificate = encryptedCertificate;
|
encryptedLdapCaCertificate = encryptedCertificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { cipherTextBlob: encryptedBindDN } = encryptor({
|
|
||||||
plainText: Buffer.from(bindDN)
|
|
||||||
});
|
|
||||||
|
|
||||||
const isConnected = await testLDAPConfig({
|
const isConnected = await testLDAPConfig({
|
||||||
bindDN,
|
bindDN: ldapConfig.bindDN,
|
||||||
bindPass,
|
bindPass: ldapConfig.bindPass,
|
||||||
caCert: ldapCaCertificate || "",
|
caCert: ldapConfig.ldapCaCertificate || "",
|
||||||
url
|
url: ldapConfig.url
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
@@ -282,15 +320,16 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
identityId: identityMembershipOrg.identityId,
|
identityId: identityMembershipOrg.identityId,
|
||||||
encryptedBindDN,
|
encryptedBindDN,
|
||||||
encryptedBindPass,
|
encryptedBindPass,
|
||||||
searchBase,
|
searchBase: ldapConfig.searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
url,
|
url: ldapConfig.url,
|
||||||
encryptedLdapCaCertificate,
|
encryptedLdapCaCertificate,
|
||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
accessTokenTrustedIps: JSON.stringify(reformattedAccessTokenTrustedIps),
|
||||||
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined
|
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined,
|
||||||
|
templateId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -301,6 +340,7 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
|
|
||||||
const updateLdapAuth = async ({
|
const updateLdapAuth = async ({
|
||||||
identityId,
|
identityId,
|
||||||
|
templateId,
|
||||||
url,
|
url,
|
||||||
searchBase,
|
searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
@@ -344,6 +384,13 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionIdentityActions.Edit, OrgPermissionSubjects.Identity);
|
||||||
|
|
||||||
|
if (templateId) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
OrgPermissionMachineIdentityAuthTemplateActions.AttachTemplates,
|
||||||
|
OrgPermissionSubjects.MachineIdentityAuthTemplate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
const plan = await licenseService.getPlan(identityMembershipOrg.orgId);
|
||||||
|
|
||||||
if (!plan.ldap) {
|
if (!plan.ldap) {
|
||||||
@@ -371,33 +418,56 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
|
|
||||||
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
|
if (allowedFields) AllowedFieldsSchema.array().parse(allowedFields);
|
||||||
|
|
||||||
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
type: KmsDataKey.Organization,
|
type: KmsDataKey.Organization,
|
||||||
orgId: identityMembershipOrg.orgId
|
orgId: identityMembershipOrg.orgId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const template = templateId
|
||||||
|
? await identityAuthTemplateDAL.findByIdAndOrgId(templateId, identityMembershipOrg.orgId)
|
||||||
|
: undefined;
|
||||||
|
let config: {
|
||||||
|
bindDN?: string;
|
||||||
|
bindPass?: string;
|
||||||
|
searchBase?: string;
|
||||||
|
url?: string;
|
||||||
|
ldapCaCertificate?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (template) {
|
||||||
|
config = JSON.parse(decryptor({ cipherTextBlob: template.templateFields }).toString());
|
||||||
|
} else {
|
||||||
|
config = {
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
url,
|
||||||
|
ldapCaCertificate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let encryptedBindPass: Buffer | undefined;
|
let encryptedBindPass: Buffer | undefined;
|
||||||
if (bindPass) {
|
if (config.bindPass) {
|
||||||
const { cipherTextBlob: bindPassCiphertext } = encryptor({
|
const { cipherTextBlob: bindPassCiphertext } = encryptor({
|
||||||
plainText: Buffer.from(bindPass)
|
plainText: Buffer.from(config.bindPass)
|
||||||
});
|
});
|
||||||
|
|
||||||
encryptedBindPass = bindPassCiphertext;
|
encryptedBindPass = bindPassCiphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
let encryptedLdapCaCertificate: Buffer | undefined;
|
let encryptedLdapCaCertificate: Buffer | undefined;
|
||||||
if (ldapCaCertificate) {
|
if (config.ldapCaCertificate) {
|
||||||
const { cipherTextBlob: ldapCaCertificateCiphertext } = encryptor({
|
const { cipherTextBlob: ldapCaCertificateCiphertext } = encryptor({
|
||||||
plainText: Buffer.from(ldapCaCertificate)
|
plainText: Buffer.from(config.ldapCaCertificate)
|
||||||
});
|
});
|
||||||
|
|
||||||
encryptedLdapCaCertificate = ldapCaCertificateCiphertext;
|
encryptedLdapCaCertificate = ldapCaCertificateCiphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
let encryptedBindDN: Buffer | undefined;
|
let encryptedBindDN: Buffer | undefined;
|
||||||
if (bindDN) {
|
if (config.bindDN) {
|
||||||
const { cipherTextBlob: bindDNCiphertext } = encryptor({
|
const { cipherTextBlob: bindDNCiphertext } = encryptor({
|
||||||
plainText: Buffer.from(bindDN)
|
plainText: Buffer.from(config.bindDN)
|
||||||
});
|
});
|
||||||
|
|
||||||
encryptedBindDN = bindDNCiphertext;
|
encryptedBindDN = bindDNCiphertext;
|
||||||
@@ -406,10 +476,10 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
const { ldapConfig } = await getLdapConfig(identityId);
|
const { ldapConfig } = await getLdapConfig(identityId);
|
||||||
|
|
||||||
const isConnected = await testLDAPConfig({
|
const isConnected = await testLDAPConfig({
|
||||||
bindDN: bindDN || ldapConfig.bindDN,
|
bindDN: config.bindDN || ldapConfig.bindDN,
|
||||||
bindPass: bindPass || ldapConfig.bindPass,
|
bindPass: config.bindPass || ldapConfig.bindPass,
|
||||||
caCert: ldapCaCertificate || ldapConfig.caCert,
|
caCert: config.ldapCaCertificate || ldapConfig.caCert,
|
||||||
url: url || ldapConfig.url
|
url: config.url || ldapConfig.url
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
@@ -420,14 +490,15 @@ export const identityLdapAuthServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updatedLdapAuth = await identityLdapAuthDAL.updateById(identityLdapAuth.id, {
|
const updatedLdapAuth = await identityLdapAuthDAL.updateById(identityLdapAuth.id, {
|
||||||
url,
|
url: config.url,
|
||||||
searchBase,
|
searchBase: config.searchBase,
|
||||||
searchFilter,
|
searchFilter,
|
||||||
encryptedBindDN,
|
encryptedBindDN,
|
||||||
encryptedBindPass,
|
encryptedBindPass,
|
||||||
encryptedLdapCaCertificate,
|
encryptedLdapCaCertificate,
|
||||||
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined,
|
allowedFields: allowedFields ? JSON.stringify(allowedFields) : undefined,
|
||||||
accessTokenMaxTTL,
|
accessTokenMaxTTL,
|
||||||
|
templateId: template?.id || null,
|
||||||
accessTokenTTL,
|
accessTokenTTL,
|
||||||
accessTokenNumUsesLimit,
|
accessTokenNumUsesLimit,
|
||||||
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
accessTokenTrustedIps: reformattedAccessTokenTrustedIps
|
||||||
|
|||||||
@@ -14,11 +14,12 @@ export type TAllowedFields = z.infer<typeof AllowedFieldsSchema>;
|
|||||||
|
|
||||||
export type TAttachLdapAuthDTO = {
|
export type TAttachLdapAuthDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
url: string;
|
templateId?: string;
|
||||||
searchBase: string;
|
url?: string;
|
||||||
|
searchBase?: string;
|
||||||
searchFilter: string;
|
searchFilter: string;
|
||||||
bindDN: string;
|
bindDN?: string;
|
||||||
bindPass: string;
|
bindPass?: string;
|
||||||
ldapCaCertificate?: string;
|
ldapCaCertificate?: string;
|
||||||
allowedFields?: TAllowedFields[];
|
allowedFields?: TAllowedFields[];
|
||||||
accessTokenTTL: number;
|
accessTokenTTL: number;
|
||||||
@@ -30,6 +31,7 @@ export type TAttachLdapAuthDTO = {
|
|||||||
|
|
||||||
export type TUpdateLdapAuthDTO = {
|
export type TUpdateLdapAuthDTO = {
|
||||||
identityId: string;
|
identityId: string;
|
||||||
|
templateId?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
searchBase?: string;
|
searchBase?: string;
|
||||||
searchFilter?: string;
|
searchFilter?: string;
|
||||||
|
|||||||
@@ -1,19 +1,16 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectVersion, SecretKeyEncoding } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectVersion } from "@app/db/schemas";
|
||||||
import { OrgPermissionAdminConsoleAction, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
import { OrgPermissionAdminConsoleAction, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
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 { 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 { TAccessProjectDTO, TListOrgProjectsDTO } from "./org-admin-types";
|
import { TAccessProjectDTO, TListOrgProjectsDTO } from "./org-admin-types";
|
||||||
|
|
||||||
type TOrgAdminServiceFactoryDep = {
|
type TOrgAdminServiceFactoryDep = {
|
||||||
@@ -25,7 +22,6 @@ type TOrgAdminServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "create">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "create">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
projectBotDAL: Pick<TProjectBotDALFactory, "findOne">;
|
||||||
userDAL: Pick<TUserDALFactory, "findUserEncKeyByUserId">;
|
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create" | "delete">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create" | "delete">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
};
|
};
|
||||||
@@ -38,7 +34,6 @@ export const orgAdminServiceFactory = ({
|
|||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectBotDAL,
|
projectBotDAL,
|
||||||
userDAL,
|
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
smtpService
|
smtpService
|
||||||
}: TOrgAdminServiceFactoryDep) => {
|
}: TOrgAdminServiceFactoryDep) => {
|
||||||
@@ -83,7 +78,7 @@ export const orgAdminServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
projectId
|
projectId
|
||||||
}: TAccessProjectDTO) => {
|
}: TAccessProjectDTO) => {
|
||||||
const { permission, membership } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
@@ -98,8 +93,10 @@ export const orgAdminServiceFactory = ({
|
|||||||
const project = await projectDAL.findOne({ id: projectId, orgId: actorOrgId });
|
const project = await projectDAL.findOne({ id: projectId, orgId: actorOrgId });
|
||||||
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
|
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
|
||||||
|
|
||||||
if (project.version === ProjectVersion.V1) {
|
if (project.version === ProjectVersion.V1 || project.version === ProjectVersion.V2) {
|
||||||
throw new BadRequestError({ message: "Please upgrade your project on your dashboard" });
|
throw new BadRequestError({
|
||||||
|
message: `Project '${project.name}' is a legacy project and must be upgraded before accessing it through the admin console.`
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// check already there exist a membership if there return it
|
// check already there exist a membership if there return it
|
||||||
@@ -144,30 +141,6 @@ export const orgAdminServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const botPrivateKey = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.decryptWithRootEncryptionKey({
|
|
||||||
keyEncoding: bot.keyEncoding as SecretKeyEncoding,
|
|
||||||
iv: bot.iv,
|
|
||||||
tag: bot.tag,
|
|
||||||
ciphertext: bot.encryptedPrivateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
const userEncryptionKey = await userDAL.findUserEncKeyByUserId(actorId);
|
|
||||||
if (!userEncryptionKey)
|
|
||||||
throw new NotFoundError({ message: `User encryption key for user with ID '${actorId}' not found` });
|
|
||||||
const [newWsMember] = assignWorkspaceKeysToMembers({
|
|
||||||
decryptKey: ghostUserLatestKey,
|
|
||||||
userPrivateKey: botPrivateKey,
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
orgMembershipId: membership.id,
|
|
||||||
userPublicKey: userEncryptionKey.publicKey
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedMembership = await projectMembershipDAL.transaction(async (tx) => {
|
const updatedMembership = await projectMembershipDAL.transaction(async (tx) => {
|
||||||
const newProjectMembership = await projectMembershipDAL.create(
|
const newProjectMembership = await projectMembershipDAL.create(
|
||||||
{
|
{
|
||||||
@@ -181,16 +154,6 @@ export const orgAdminServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await projectKeyDAL.create(
|
|
||||||
{
|
|
||||||
encryptedKey: newWsMember.workspaceEncryptedKey,
|
|
||||||
nonce: newWsMember.workspaceEncryptedNonce,
|
|
||||||
senderId: ghostUser.id,
|
|
||||||
receiverId: actorId,
|
|
||||||
projectId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
return newProjectMembership;
|
return newProjectMembership;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
OrgMembershipStatus,
|
OrgMembershipStatus,
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectVersion,
|
ProjectVersion,
|
||||||
SecretKeyEncoding,
|
|
||||||
TableName,
|
TableName,
|
||||||
TProjectMemberships,
|
TProjectMemberships,
|
||||||
TProjectUserMembershipRolesInsert,
|
TProjectUserMembershipRolesInsert,
|
||||||
@@ -58,8 +57,6 @@ 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 { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
|
import { TIdentityMetadataDALFactory } from "../identity/identity-metadata-dal";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { assignWorkspaceKeysToMembers, createProjectKey } from "../project/project-fns";
|
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
|
||||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||||
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";
|
||||||
@@ -137,7 +134,6 @@ type TOrgServiceFactoryDep = {
|
|||||||
>;
|
>;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "findOne" | "updateById">;
|
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "insertMany" | "create">;
|
||||||
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
projectBotService: Pick<TProjectBotServiceFactory, "getBotKey">;
|
||||||
loginService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
loginService: Pick<TAuthLoginFactory, "generateUserTokens">;
|
||||||
@@ -169,7 +165,6 @@ export const orgServiceFactory = ({
|
|||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
samlConfigDAL,
|
samlConfigDAL,
|
||||||
oidcConfigDAL,
|
oidcConfigDAL,
|
||||||
projectBotDAL,
|
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
identityMetadataDAL,
|
identityMetadataDAL,
|
||||||
projectBotService,
|
projectBotService,
|
||||||
@@ -287,15 +282,7 @@ export const orgServiceFactory = ({
|
|||||||
user.id,
|
user.id,
|
||||||
{
|
{
|
||||||
encryptionVersion: 2,
|
encryptionVersion: 2,
|
||||||
protectedKey: encKeys.protectedKey,
|
publicKey: encKeys.publicKey
|
||||||
protectedKeyIV: encKeys.protectedKeyIV,
|
|
||||||
protectedKeyTag: encKeys.protectedKeyTag,
|
|
||||||
publicKey: encKeys.publicKey,
|
|
||||||
encryptedPrivateKey: encKeys.encryptedPrivateKey,
|
|
||||||
iv: encKeys.encryptedPrivateKeyIV,
|
|
||||||
tag: encKeys.encryptedPrivateKeyTag,
|
|
||||||
salt: encKeys.salt,
|
|
||||||
verifier: encKeys.verifier
|
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -885,29 +872,10 @@ export const orgServiceFactory = ({
|
|||||||
// So what we do is we generate a random secure password and then encrypt it with a random pub-private key
|
// 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
|
// 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)) {
|
if (!inviteeUser || (inviteeUser && !inviteeUser?.isAccepted && !existingEncrytionKey)) {
|
||||||
const serverGeneratedPassword = crypto.randomBytes(32).toString("hex");
|
|
||||||
const { tag, encoding, ciphertext, iv } = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.encryptWithRootEncryptionKey(serverGeneratedPassword);
|
|
||||||
const encKeys = await generateUserSrpKeys(inviteeEmail, serverGeneratedPassword);
|
|
||||||
await userDAL.createUserEncryption(
|
await userDAL.createUserEncryption(
|
||||||
{
|
{
|
||||||
userId: inviteeUserId,
|
userId: inviteeUserId,
|
||||||
encryptionVersion: 2,
|
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
|
tx
|
||||||
);
|
);
|
||||||
@@ -1069,106 +1037,6 @@ export const orgServiceFactory = ({
|
|||||||
|
|
||||||
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
const customRolesGroupBySlug = groupBy(customRoles, ({ slug }) => slug);
|
||||||
|
|
||||||
// this will auto generate bot
|
|
||||||
const { botKey, bot: autoGeneratedBot } = await projectBotService.getBotKey(projectId, true);
|
|
||||||
|
|
||||||
const ghostUser = await projectDAL.findProjectGhostUser(projectId, tx);
|
|
||||||
let ghostUserId = ghostUser?.id;
|
|
||||||
|
|
||||||
// backfill missing ghost user
|
|
||||||
if (!ghostUserId) {
|
|
||||||
const newGhostUser = await addGhostUser(project.orgId, tx);
|
|
||||||
const projectMembership = await projectMembershipDAL.create(
|
|
||||||
{
|
|
||||||
userId: newGhostUser.user.id,
|
|
||||||
projectId: project.id
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
await projectUserMembershipRoleDAL.create(
|
|
||||||
{ projectMembershipId: projectMembership.id, role: ProjectMembershipRole.Admin },
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({
|
|
||||||
publicKey: newGhostUser.keys.publicKey,
|
|
||||||
privateKey: newGhostUser.keys.plainPrivateKey,
|
|
||||||
plainProjectKey: botKey
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. Save the project key for the ghost user.
|
|
||||||
await projectKeyDAL.create(
|
|
||||||
{
|
|
||||||
projectId: project.id,
|
|
||||||
receiverId: newGhostUser.user.id,
|
|
||||||
encryptedKey: encryptedProjectKey,
|
|
||||||
nonce: encryptedProjectKeyIv,
|
|
||||||
senderId: newGhostUser.user.id
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
const { iv, tag, ciphertext, encoding, algorithm } = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.encryptWithRootEncryptionKey(newGhostUser.keys.plainPrivateKey);
|
|
||||||
if (autoGeneratedBot) {
|
|
||||||
await projectBotDAL.updateById(
|
|
||||||
autoGeneratedBot.id,
|
|
||||||
{
|
|
||||||
tag,
|
|
||||||
iv,
|
|
||||||
encryptedProjectKey,
|
|
||||||
encryptedProjectKeyNonce: encryptedProjectKeyIv,
|
|
||||||
encryptedPrivateKey: ciphertext,
|
|
||||||
isActive: true,
|
|
||||||
publicKey: newGhostUser.keys.publicKey,
|
|
||||||
senderId: newGhostUser.user.id,
|
|
||||||
algorithm,
|
|
||||||
keyEncoding: encoding
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ghostUserId = newGhostUser.user.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bot = await projectBotDAL.findOne({ projectId }, tx);
|
|
||||||
if (!bot) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
name: "InviteUser",
|
|
||||||
message: `Failed to find project bot for project with ID '${projectId}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const ghostUserLatestKey = await projectKeyDAL.findLatestProjectKey(ghostUserId, projectId, tx);
|
|
||||||
if (!ghostUserLatestKey) {
|
|
||||||
throw new NotFoundError({
|
|
||||||
name: "InviteUser",
|
|
||||||
message: `Failed to find project owner's latest key for project with ID '${projectId}'`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const botPrivateKey = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.decryptWithRootEncryptionKey({
|
|
||||||
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(
|
const projectMemberships = await projectMembershipDAL.insertMany(
|
||||||
userWithEncryptionKeyInvitedToProject.map((userEnc) => ({
|
userWithEncryptionKeyInvitedToProject.map((userEnc) => ({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -1191,16 +1059,6 @@ export const orgServiceFactory = ({
|
|||||||
});
|
});
|
||||||
await projectUserMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
await projectUserMembershipRoleDAL.insertMany(sanitizedProjectMembershipRoles, tx);
|
||||||
|
|
||||||
await projectKeyDAL.insertMany(
|
|
||||||
newWsMembers.map((el) => ({
|
|
||||||
encryptedKey: el.workspaceEncryptedKey,
|
|
||||||
nonce: el.workspaceEncryptedNonce,
|
|
||||||
senderId: ghostUserId,
|
|
||||||
receiverId: el.orgMembershipId,
|
|
||||||
projectId
|
|
||||||
})),
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
mailsForProjectInvitation.push({
|
mailsForProjectInvitation.push({
|
||||||
email: userWithEncryptionKeyInvitedToProject
|
email: userWithEncryptionKeyInvitedToProject
|
||||||
.filter((el) => !userIdsWithOrgInvitation.has(el.userId))
|
.filter((el) => !userIdsWithOrgInvitation.has(el.userId))
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ export const getBotKeyFnFactory = (
|
|||||||
message: `Project bot not found for project with ID '${projectId}'. Please ask an administrator to log-in to the Infisical Console.`
|
message: `Project bot not found for project with ID '${projectId}'. Please ask an administrator to log-in to the Infisical Console.`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!projectV1Keys.senderPublicKey) {
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Project bot not found for project with ID '${projectId}'. Please ask an administrator to log-in to the Infisical Console and upgrade the project.`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let userPrivateKey = "";
|
let userPrivateKey = "";
|
||||||
if (
|
if (
|
||||||
projectV1Keys?.serverEncryptedPrivateKey &&
|
projectV1Keys?.serverEncryptedPrivateKey &&
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const projectKeyDALFactory = (db: TDbClient) => {
|
|||||||
userId: string,
|
userId: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
tx?: Knex
|
tx?: Knex
|
||||||
): Promise<(TProjectKeys & { sender: { publicKey: string } }) | undefined> => {
|
): Promise<(TProjectKeys & { sender: { publicKey?: string } }) | undefined> => {
|
||||||
try {
|
try {
|
||||||
const projectKey = await (tx || db.replicaNode())(TableName.ProjectKeys)
|
const projectKey = await (tx || db.replicaNode())(TableName.ProjectKeys)
|
||||||
.join(TableName.Users, `${TableName.ProjectKeys}.senderId`, `${TableName.Users}.id`)
|
.join(TableName.Users, `${TableName.ProjectKeys}.senderId`, `${TableName.Users}.id`)
|
||||||
@@ -25,7 +25,7 @@ export const projectKeyDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("publicKey").withSchema(TableName.UserEncryptionKey))
|
.select(db.ref("publicKey").withSchema(TableName.UserEncryptionKey))
|
||||||
.first();
|
.first();
|
||||||
if (projectKey) {
|
if (projectKey) {
|
||||||
return { ...projectKey, sender: { publicKey: projectKey.publicKey } };
|
return { ...projectKey, sender: { publicKey: projectKey.publicKey || undefined } };
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find latest project key" });
|
throw new DatabaseError({ error, name: "Find latest project key" });
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
import { AddUserToWsDTO, TBootstrapSshProjectDTO } from "./project-types";
|
import { AddUserToWsDTO, TBootstrapSshProjectDTO } from "./project-types";
|
||||||
|
|
||||||
export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateKey }: AddUserToWsDTO) => {
|
export const assignWorkspaceKeysToMembers = ({ members, decryptKey, userPrivateKey }: AddUserToWsDTO) => {
|
||||||
|
if (!decryptKey.sender.publicKey) {
|
||||||
|
throw new Error("Decrypt key sender public key not found");
|
||||||
|
}
|
||||||
|
|
||||||
const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({
|
const plaintextProjectKey = crypto.encryption().asymmetric().decrypt({
|
||||||
ciphertext: decryptKey.encryptedKey,
|
ciphertext: decryptKey.encryptedKey,
|
||||||
nonce: decryptKey.nonce,
|
nonce: decryptKey.nonce,
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ export const projectQueueFactory = ({
|
|||||||
tag: data.encryptedPrivateKey.encryptedKeyTag
|
tag: data.encryptedPrivateKey.encryptedKeyTag
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!oldProjectKey.sender.publicKey) {
|
||||||
|
throw new Error("Old project key sender public key not found");
|
||||||
|
}
|
||||||
|
|
||||||
const decryptedPlainProjectKey = crypto.encryption().asymmetric().decrypt({
|
const decryptedPlainProjectKey = crypto.encryption().asymmetric().decrypt({
|
||||||
ciphertext: oldProjectKey.encryptedKey,
|
ciphertext: oldProjectKey.encryptedKey,
|
||||||
nonce: oldProjectKey.nonce,
|
nonce: oldProjectKey.nonce,
|
||||||
@@ -290,6 +294,10 @@ export const projectQueueFactory = ({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user.publicKey) {
|
||||||
|
throw new Error(`User with ID ${key.receiverId} has no public key during upgrade.`);
|
||||||
|
}
|
||||||
|
|
||||||
const [newMember] = assignWorkspaceKeysToMembers({
|
const [newMember] = assignWorkspaceKeysToMembers({
|
||||||
decryptKey: ghostUserLatestKey,
|
decryptKey: ghostUserLatestKey,
|
||||||
userPrivateKey: ghostUser.keys.plainPrivateKey,
|
userPrivateKey: ghostUser.keys.plainPrivateKey,
|
||||||
|
|||||||
@@ -55,13 +55,10 @@ import { validateMicrosoftTeamsChannelsSchema } from "../microsoft-teams/microso
|
|||||||
import { TMicrosoftTeamsIntegrationDALFactory } from "../microsoft-teams/microsoft-teams-integration-dal";
|
import { TMicrosoftTeamsIntegrationDALFactory } from "../microsoft-teams/microsoft-teams-integration-dal";
|
||||||
import { TProjectMicrosoftTeamsConfigDALFactory } from "../microsoft-teams/project-microsoft-teams-config-dal";
|
import { TProjectMicrosoftTeamsConfigDALFactory } from "../microsoft-teams/project-microsoft-teams-config-dal";
|
||||||
import { TOrgDALFactory } from "../org/org-dal";
|
import { TOrgDALFactory } from "../org/org-dal";
|
||||||
import { TOrgServiceFactory } from "../org/org-service";
|
|
||||||
import { TPkiAlertDALFactory } from "../pki-alert/pki-alert-dal";
|
import { TPkiAlertDALFactory } from "../pki-alert/pki-alert-dal";
|
||||||
import { TPkiCollectionDALFactory } from "../pki-collection/pki-collection-dal";
|
import { TPkiCollectionDALFactory } from "../pki-collection/pki-collection-dal";
|
||||||
import { TProjectBotDALFactory } from "../project-bot/project-bot-dal";
|
|
||||||
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
import { TProjectBotServiceFactory } from "../project-bot/project-bot-service";
|
||||||
import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
import { TProjectEnvDALFactory } from "../project-env/project-env-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 { 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 { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||||
@@ -78,7 +75,7 @@ import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
|||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { WorkflowIntegration, WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types";
|
import { WorkflowIntegration, WorkflowIntegrationStatus } from "../workflow-integration/workflow-integration-types";
|
||||||
import { TProjectDALFactory } from "./project-dal";
|
import { TProjectDALFactory } from "./project-dal";
|
||||||
import { assignWorkspaceKeysToMembers, bootstrapSshProject, createProjectKey } from "./project-fns";
|
import { bootstrapSshProject } from "./project-fns";
|
||||||
import { TProjectQueueFactory } from "./project-queue";
|
import { TProjectQueueFactory } from "./project-queue";
|
||||||
import { TProjectSshConfigDALFactory } from "./project-ssh-config-dal";
|
import { TProjectSshConfigDALFactory } from "./project-ssh-config-dal";
|
||||||
import {
|
import {
|
||||||
@@ -123,6 +120,7 @@ export const DEFAULT_PROJECT_ENVS = [
|
|||||||
|
|
||||||
type TProjectServiceFactoryDep = {
|
type TProjectServiceFactoryDep = {
|
||||||
projectDAL: TProjectDALFactory;
|
projectDAL: TProjectDALFactory;
|
||||||
|
identityProjectDAL: Pick<TIdentityProjectDALFactory, "create">;
|
||||||
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "transaction" | "create" | "findOne" | "updateById">;
|
projectSshConfigDAL: Pick<TProjectSshConfigDALFactory, "transaction" | "create" | "findOne" | "updateById">;
|
||||||
projectQueue: TProjectQueueFactory;
|
projectQueue: TProjectQueueFactory;
|
||||||
userDAL: TUserDALFactory;
|
userDAL: TUserDALFactory;
|
||||||
@@ -132,9 +130,7 @@ type TProjectServiceFactoryDep = {
|
|||||||
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "find">;
|
secretV2BridgeDAL: Pick<TSecretV2BridgeDALFactory, "find">;
|
||||||
projectEnvDAL: Pick<TProjectEnvDALFactory, "insertMany" | "find">;
|
projectEnvDAL: Pick<TProjectEnvDALFactory, "insertMany" | "find">;
|
||||||
identityOrgMembershipDAL: TIdentityOrgDALFactory;
|
identityOrgMembershipDAL: TIdentityOrgDALFactory;
|
||||||
identityProjectDAL: TIdentityProjectDALFactory;
|
|
||||||
identityProjectMembershipRoleDAL: Pick<TIdentityProjectMembershipRoleDALFactory, "create">;
|
identityProjectMembershipRoleDAL: Pick<TIdentityProjectMembershipRoleDALFactory, "create">;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "create" | "findLatestProjectKey" | "delete" | "find" | "insertMany">;
|
|
||||||
projectMembershipDAL: Pick<
|
projectMembershipDAL: Pick<
|
||||||
TProjectMembershipDALFactory,
|
TProjectMembershipDALFactory,
|
||||||
"create" | "findProjectGhostUser" | "findOne" | "delete" | "findAllProjectMembers"
|
"create" | "findProjectGhostUser" | "findOne" | "delete" | "findAllProjectMembers"
|
||||||
@@ -167,12 +163,10 @@ type TProjectServiceFactoryDep = {
|
|||||||
sshHostDAL: Pick<TSshHostDALFactory, "find" | "findSshHostsWithLoginMappings">;
|
sshHostDAL: Pick<TSshHostDALFactory, "find" | "findSshHostsWithLoginMappings">;
|
||||||
sshHostGroupDAL: Pick<TSshHostGroupDALFactory, "find" | "findSshHostGroupsWithLoginMappings">;
|
sshHostGroupDAL: Pick<TSshHostGroupDALFactory, "find" | "findSshHostGroupsWithLoginMappings">;
|
||||||
permissionService: TPermissionServiceFactory;
|
permissionService: TPermissionServiceFactory;
|
||||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "invalidateGetPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "invalidateGetPlan">;
|
||||||
smtpService: Pick<TSmtpService, "sendMail">;
|
smtpService: Pick<TSmtpService, "sendMail">;
|
||||||
orgDAL: Pick<TOrgDALFactory, "findOne">;
|
orgDAL: Pick<TOrgDALFactory, "findOne">;
|
||||||
keyStore: Pick<TKeyStoreFactory, "deleteItem">;
|
keyStore: Pick<TKeyStoreFactory, "deleteItem">;
|
||||||
projectBotDAL: Pick<TProjectBotDALFactory, "create">;
|
|
||||||
projectRoleDAL: Pick<TProjectRoleDALFactory, "find" | "insertMany" | "delete">;
|
projectRoleDAL: Pick<TProjectRoleDALFactory, "find" | "insertMany" | "delete">;
|
||||||
kmsService: Pick<
|
kmsService: Pick<
|
||||||
TKmsServiceFactory,
|
TKmsServiceFactory,
|
||||||
@@ -196,27 +190,25 @@ export const projectServiceFactory = ({
|
|||||||
secretDAL,
|
secretDAL,
|
||||||
secretV2BridgeDAL,
|
secretV2BridgeDAL,
|
||||||
projectQueue,
|
projectQueue,
|
||||||
projectKeyDAL,
|
|
||||||
permissionService,
|
permissionService,
|
||||||
projectBotService,
|
projectBotService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
userDAL,
|
userDAL,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
orgService,
|
|
||||||
identityProjectDAL,
|
|
||||||
identityOrgMembershipDAL,
|
identityOrgMembershipDAL,
|
||||||
projectMembershipDAL,
|
projectMembershipDAL,
|
||||||
projectEnvDAL,
|
projectEnvDAL,
|
||||||
licenseService,
|
licenseService,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
identityProjectMembershipRoleDAL,
|
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateTemplateDAL,
|
certificateTemplateDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
pkiAlertDAL,
|
pkiAlertDAL,
|
||||||
pkiSubscriberDAL,
|
pkiSubscriberDAL,
|
||||||
|
identityProjectDAL,
|
||||||
|
identityProjectMembershipRoleDAL,
|
||||||
sshCertificateAuthorityDAL,
|
sshCertificateAuthorityDAL,
|
||||||
sshCertificateAuthoritySecretDAL,
|
sshCertificateAuthoritySecretDAL,
|
||||||
sshCertificateDAL,
|
sshCertificateDAL,
|
||||||
@@ -225,7 +217,6 @@ export const projectServiceFactory = ({
|
|||||||
sshHostGroupDAL,
|
sshHostGroupDAL,
|
||||||
keyStore,
|
keyStore,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectBotDAL,
|
|
||||||
projectSlackConfigDAL,
|
projectSlackConfigDAL,
|
||||||
projectMicrosoftTeamsConfigDAL,
|
projectMicrosoftTeamsConfigDAL,
|
||||||
slackIntegrationDAL,
|
slackIntegrationDAL,
|
||||||
@@ -253,7 +244,7 @@ export const projectServiceFactory = ({
|
|||||||
type = ProjectType.SecretManager
|
type = ProjectType.SecretManager
|
||||||
}: TCreateProjectDTO) => {
|
}: TCreateProjectDTO) => {
|
||||||
const organization = await orgDAL.findOne({ id: actorOrgId });
|
const organization = await orgDAL.findOne({ id: actorOrgId });
|
||||||
const { permission, membership: orgMembership } = await permissionService.getOrgPermission(
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
organization.id,
|
organization.id,
|
||||||
@@ -277,7 +268,6 @@ export const projectServiceFactory = ({
|
|||||||
message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces."
|
message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces."
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const ghostUser = await orgService.addGhostUser(organization.id, tx);
|
|
||||||
|
|
||||||
if (kmsKeyId) {
|
if (kmsKeyId) {
|
||||||
const kms = await kmsService.getKmsById(kmsKeyId, tx);
|
const kms = await kmsService.getKmsById(kmsKeyId, tx);
|
||||||
@@ -329,19 +319,6 @@ export const projectServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// set ghost user as admin of project
|
|
||||||
const projectMembership = await projectMembershipDAL.create(
|
|
||||||
{
|
|
||||||
userId: ghostUser.user.id,
|
|
||||||
projectId: project.id
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
await projectUserMembershipRoleDAL.create(
|
|
||||||
{ projectMembershipId: projectMembership.id, role: ProjectMembershipRole.Admin },
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
// set default environments and root folder for provided environments
|
// set default environments and root folder for provided environments
|
||||||
let envs: TProjectEnvironments[] = [];
|
let envs: TProjectEnvironments[] = [];
|
||||||
if (projectTemplate) {
|
if (projectTemplate) {
|
||||||
@@ -374,55 +351,6 @@ export const projectServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Create a random key that we'll use as the project key.
|
|
||||||
const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({
|
|
||||||
publicKey: ghostUser.keys.publicKey,
|
|
||||||
privateKey: ghostUser.keys.plainPrivateKey
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. Save the project key for the ghost user.
|
|
||||||
await projectKeyDAL.create(
|
|
||||||
{
|
|
||||||
projectId: project.id,
|
|
||||||
receiverId: ghostUser.user.id,
|
|
||||||
encryptedKey: encryptedProjectKey,
|
|
||||||
nonce: encryptedProjectKeyIv,
|
|
||||||
senderId: ghostUser.user.id
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
const { iv, tag, ciphertext, encoding, algorithm } = crypto
|
|
||||||
.encryption()
|
|
||||||
.symmetric()
|
|
||||||
.encryptWithRootEncryptionKey(ghostUser.keys.plainPrivateKey);
|
|
||||||
|
|
||||||
// 5. Create & a bot for the project
|
|
||||||
await projectBotDAL.create(
|
|
||||||
{
|
|
||||||
name: "Infisical Bot (Ghost)",
|
|
||||||
projectId: project.id,
|
|
||||||
tag,
|
|
||||||
iv,
|
|
||||||
encryptedProjectKey,
|
|
||||||
encryptedProjectKeyNonce: encryptedProjectKeyIv,
|
|
||||||
encryptedPrivateKey: ciphertext,
|
|
||||||
isActive: true,
|
|
||||||
publicKey: ghostUser.keys.publicKey,
|
|
||||||
senderId: ghostUser.user.id,
|
|
||||||
algorithm,
|
|
||||||
keyEncoding: encoding
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find the ghost users latest key
|
|
||||||
const latestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.user.id, project.id, tx);
|
|
||||||
|
|
||||||
if (!latestKey) {
|
|
||||||
throw new Error("Latest key not found for user");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the project is being created by a user, add the user to the project as an admin
|
// If the project is being created by a user, add the user to the project as an admin
|
||||||
if (actor === ActorType.USER) {
|
if (actor === ActorType.USER) {
|
||||||
// Find public key of user
|
// Find public key of user
|
||||||
@@ -432,17 +360,6 @@ export const projectServiceFactory = ({
|
|||||||
throw new Error("User not found");
|
throw new Error("User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const [projectAdmin] = assignWorkspaceKeysToMembers({
|
|
||||||
decryptKey: latestKey,
|
|
||||||
userPrivateKey: ghostUser.keys.plainPrivateKey,
|
|
||||||
members: [
|
|
||||||
{
|
|
||||||
userPublicKey: user.publicKey,
|
|
||||||
orgMembershipId: orgMembership.id
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create a membership for the user
|
// Create a membership for the user
|
||||||
const userProjectMembership = await projectMembershipDAL.create(
|
const userProjectMembership = await projectMembershipDAL.create(
|
||||||
{
|
{
|
||||||
@@ -455,18 +372,6 @@ export const projectServiceFactory = ({
|
|||||||
{ projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin },
|
{ projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin },
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a project key for the user
|
|
||||||
await projectKeyDAL.create(
|
|
||||||
{
|
|
||||||
encryptedKey: projectAdmin.workspaceEncryptedKey,
|
|
||||||
nonce: projectAdmin.workspaceEncryptedNonce,
|
|
||||||
senderId: ghostUser.user.id,
|
|
||||||
receiverId: user.id,
|
|
||||||
projectId: project.id
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the project is being created by an identity, add the identity to the project as an admin
|
// If the project is being created by an identity, add the identity to the project as an admin
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ export type TUpgradeProjectDTO = {
|
|||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type AddUserToWsDTO = {
|
export type AddUserToWsDTO = {
|
||||||
decryptKey: TProjectKeys & { sender: { publicKey: string } };
|
decryptKey: TProjectKeys & { sender: { publicKey?: string } };
|
||||||
userPrivateKey: string;
|
userPrivateKey: string;
|
||||||
members: {
|
members: {
|
||||||
orgMembershipId: string;
|
orgMembershipId: string;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const reminderServiceFactory = ({
|
|||||||
const $manageReminderRecipients = async (reminderId: string, newRecipients?: string[] | null): Promise<void> => {
|
const $manageReminderRecipients = async (reminderId: string, newRecipients?: string[] | null): Promise<void> => {
|
||||||
if (!newRecipients || newRecipients.length === 0) {
|
if (!newRecipients || newRecipients.length === 0) {
|
||||||
// If no recipients provided, remove all existing recipients
|
// If no recipients provided, remove all existing recipients
|
||||||
await reminderRecipientDAL.deleteById(reminderId);
|
await reminderRecipientDAL.delete({ reminderId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,25 +79,33 @@ export const reminderServiceFactory = ({
|
|||||||
repeatDays,
|
repeatDays,
|
||||||
nextReminderDate: nextReminderDateInput,
|
nextReminderDate: nextReminderDateInput,
|
||||||
recipients,
|
recipients,
|
||||||
projectId
|
projectId,
|
||||||
|
fromDate: fromDateInput
|
||||||
}: {
|
}: {
|
||||||
secretId?: string;
|
secretId?: string;
|
||||||
message?: string | null;
|
message?: string | null;
|
||||||
repeatDays?: number | null;
|
repeatDays?: number | null;
|
||||||
nextReminderDate?: string | null;
|
nextReminderDate?: string | null;
|
||||||
recipients?: string[] | null;
|
recipients?: string[] | null;
|
||||||
|
fromDate?: string | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!secretId) {
|
if (!secretId) {
|
||||||
throw new BadRequestError({ message: "secretId is required" });
|
throw new BadRequestError({ message: "secretId is required" });
|
||||||
}
|
}
|
||||||
let nextReminderDate;
|
let nextReminderDate;
|
||||||
|
let fromDate;
|
||||||
if (nextReminderDateInput) {
|
if (nextReminderDateInput) {
|
||||||
nextReminderDate = new Date(nextReminderDateInput);
|
nextReminderDate = new Date(nextReminderDateInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repeatDays && repeatDays > 0) {
|
if (repeatDays) {
|
||||||
nextReminderDate = $addDays(repeatDays);
|
if (fromDateInput) {
|
||||||
|
fromDate = new Date(fromDateInput);
|
||||||
|
nextReminderDate = fromDate;
|
||||||
|
} else {
|
||||||
|
nextReminderDate = $addDays(repeatDays);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nextReminderDate) {
|
if (!nextReminderDate) {
|
||||||
@@ -112,7 +120,8 @@ export const reminderServiceFactory = ({
|
|||||||
await reminderDAL.updateById(existingReminder.id, {
|
await reminderDAL.updateById(existingReminder.id, {
|
||||||
message,
|
message,
|
||||||
repeatDays,
|
repeatDays,
|
||||||
nextReminderDate
|
nextReminderDate,
|
||||||
|
fromDate
|
||||||
});
|
});
|
||||||
reminderId = existingReminder.id;
|
reminderId = existingReminder.id;
|
||||||
} else {
|
} else {
|
||||||
@@ -121,7 +130,8 @@ export const reminderServiceFactory = ({
|
|||||||
secretId,
|
secretId,
|
||||||
message,
|
message,
|
||||||
repeatDays,
|
repeatDays,
|
||||||
nextReminderDate
|
nextReminderDate,
|
||||||
|
fromDate
|
||||||
});
|
});
|
||||||
reminderId = newReminder.id;
|
reminderId = newReminder.id;
|
||||||
}
|
}
|
||||||
@@ -280,14 +290,28 @@ export const reminderServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const processedReminders = remindersData.map(
|
const processedReminders = remindersData.map(
|
||||||
({ secretId, message, repeatDays, nextReminderDate: nextReminderDateInput, recipients, projectId }) => {
|
({
|
||||||
|
secretId,
|
||||||
|
message,
|
||||||
|
repeatDays,
|
||||||
|
nextReminderDate: nextReminderDateInput,
|
||||||
|
recipients,
|
||||||
|
projectId,
|
||||||
|
fromDate: fromDateInput
|
||||||
|
}) => {
|
||||||
let nextReminderDate;
|
let nextReminderDate;
|
||||||
|
let fromDate;
|
||||||
if (nextReminderDateInput) {
|
if (nextReminderDateInput) {
|
||||||
nextReminderDate = new Date(nextReminderDateInput);
|
nextReminderDate = new Date(nextReminderDateInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repeatDays && repeatDays > 0 && !nextReminderDate) {
|
if (repeatDays && !nextReminderDate) {
|
||||||
nextReminderDate = $addDays(repeatDays);
|
if (fromDateInput) {
|
||||||
|
fromDate = new Date(fromDateInput);
|
||||||
|
nextReminderDate = fromDate;
|
||||||
|
} else {
|
||||||
|
nextReminderDate = $addDays(repeatDays);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nextReminderDate) {
|
if (!nextReminderDate) {
|
||||||
@@ -302,17 +326,19 @@ export const reminderServiceFactory = ({
|
|||||||
repeatDays,
|
repeatDays,
|
||||||
nextReminderDate,
|
nextReminderDate,
|
||||||
recipients: recipients ? [...new Set(recipients)] : [],
|
recipients: recipients ? [...new Set(recipients)] : [],
|
||||||
projectId
|
projectId,
|
||||||
|
fromDate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const newReminders = await reminderDAL.insertMany(
|
const newReminders = await reminderDAL.insertMany(
|
||||||
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate }) => ({
|
processedReminders.map(({ secretId, message, repeatDays, nextReminderDate, fromDate }) => ({
|
||||||
secretId,
|
secretId,
|
||||||
message,
|
message,
|
||||||
repeatDays,
|
repeatDays,
|
||||||
nextReminderDate
|
nextReminderDate,
|
||||||
|
fromDate
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type TReminder = {
|
|||||||
message?: string | null;
|
message?: string | null;
|
||||||
repeatDays?: number | null;
|
repeatDays?: number | null;
|
||||||
nextReminderDate: Date;
|
nextReminderDate: Date;
|
||||||
|
fromDate?: Date | null;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
};
|
};
|
||||||
@@ -21,6 +22,7 @@ export type TCreateReminderDTO = {
|
|||||||
secretId?: string;
|
secretId?: string;
|
||||||
message?: string | null;
|
message?: string | null;
|
||||||
repeatDays?: number | null;
|
repeatDays?: number | null;
|
||||||
|
fromDate?: string | null;
|
||||||
nextReminderDate?: string | null;
|
nextReminderDate?: string | null;
|
||||||
recipients?: string[] | null;
|
recipients?: string[] | null;
|
||||||
};
|
};
|
||||||
@@ -31,6 +33,7 @@ export type TBatchCreateReminderDTO = {
|
|||||||
message?: string | null;
|
message?: string | null;
|
||||||
repeatDays?: number | null;
|
repeatDays?: number | null;
|
||||||
nextReminderDate?: string | Date | null;
|
nextReminderDate?: string | Date | null;
|
||||||
|
fromDate?: Date | null;
|
||||||
recipients?: string[] | null;
|
recipients?: string[] | null;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
}[];
|
}[];
|
||||||
@@ -95,6 +98,7 @@ export interface TReminderServiceFactory {
|
|||||||
nextReminderDate?: string | null;
|
nextReminderDate?: string | null;
|
||||||
recipients?: string[] | null;
|
recipients?: string[] | null;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
fromDate?: string | null;
|
||||||
}) => Promise<{
|
}) => Promise<{
|
||||||
id: string;
|
id: string;
|
||||||
created: boolean;
|
created: boolean;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import sodium from "libsodium-wrappers";
|
|||||||
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { TGatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import {
|
import {
|
||||||
getGitHubAppAuthToken,
|
getGitHubAppAuthToken,
|
||||||
|
getGitHubInstanceApiUrl,
|
||||||
GitHubConnectionMethod,
|
GitHubConnectionMethod,
|
||||||
makePaginatedGitHubRequest,
|
makePaginatedGitHubRequest,
|
||||||
requestWithGitHubGateway
|
requestWithGitHubGateway
|
||||||
@@ -73,7 +74,7 @@ const getPublicKey = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
|
const response = await requestWithGitHubGateway<TGitHubPublicKey>(connection, gatewayService, {
|
||||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
@@ -111,7 +112,7 @@ const deleteSecret = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await requestWithGitHubGateway(connection, gatewayService, {
|
await requestWithGitHubGateway(connection, gatewayService, {
|
||||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
@@ -157,7 +158,7 @@ const putSecret = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
await requestWithGitHubGateway(connection, gatewayService, {
|
await requestWithGitHubGateway(connection, gatewayService, {
|
||||||
url: `https://api.${connection.credentials.host || "github.com"}${path}`,
|
url: `https://${await getGitHubInstanceApiUrl(connection)}${path}`,
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: {
|
headers: {
|
||||||
Accept: "application/vnd.github+json",
|
Accept: "application/vnd.github+json",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const baseSecretSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: Secre
|
|||||||
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("connectionEncryptedCredentials"),
|
||||||
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
db.ref("description").withSchema(TableName.AppConnection).as("connectionDescription"),
|
||||||
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
db.ref("version").withSchema(TableName.AppConnection).as("connectionVersion"),
|
||||||
|
db.ref("gatewayId").withSchema(TableName.AppConnection).as("connectionGatewayId"),
|
||||||
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
db.ref("createdAt").withSchema(TableName.AppConnection).as("connectionCreatedAt"),
|
||||||
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
db.ref("updatedAt").withSchema(TableName.AppConnection).as("connectionUpdatedAt"),
|
||||||
db
|
db
|
||||||
@@ -65,6 +66,7 @@ const expandSecretSync = (
|
|||||||
connectionUpdatedAt,
|
connectionUpdatedAt,
|
||||||
connectionVersion,
|
connectionVersion,
|
||||||
connectionIsPlatformManagedCredentials,
|
connectionIsPlatformManagedCredentials,
|
||||||
|
connectionGatewayId,
|
||||||
...el
|
...el
|
||||||
} = secretSync;
|
} = secretSync;
|
||||||
|
|
||||||
@@ -83,7 +85,8 @@ const expandSecretSync = (
|
|||||||
createdAt: connectionCreatedAt,
|
createdAt: connectionCreatedAt,
|
||||||
updatedAt: connectionUpdatedAt,
|
updatedAt: connectionUpdatedAt,
|
||||||
version: connectionVersion,
|
version: connectionVersion,
|
||||||
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials
|
isPlatformManagedCredentials: connectionIsPlatformManagedCredentials,
|
||||||
|
gatewayId: connectionGatewayId
|
||||||
},
|
},
|
||||||
folder: folder
|
folder: folder
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface AccessApprovalRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface AccessApprovalRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@@ -38,18 +40,15 @@ export const AccessApprovalRequestTemplate = ({
|
|||||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
You have a new access approval request pending review for the project <strong>{projectName}</strong>
|
You have a new access approval request pending review for the project <strong>{projectName}</strong>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-black text-[14px] leading-[24px]">
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
<strong>{requesterFullName}</strong> (
|
<strong>{requesterFullName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>)
|
||||||
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
has requested {isTemporary ? "temporary" : "permanent"} access to <strong>{secretPath}</strong> in the{" "}
|
||||||
{requesterEmail}
|
|
||||||
</Link>
|
|
||||||
) has requested {isTemporary ? "temporary" : "permanent"} access to <strong>{secretPath}</strong> in the{" "}
|
|
||||||
<strong>{environment}</strong> environment.
|
<strong>{environment}</strong> environment.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{isTemporary && (
|
{isTemporary && (
|
||||||
<Text className="text-[14px] text-red-500 leading-[24px]">
|
<Text className="text-[14px] text-red-600 leading-[24px]">
|
||||||
<strong>This access will expire {expiresIn} after approval.</strong>
|
<strong>This access will expire {expiresIn} after approval.</strong>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
@@ -67,13 +66,8 @@ export const AccessApprovalRequestTemplate = ({
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton href={approvalUrl}>Review Request</BaseButton>
|
||||||
href={approvalUrl}
|
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
|
||||||
Review Request
|
|
||||||
</Button>
|
|
||||||
</Section>
|
</Section>
|
||||||
</BaseEmailWrapper>
|
</BaseEmailWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Button } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
href: string;
|
||||||
|
children: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BaseButton = ({ href, children }: Props) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
href={href}
|
||||||
|
className="rounded-[8px] py-[12px] px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -16,23 +16,21 @@ export const BaseEmailWrapper = ({ title, preview, children, siteUrl }: BaseEmai
|
|||||||
<Body className="bg-gray-300 my-auto mx-auto font-sans px-[8px] py-[4px]">
|
<Body className="bg-gray-300 my-auto mx-auto font-sans px-[8px] py-[4px]">
|
||||||
<Preview>{preview}</Preview>
|
<Preview>{preview}</Preview>
|
||||||
<Container className="bg-white rounded-xl my-[40px] mx-auto pb-[0px] max-w-[500px]">
|
<Container className="bg-white rounded-xl my-[40px] mx-auto pb-[0px] max-w-[500px]">
|
||||||
<Section className="border-0 border-b border-[#d1e309] border-solid bg-[#EBF852] mb-[44px] h-[10px] rounded-t-xl" />
|
<Section className="mb-[24px] px-[24px] mt-[24px]">
|
||||||
<Section className="px-[32px] mb-[18px]">
|
<Img
|
||||||
<Section className="w-[48px] h-[48px] border border-solid border-gray-300 rounded-full bg-gray-100 mx-auto">
|
src="https://infisical.com/_next/image?url=%2Fimages%2Flogo-black.png&w=64&q=75"
|
||||||
<Img
|
width="36"
|
||||||
src={`https://infisical.com/_next/image?url=%2Fimages%2Flogo-black.png&w=64&q=75`}
|
alt="Infisical Logo"
|
||||||
width="32"
|
className="mx-auto"
|
||||||
alt="Infisical Logo"
|
/>
|
||||||
className="mx-auto"
|
|
||||||
/>
|
|
||||||
</Section>
|
|
||||||
</Section>
|
</Section>
|
||||||
|
<Hr className=" mb-[32px] mt-[0px] h-[1px]" />
|
||||||
<Section className="px-[28px]">{children}</Section>
|
<Section className="px-[28px]">{children}</Section>
|
||||||
<Hr className=" mt-[32px] mb-[0px] h-[1px]" />
|
<Hr className=" mt-[32px] mb-[0px] h-[1px]" />
|
||||||
<Section className="px-[24px] text-center">
|
<Section className="px-[24px] text-center">
|
||||||
<Text className="text-gray-500 text-[12px]">
|
<Text className="text-gray-500 text-[12px]">
|
||||||
Email sent via{" "}
|
Email sent via{" "}
|
||||||
<Link href={siteUrl} className="text-slate-700 no-underline">
|
<Link href={siteUrl} className="text-slate-700 underline decoration-slate-700">
|
||||||
Infisical
|
Infisical
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Link } from "@react-email/components";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
href: string;
|
||||||
|
children: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BaseLink = ({ href, children }: Props) => {
|
||||||
|
return (
|
||||||
|
<Link href={href} className="text-slate-700 underline decoration-slate-700">
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface EmailMfaTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface EmailMfaTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
code: string;
|
code: string;
|
||||||
@@ -25,11 +26,7 @@ export const EmailMfaTemplate = ({ code, siteUrl, isCloud }: EmailMfaTemplatePro
|
|||||||
<strong>Not you?</strong>{" "}
|
<strong>Not you?</strong>{" "}
|
||||||
{isCloud ? (
|
{isCloud ? (
|
||||||
<>
|
<>
|
||||||
Contact us at{" "}
|
Contact us at <BaseLink href="mailto:[email protected]">support@infisical.com</BaseLink> immediately
|
||||||
<Link href="mailto:[email protected]" className="text-slate-700 no-underline">
|
|
||||||
support@infisical.com
|
|
||||||
</Link>{" "}
|
|
||||||
immediately
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Contact your administrator immediately"
|
"Contact your administrator immediately"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface EmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface EmailVerificationTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
code: string;
|
code: string;
|
||||||
@@ -29,10 +30,7 @@ export const EmailVerificationTemplate = ({ code, siteUrl, isCloud }: EmailVerif
|
|||||||
<strong>Questions about Infisical?</strong>{" "}
|
<strong>Questions about Infisical?</strong>{" "}
|
||||||
{isCloud ? (
|
{isCloud ? (
|
||||||
<>
|
<>
|
||||||
Email us at{" "}
|
Email us at <BaseLink href="mailto:[email protected]">support@infisical.com</BaseLink>
|
||||||
<Link href="mailto:[email protected]" className="text-slate-700 no-underline">
|
|
||||||
support@infisical.com
|
|
||||||
</Link>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Contact your administrator"
|
"Contact your administrator"
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface ExternalImportFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface ExternalImportFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
error: string;
|
error: string;
|
||||||
@@ -21,12 +22,9 @@ export const ExternalImportFailedTemplate = ({ error, siteUrl, provider }: Exter
|
|||||||
</Text>
|
</Text>
|
||||||
<Text className="text-black text-[14px] leading-[24px]">
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
If your issue persists, you can contact the Infisical team at{" "}
|
If your issue persists, you can contact the Infisical team at{" "}
|
||||||
<Link href="mailto:[email protected]" className="text-slate-700 no-underline">
|
<BaseLink href="mailto:[email protected]">support@infisical.com</BaseLink>.
|
||||||
support@infisical.com
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-[14px] text-red-500 leading-[24px]">
|
<Text className="text-[14px] text-red-600 leading-[24px]">
|
||||||
<strong>Error:</strong> "{error}"
|
<strong>Error:</strong> "{error}"
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
interface IntegrationSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface IntegrationSyncFailedTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
@@ -30,7 +31,7 @@ export const IntegrationSyncFailedTemplate = ({
|
|||||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
<strong>{count}</strong> integration(s) failed to sync
|
<strong>{count}</strong> integration(s) failed to sync
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[26px] pb-[4px] text-[14px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<strong>Project</strong>
|
<strong>Project</strong>
|
||||||
<Text className="text-[14px] mt-[4px]">{projectName}</Text>
|
<Text className="text-[14px] mt-[4px]">{projectName}</Text>
|
||||||
<strong>Environment</strong>
|
<strong>Environment</strong>
|
||||||
@@ -38,15 +39,10 @@ export const IntegrationSyncFailedTemplate = ({
|
|||||||
<strong>Secret Path</strong>
|
<strong>Secret Path</strong>
|
||||||
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
|
<Text className="text-[14px] mt-[4px]">{secretPath}</Text>
|
||||||
<strong className="text-black">Failure Reason:</strong>
|
<strong className="text-black">Failure Reason:</strong>
|
||||||
<Text className="text-[14px] mt-[4px] text-red-500 leading-[24px]">"{syncMessage}"</Text>
|
<Text className="text-[14px] mt-[4px] text-red-600 leading-[24px]">"{syncMessage}"</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton href={integrationUrl}>View Integrations</BaseButton>
|
||||||
href={integrationUrl}
|
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
|
||||||
View Integrations
|
|
||||||
</Button>
|
|
||||||
</Section>
|
</Section>
|
||||||
</BaseEmailWrapper>
|
</BaseEmailWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface NewDeviceLoginTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface NewDeviceLoginTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -42,9 +43,7 @@ export const NewDeviceLoginTemplate = ({
|
|||||||
<Text className="mb-[0px]">
|
<Text className="mb-[0px]">
|
||||||
If you believe that this login is suspicious, please contact{" "}
|
If you believe that this login is suspicious, please contact{" "}
|
||||||
{isCloud ? (
|
{isCloud ? (
|
||||||
<Link href="mailto:[email protected]" className="text-slate-700 no-underline">
|
<BaseLink href="mailto:[email protected]">support@infisical.com</BaseLink>
|
||||||
support@infisical.com
|
|
||||||
</Link>
|
|
||||||
) : (
|
) : (
|
||||||
"your administrator"
|
"your administrator"
|
||||||
)}{" "}
|
)}{" "}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface OrgAdminBreakglassAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface OrgAdminBreakglassAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -35,10 +36,7 @@ export const OrgAdminBreakglassAccessTemplate = ({
|
|||||||
<Text className="text-[14px] mt-[4px]">{userAgent}</Text>
|
<Text className="text-[14px] mt-[4px]">{userAgent}</Text>
|
||||||
<Text className="text-[14px]">
|
<Text className="text-[14px]">
|
||||||
If you'd like to disable Admin SSO Bypass, please visit{" "}
|
If you'd like to disable Admin SSO Bypass, please visit{" "}
|
||||||
<Link href={`${siteUrl}/organization/settings`} className="text-slate-700 no-underline">
|
<BaseLink href={`${siteUrl}/organization/settings`}>Organization Security Settings</BaseLink>.
|
||||||
Organization Security Settings
|
|
||||||
</Link>
|
|
||||||
.
|
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
</BaseEmailWrapper>
|
</BaseEmailWrapper>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Heading, Section, Text } from "@react-email/components";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface OrgAdminProjectGrantAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview"> {
|
interface OrgAdminProjectGrantAccessTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview"> {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -24,8 +25,8 @@ export const OrgAdminProjectGrantAccessTemplate = ({
|
|||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[24px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mt-[36px] pt-[24px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-[14px] mt-[4px]">
|
<Text className="text-[14px] mt-[4px]">
|
||||||
The organization admin <strong>{email}</strong> has self-issued direct access to the project{" "}
|
The organization admin <BaseLink href={`mailto:${email}`}>{email}</BaseLink> has self-issued direct access to
|
||||||
<strong>{projectName}</strong>.
|
the project <strong>{projectName}</strong>.
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
</BaseEmailWrapper>
|
</BaseEmailWrapper>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface OrganizationInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
interface OrganizationInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||||
metadata?: string;
|
metadata?: string;
|
||||||
@@ -36,15 +38,13 @@ export const OrganizationInvitationTemplate = ({
|
|||||||
<br />
|
<br />
|
||||||
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-black text-[14px] leading-[24px]">
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
{inviterFirstName && inviterUsername ? (
|
{inviterFirstName && inviterUsername ? (
|
||||||
<>
|
<>
|
||||||
<strong>{inviterFirstName}</strong> (
|
<strong>{inviterFirstName}</strong> (
|
||||||
<Link href={`mailto:${inviterUsername}`} className="text-slate-700 no-underline">
|
<BaseLink href={`mailto:${inviterUsername}`}>{inviterUsername}</BaseLink>) has invited you to collaborate
|
||||||
{inviterUsername}
|
on <strong>{organizationName}</strong>.
|
||||||
</Link>
|
|
||||||
) has invited you to collaborate on <strong>{organizationName}</strong>.
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -53,13 +53,12 @@ export const OrganizationInvitationTemplate = ({
|
|||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton
|
||||||
href={`${callback_url}?token=${token}${metadata ? `&metadata=${metadata}` : ""}&to=${encodeURIComponent(email)}&organization_id=${organizationId}`}
|
href={`${callback_url}?token=${token}${metadata ? `&metadata=${metadata}` : ""}&to=${encodeURIComponent(email)}&organization_id=${organizationId}`}
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
>
|
||||||
Accept Invite
|
Accept Invite
|
||||||
</Button>
|
</BaseButton>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
||||||
<Text className="mb-[0px]">
|
<Text className="mb-[0px]">
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface PasswordResetTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface PasswordResetTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -20,16 +22,13 @@ export const PasswordResetTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
|||||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
<strong>Account Recovery</strong>
|
<strong>Account Recovery</strong>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-[14px]">A password reset was requested for your Infisical account.</Text>
|
<Text className="text-[14px]">A password reset was requested for your Infisical account.</Text>
|
||||||
<Text className="text-[14px]">
|
<Text className="text-[14px]">
|
||||||
If you did not initiate this request, please contact{" "}
|
If you did not initiate this request, please contact{" "}
|
||||||
{isCloud ? (
|
{isCloud ? (
|
||||||
<>
|
<>
|
||||||
us immediately at{" "}
|
us immediately at <BaseLink href="mailto:[email protected]">support@infisical.com</BaseLink>
|
||||||
<Link href="mailto:[email protected]" className="text-slate-700 no-underline">
|
|
||||||
support@infisical.com
|
|
||||||
</Link>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"your administrator immediately"
|
"your administrator immediately"
|
||||||
@@ -37,13 +36,8 @@ export const PasswordResetTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
|||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}>Reset Password</BaseButton>
|
||||||
href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}
|
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
|
||||||
Reset Password
|
|
||||||
</Button>
|
|
||||||
</Section>
|
</Section>
|
||||||
</BaseEmailWrapper>
|
</BaseEmailWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
import { Button, Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseLink } from "@app/services/smtp/emails/BaseLink";
|
||||||
|
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
interface PasswordSetupTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface PasswordSetupTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
@@ -16,19 +18,16 @@ export const PasswordSetupTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
|||||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
<strong>Password Setup</strong>
|
<strong>Password Setup</strong>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-[14px]">Someone requested to set up a password for your Infisical account.</Text>
|
<Text className="text-[14px]">Someone requested to set up a password for your Infisical account.</Text>
|
||||||
<Text className="text-[14px] text-red-500">
|
<Text className="text-[14px] text-red-600">
|
||||||
Make sure you are already logged in to Infisical in the current browser before clicking the link below.
|
Make sure you are already logged in to Infisical in the current browser before clicking the link below.
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-[14px]">
|
<Text className="text-[14px]">
|
||||||
If you did not initiate this request, please contact{" "}
|
If you did not initiate this request, please contact{" "}
|
||||||
{isCloud ? (
|
{isCloud ? (
|
||||||
<>
|
<>
|
||||||
us immediately at{" "}
|
us immediately at <BaseLink href="mailto:[email protected]">support@infisical.com</BaseLink>
|
||||||
<Link href="mailto:[email protected]" className="text-slate-700 no-underline">
|
|
||||||
support@infisical.com
|
|
||||||
</Link>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"your administrator immediately"
|
"your administrator immediately"
|
||||||
@@ -36,7 +35,7 @@ export const PasswordSetupTemplate = ({ email, isCloud, siteUrl, callback_url, t
|
|||||||
.
|
.
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<Button
|
||||||
href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}
|
href={`${callback_url}?token=${token}&to=${encodeURIComponent(email)}`}
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface ProjectAccessRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
interface ProjectAccessRequestTemplateProps extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@@ -30,26 +32,17 @@ export const ProjectAccessRequestTemplate = ({
|
|||||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
A user has requested access to the project <strong>{projectName}</strong>
|
A user has requested access to the project <strong>{projectName}</strong>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-black text-[14px] leading-[24px]">
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
<strong>{requesterName}</strong> (
|
<strong>{requesterName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>) has
|
||||||
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
requested access to the project <strong>{projectName}</strong> in the organization <strong>{orgName}</strong>.
|
||||||
{requesterEmail}
|
|
||||||
</Link>
|
|
||||||
) has requested access to the project <strong>{projectName}</strong> in the organization{" "}
|
|
||||||
<strong>{orgName}</strong>.
|
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-[14px] text-slate-700 leading-[24px]">
|
<Text className="text-[14px] text-slate-700 leading-[24px]">
|
||||||
<strong className="text-black">User note:</strong> "{note}"
|
<strong className="text-black">User note:</strong> "{note}"
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton href={callback_url}>Grant Access</BaseButton>
|
||||||
href={callback_url}
|
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
|
||||||
Grant Access
|
|
||||||
</Button>
|
|
||||||
</Section>
|
</Section>
|
||||||
</BaseEmailWrapper>
|
</BaseEmailWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
interface ProjectInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
interface ProjectInvitationTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||||
@@ -18,18 +19,13 @@ export const ProjectInvitationTemplate = ({ callback_url, workspaceName, siteUrl
|
|||||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
You've been invited to join a project on Infisical
|
You've been invited to join a project on Infisical
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-black text-[14px] leading-[24px]">
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
You've been invited to join the project <strong>{workspaceName}</strong>.
|
You've been invited to join the project <strong>{workspaceName}</strong>.
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton href={callback_url}>Join Project</BaseButton>
|
||||||
href={callback_url}
|
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
|
||||||
Join Project
|
|
||||||
</Button>
|
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
||||||
<Text className="mb-[0px]">
|
<Text className="mb-[0px]">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Heading, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
|
||||||
interface ScimUserProvisionedTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
interface ScimUserProvisionedTemplateProps extends Omit<BaseEmailWrapperProps, "preview" | "title"> {
|
||||||
@@ -24,18 +25,13 @@ export const ScimUserProvisionedTemplate = ({
|
|||||||
<br />
|
<br />
|
||||||
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
<strong>{organizationName}</strong> on <strong>Infisical</strong>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border text-center border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-black text-[14px] leading-[24px]">
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
You've been invited to collaborate on <strong>{organizationName}</strong>.
|
You've been invited to collaborate on <strong>{organizationName}</strong>.
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton href={callback_url}>Accept Invite</BaseButton>
|
||||||
href={callback_url}
|
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
|
||||||
Accept Invite
|
|
||||||
</Button>
|
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
<Section className="mt-[24px] bg-gray-50 pt-[2px] pb-[16px] border border-solid border-gray-200 px-[24px] rounded-md text-gray-800">
|
||||||
<Text className="mb-[0px]">
|
<Text className="mb-[0px]">
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Button, Heading, Link, Section, Text } from "@react-email/components";
|
import { Heading, Section, Text } from "@react-email/components";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { BaseButton } from "./BaseButton";
|
||||||
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
import { BaseEmailWrapper, BaseEmailWrapperProps } from "./BaseEmailWrapper";
|
||||||
|
import { BaseLink } from "./BaseLink";
|
||||||
|
|
||||||
interface SecretApprovalRequestBypassedTemplateProps
|
interface SecretApprovalRequestBypassedTemplateProps
|
||||||
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
extends Omit<BaseEmailWrapperProps, "title" | "preview" | "children"> {
|
||||||
@@ -35,13 +37,10 @@ export const SecretApprovalRequestBypassedTemplate = ({
|
|||||||
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
<Heading className="text-black text-[18px] leading-[28px] text-center font-normal p-0 mx-0">
|
||||||
A secret approval request has been bypassed in the project <strong>{projectName}</strong>
|
A secret approval request has been bypassed in the project <strong>{projectName}</strong>
|
||||||
</Heading>
|
</Heading>
|
||||||
<Section className="px-[24px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
<Section className="px-[24px] mb-[28px] mt-[36px] pt-[12px] pb-[8px] border border-solid border-gray-200 rounded-md bg-gray-50">
|
||||||
<Text className="text-black text-[14px] leading-[24px]">
|
<Text className="text-black text-[14px] leading-[24px]">
|
||||||
<strong>{requesterFullName}</strong> (
|
<strong>{requesterFullName}</strong> (<BaseLink href={`mailto:${requesterEmail}`}>{requesterEmail}</BaseLink>)
|
||||||
<Link href={`mailto:${requesterEmail}`} className="text-slate-700 no-underline">
|
has {requestType === "change" ? "merged" : "accessed"} a secret {requestType === "change" ? "to" : "in"}{" "}
|
||||||
{requesterEmail}
|
|
||||||
</Link>
|
|
||||||
) has {requestType === "change" ? "merged" : "accessed"} a secret {requestType === "change" ? "to" : "in"}{" "}
|
|
||||||
<strong>{secretPath}</strong> in the <strong>{environment}</strong> environment without obtaining the required
|
<strong>{secretPath}</strong> in the <strong>{environment}</strong> environment without obtaining the required
|
||||||
approval.
|
approval.
|
||||||
</Text>
|
</Text>
|
||||||
@@ -50,13 +49,8 @@ export const SecretApprovalRequestBypassedTemplate = ({
|
|||||||
{bypassReason}"
|
{bypassReason}"
|
||||||
</Text>
|
</Text>
|
||||||
</Section>
|
</Section>
|
||||||
<Section className="text-center mt-[28px]">
|
<Section className="text-center">
|
||||||
<Button
|
<BaseButton href={approvalUrl}>Review Bypass</BaseButton>
|
||||||
href={approvalUrl}
|
|
||||||
className="rounded-md p-3 px-[28px] my-[8px] text-center text-[16px] bg-[#EBF852] border-solid border border-[#d1e309] text-black font-medium"
|
|
||||||
>
|
|
||||||
Review Bypass
|
|
||||||
</Button>
|
|
||||||
</Section>
|
</Section>
|
||||||
</BaseEmailWrapper>
|
</BaseEmailWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user