Merge branch 'heads/main' into daniel/ms-teams-integration
@@ -33,6 +33,7 @@
|
|||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
|
"@octokit/plugin-paginate-graphql": "^5.2.4",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
@@ -92,10 +93,10 @@
|
|||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"oracledb": "^6.4.0",
|
"oracledb": "^6.4.0",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"passport-github": "^1.1.0",
|
|
||||||
"passport-gitlab2": "^5.0.0",
|
"passport-gitlab2": "^5.0.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
|
"passport-oauth2": "^1.8.0",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"pg-boss": "^10.1.5",
|
"pg-boss": "^10.1.5",
|
||||||
"pg-query-stream": "^4.5.3",
|
"pg-query-stream": "^4.5.3",
|
||||||
@@ -136,7 +137,6 @@
|
|||||||
"@types/lodash.isequal": "^4.5.8",
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
"@types/node": "^20.17.30",
|
"@types/node": "^20.17.30",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/passport-github": "^1.1.12",
|
|
||||||
"@types/passport-google-oauth20": "^2.0.14",
|
"@types/passport-google-oauth20": "^2.0.14",
|
||||||
"@types/pg": "^8.10.9",
|
"@types/pg": "^8.10.9",
|
||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
@@ -7275,47 +7275,247 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/core": {
|
"node_modules/@octokit/core": {
|
||||||
"version": "5.0.2",
|
"version": "6.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.5.tgz",
|
||||||
"integrity": "sha512-cZUy1gUvd4vttMic7C0lwPed8IYXWYp8kHIMatyhY8t8n3Cpw2ILczkV5pGMPqef7v0bLo0pOHrEHarsau2Ydg==",
|
"integrity": "sha512-vvmsN0r7rguA+FySiCsbaTTobSftpIDIpPW81trAmsv9TGxg3YCujAxRYp/Uy8xmDgYCzzgulG62H7KYUFmeIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/auth-token": "^4.0.0",
|
"@octokit/auth-token": "^5.0.0",
|
||||||
"@octokit/graphql": "^7.0.0",
|
"@octokit/graphql": "^8.2.2",
|
||||||
"@octokit/request": "^8.0.2",
|
"@octokit/request": "^9.2.3",
|
||||||
"@octokit/request-error": "^5.0.0",
|
"@octokit/request-error": "^6.1.8",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^14.0.0",
|
||||||
"before-after-hook": "^2.2.0",
|
"before-after-hook": "^3.0.2",
|
||||||
|
"universal-user-agent": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/@octokit/auth-token": {
|
||||||
|
"version": "5.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz",
|
||||||
|
"integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/@octokit/endpoint": {
|
||||||
|
"version": "10.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz",
|
||||||
|
"integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^14.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "25.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.0.0.tgz",
|
||||||
|
"integrity": "sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/@octokit/request": {
|
||||||
|
"version": "9.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.3.tgz",
|
||||||
|
"integrity": "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/endpoint": "^10.1.4",
|
||||||
|
"@octokit/request-error": "^6.1.8",
|
||||||
|
"@octokit/types": "^14.0.0",
|
||||||
|
"fast-content-type-parse": "^2.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/@octokit/request-error": {
|
||||||
|
"version": "6.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz",
|
||||||
|
"integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^14.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/@octokit/types": {
|
||||||
|
"version": "14.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.0.0.tgz",
|
||||||
|
"integrity": "sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^25.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/node_modules/fast-content-type-parse": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/core/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==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"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"
|
"universal-user-agent": "^6.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/endpoint": {
|
"node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": {
|
||||||
"version": "9.0.4",
|
"version": "24.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||||
"integrity": "sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==",
|
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/endpoint/node_modules/@octokit/types": {
|
||||||
|
"version": "13.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
"universal-user-agent": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/graphql": {
|
"node_modules/@octokit/graphql": {
|
||||||
"version": "7.0.2",
|
"version": "8.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.2.tgz",
|
||||||
"integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==",
|
"integrity": "sha512-Yi8hcoqsrXGdt0yObxbebHXFOiUA+2v3n53epuOg1QUgOB6c4XzvisBNVXJSl8RYA5KrDuSL2yq9Qmqe5N0ryA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/request": "^8.0.1",
|
"@octokit/request": "^9.2.3",
|
||||||
"@octokit/types": "^12.0.0",
|
"@octokit/types": "^14.0.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/graphql/node_modules/@octokit/endpoint": {
|
||||||
|
"version": "10.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz",
|
||||||
|
"integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^14.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "25.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.0.0.tgz",
|
||||||
|
"integrity": "sha512-FZvktFu7HfOIJf2BScLKIEYjDsw6RKc7rBJCdvCTfKsVnx2GEB/Nbzjr29DUdb7vQhlzS/j8qDzdditP0OC6aw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/graphql/node_modules/@octokit/request": {
|
||||||
|
"version": "9.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.3.tgz",
|
||||||
|
"integrity": "sha512-Ma+pZU8PXLOEYzsWf0cn/gY+ME57Wq8f49WTXA8FMHp2Ps9djKw//xYJ1je8Hm0pR2lU9FUGeJRWOtxq6olt4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/endpoint": "^10.1.4",
|
||||||
|
"@octokit/request-error": "^6.1.8",
|
||||||
|
"@octokit/types": "^14.0.0",
|
||||||
|
"fast-content-type-parse": "^2.0.0",
|
||||||
|
"universal-user-agent": "^7.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/graphql/node_modules/@octokit/request-error": {
|
||||||
|
"version": "6.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz",
|
||||||
|
"integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^14.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/graphql/node_modules/@octokit/types": {
|
||||||
|
"version": "14.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.0.0.tgz",
|
||||||
|
"integrity": "sha512-VVmZP0lEhbo2O1pdq63gZFiGCKkm8PPp8AUOijlwPO6hojEVjspA0MWKP7E4hbvGxzFKNqKr6p0IYtOH/Wf/zA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^25.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/graphql/node_modules/fast-content-type-parse": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/graphql/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==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"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",
|
||||||
@@ -7410,6 +7610,18 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/plugin-paginate-graphql": {
|
||||||
|
"version": "5.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-5.2.4.tgz",
|
||||||
|
"integrity": "sha512-pLZES1jWaOynXKHOqdnwZ5ULeVR6tVVCMm+AUbp0htdcyXDU95WbkYdU4R2ej1wKj5Tu94Mee2Ne0PjPO9cCyA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@octokit/core": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@octokit/plugin-paginate-rest": {
|
"node_modules/@octokit/plugin-paginate-rest": {
|
||||||
"version": "9.1.5",
|
"version": "9.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.1.5.tgz",
|
||||||
@@ -7491,28 +7703,14 @@
|
|||||||
"@octokit/openapi-types": "^18.0.0"
|
"@octokit/openapi-types": "^18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/plugin-throttling": {
|
|
||||||
"version": "8.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.1.3.tgz",
|
|
||||||
"integrity": "sha512-pfyqaqpc0EXh5Cn4HX9lWYsZ4gGbjnSmUILeu4u2gnuM50K/wIk9s1Pxt3lVeVwekmITgN/nJdoh43Ka+vye8A==",
|
|
||||||
"dependencies": {
|
|
||||||
"@octokit/types": "^12.2.0",
|
|
||||||
"bottleneck": "^2.15.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@octokit/core": "^5.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@octokit/request": {
|
"node_modules/@octokit/request": {
|
||||||
"version": "8.4.0",
|
"version": "8.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
|
||||||
"integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==",
|
"integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/endpoint": "^9.0.1",
|
"@octokit/endpoint": "^9.0.6",
|
||||||
"@octokit/request-error": "^5.1.0",
|
"@octokit/request-error": "^5.1.1",
|
||||||
"@octokit/types": "^13.1.0",
|
"@octokit/types": "^13.1.0",
|
||||||
"universal-user-agent": "^6.0.0"
|
"universal-user-agent": "^6.0.0"
|
||||||
},
|
},
|
||||||
@@ -7521,9 +7719,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/request-error": {
|
"node_modules/@octokit/request-error": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
|
||||||
"integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==",
|
"integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/types": "^13.1.0",
|
"@octokit/types": "^13.1.0",
|
||||||
"deprecation": "^2.0.0",
|
"deprecation": "^2.0.0",
|
||||||
@@ -7573,6 +7772,59 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@octokit/rest/node_modules/@octokit/core": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/auth-token": "^4.0.0",
|
||||||
|
"@octokit/graphql": "^7.1.0",
|
||||||
|
"@octokit/request": "^8.4.1",
|
||||||
|
"@octokit/request-error": "^5.1.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"before-after-hook": "^2.2.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/rest/node_modules/@octokit/graphql": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/request": "^8.4.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "24.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||||
|
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/rest/node_modules/@octokit/types": {
|
||||||
|
"version": "13.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@octokit/rest/node_modules/before-after-hook": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@octokit/types": {
|
"node_modules/@octokit/types": {
|
||||||
"version": "12.4.0",
|
"version": "12.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.4.0.tgz",
|
||||||
@@ -9902,17 +10154,6 @@
|
|||||||
"@types/express": "*"
|
"@types/express": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/passport-github": {
|
|
||||||
"version": "1.1.12",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/passport-github/-/passport-github-1.1.12.tgz",
|
|
||||||
"integrity": "sha512-VJpMEIH+cOoXB694QgcxuvWy2wPd1Oq3gqrg2Y9DMVBYs9TmH9L14qnqPDZsNMZKBDH+SvqRsGZj9SgHYeDgcA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/express": "*",
|
|
||||||
"@types/passport": "*",
|
|
||||||
"@types/passport-oauth2": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/passport-google-oauth20": {
|
"node_modules/@types/passport-google-oauth20": {
|
||||||
"version": "2.0.14",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.14.tgz",
|
"resolved": "https://registry.npmjs.org/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.14.tgz",
|
||||||
@@ -11700,9 +11941,11 @@
|
|||||||
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
|
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
|
||||||
},
|
},
|
||||||
"node_modules/before-after-hook": {
|
"node_modules/before-after-hook": {
|
||||||
"version": "2.2.3",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz",
|
||||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
"integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/big-integer": {
|
"node_modules/big-integer": {
|
||||||
"version": "1.6.52",
|
"version": "1.6.52",
|
||||||
@@ -18631,9 +18874,10 @@
|
|||||||
"integrity": "sha512-p1TRH/edngVEHVbwqWnxUViEmq5znDvyB+Sik5cmuLpGOIfDf/39zLiq3swPF8Vakqn+gvNiOQAZu8djYlQILA=="
|
"integrity": "sha512-p1TRH/edngVEHVbwqWnxUViEmq5znDvyB+Sik5cmuLpGOIfDf/39zLiq3swPF8Vakqn+gvNiOQAZu8djYlQILA=="
|
||||||
},
|
},
|
||||||
"node_modules/oauth": {
|
"node_modules/oauth": {
|
||||||
"version": "0.9.15",
|
"version": "0.10.2",
|
||||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz",
|
||||||
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="
|
"integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
@@ -19339,17 +19583,6 @@
|
|||||||
"url": "https://github.com/sponsors/jaredhanson"
|
"url": "https://github.com/sponsors/jaredhanson"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/passport-github": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/passport-github/-/passport-github-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-XARXJycE6fFh/dxF+Uut8OjlwbFEXgbPVj/+V+K7cvriRK7VcAOm+NgBmbiLM9Qv3SSxEAV+V6fIk89nYHXa8A==",
|
|
||||||
"dependencies": {
|
|
||||||
"passport-oauth2": "1.x.x"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/passport-gitlab2": {
|
"node_modules/passport-gitlab2": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/passport-gitlab2/-/passport-gitlab2-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/passport-gitlab2/-/passport-gitlab2-5.0.0.tgz",
|
||||||
@@ -19385,12 +19618,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/passport-oauth2": {
|
"node_modules/passport-oauth2": {
|
||||||
"version": "1.7.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
|
||||||
"integrity": "sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==",
|
"integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64url": "3.x.x",
|
"base64url": "3.x.x",
|
||||||
"oauth": "0.9.x",
|
"oauth": "0.10.x",
|
||||||
"passport-strategy": "1.x.x",
|
"passport-strategy": "1.x.x",
|
||||||
"uid2": "0.0.x",
|
"uid2": "0.0.x",
|
||||||
"utils-merge": "1.x.x"
|
"utils-merge": "1.x.x"
|
||||||
@@ -20179,6 +20413,62 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/probot/node_modules/@octokit/core": {
|
||||||
|
"version": "5.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.1.tgz",
|
||||||
|
"integrity": "sha512-dKYCMuPO1bmrpuogcjQ8z7ICCH3FP6WmxpwC03yjzGfZhj9fTJg6+bS1+UAplekbN2C+M61UNllGOOoAfGCrdQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/auth-token": "^4.0.0",
|
||||||
|
"@octokit/graphql": "^7.1.0",
|
||||||
|
"@octokit/request": "^8.4.1",
|
||||||
|
"@octokit/request-error": "^5.1.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"before-after-hook": "^2.2.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/probot/node_modules/@octokit/core/node_modules/@octokit/types": {
|
||||||
|
"version": "13.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/probot/node_modules/@octokit/graphql": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/request": "^8.4.1",
|
||||||
|
"@octokit/types": "^13.0.0",
|
||||||
|
"universal-user-agent": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/probot/node_modules/@octokit/graphql/node_modules/@octokit/types": {
|
||||||
|
"version": "13.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz",
|
||||||
|
"integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/openapi-types": "^24.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/probot/node_modules/@octokit/openapi-types": {
|
||||||
|
"version": "24.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz",
|
||||||
|
"integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/probot/node_modules/@octokit/plugin-retry": {
|
"node_modules/probot/node_modules/@octokit/plugin-retry": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-6.0.1.tgz",
|
||||||
@@ -20195,6 +20485,28 @@
|
|||||||
"@octokit/core": ">=5"
|
"@octokit/core": ">=5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/probot/node_modules/@octokit/plugin-throttling": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-8.2.0.tgz",
|
||||||
|
"integrity": "sha512-nOpWtLayKFpgqmgD0y3GqXafMFuKcA4tRPZIfu7BArd2lEZeb1988nhWhwx4aZWmjDmUfdgVf7W+Tt4AmvRmMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/types": "^12.2.0",
|
||||||
|
"bottleneck": "^2.15.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@octokit/core": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/probot/node_modules/before-after-hook": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/probot/node_modules/commander": {
|
"node_modules/probot/node_modules/commander": {
|
||||||
"version": "12.1.0",
|
"version": "12.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||||
|
|||||||
@@ -91,7 +91,6 @@
|
|||||||
"@types/lodash.isequal": "^4.5.8",
|
"@types/lodash.isequal": "^4.5.8",
|
||||||
"@types/node": "^20.17.30",
|
"@types/node": "^20.17.30",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/passport-github": "^1.1.12",
|
|
||||||
"@types/passport-google-oauth20": "^2.0.14",
|
"@types/passport-google-oauth20": "^2.0.14",
|
||||||
"@types/pg": "^8.10.9",
|
"@types/pg": "^8.10.9",
|
||||||
"@types/picomatch": "^2.3.3",
|
"@types/picomatch": "^2.3.3",
|
||||||
@@ -150,6 +149,7 @@
|
|||||||
"@infisical/quic": "^1.0.8",
|
"@infisical/quic": "^1.0.8",
|
||||||
"@node-saml/passport-saml": "^5.0.1",
|
"@node-saml/passport-saml": "^5.0.1",
|
||||||
"@octokit/auth-app": "^7.1.1",
|
"@octokit/auth-app": "^7.1.1",
|
||||||
|
"@octokit/plugin-paginate-graphql": "^5.2.4",
|
||||||
"@octokit/plugin-retry": "^5.0.5",
|
"@octokit/plugin-retry": "^5.0.5",
|
||||||
"@octokit/rest": "^20.0.2",
|
"@octokit/rest": "^20.0.2",
|
||||||
"@octokit/webhooks-types": "^7.3.1",
|
"@octokit/webhooks-types": "^7.3.1",
|
||||||
@@ -209,10 +209,10 @@
|
|||||||
"ora": "^7.0.1",
|
"ora": "^7.0.1",
|
||||||
"oracledb": "^6.4.0",
|
"oracledb": "^6.4.0",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"passport-github": "^1.1.0",
|
|
||||||
"passport-gitlab2": "^5.0.0",
|
"passport-gitlab2": "^5.0.0",
|
||||||
"passport-google-oauth20": "^2.0.0",
|
"passport-google-oauth20": "^2.0.0",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
|
"passport-oauth2": "^1.8.0",
|
||||||
"pg": "^8.11.3",
|
"pg": "^8.11.3",
|
||||||
"pg-boss": "^10.1.5",
|
"pg-boss": "^10.1.5",
|
||||||
"pg-query-stream": "^4.5.3",
|
"pg-query-stream": "^4.5.3",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Redis } from "ioredis";
|
|||||||
import { TUsers } from "@app/db/schemas";
|
import { TUsers } from "@app/db/schemas";
|
||||||
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
import { TAccessApprovalPolicyServiceFactory } from "@app/ee/services/access-approval-policy/access-approval-policy-service";
|
||||||
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
import { TAccessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||||
|
import { TAssumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
|
||||||
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { TAuditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
import { TCreateAuditLogDTO } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
import { TAuditLogStreamServiceFactory } from "@app/ee/services/audit-log-stream/audit-log-stream-service";
|
||||||
@@ -14,6 +15,7 @@ import { TDynamicSecretServiceFactory } from "@app/ee/services/dynamic-secret/dy
|
|||||||
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
import { TDynamicSecretLeaseServiceFactory } from "@app/ee/services/dynamic-secret-lease/dynamic-secret-lease-service";
|
||||||
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
import { TExternalKmsServiceFactory } from "@app/ee/services/external-kms/external-kms-service";
|
||||||
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 { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
import { TGroupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
import { TIdentityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
|
||||||
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
import { TIdentityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
|
||||||
@@ -110,12 +112,14 @@ declare module "@fastify/request-context" {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
identityPermissionMetadata?: Record<string, unknown>; // filled by permission service
|
||||||
|
assumedPrivilegeDetails?: { requesterId: string; actorId: string; actorType: ActorType; projectId: string };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
interface Session {
|
interface Session {
|
||||||
callbackPort: string;
|
callbackPort: string;
|
||||||
|
isAdminLogin: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
@@ -139,6 +143,7 @@ declare module "fastify" {
|
|||||||
passportUser: {
|
passportUser: {
|
||||||
isUserCompleted: boolean;
|
isUserCompleted: boolean;
|
||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
|
externalProviderAccessToken?: string;
|
||||||
};
|
};
|
||||||
kmipUser: {
|
kmipUser: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -243,6 +248,8 @@ declare module "fastify" {
|
|||||||
gateway: TGatewayServiceFactory;
|
gateway: TGatewayServiceFactory;
|
||||||
secretRotationV2: TSecretRotationV2ServiceFactory;
|
secretRotationV2: TSecretRotationV2ServiceFactory;
|
||||||
microsoftTeams: TMicrosoftTeamsServiceFactory;
|
microsoftTeams: TMicrosoftTeamsServiceFactory;
|
||||||
|
assumePrivileges: TAssumePrivilegeServiceFactory;
|
||||||
|
githubOrgSync: TGithubOrgSyncServiceFactory;
|
||||||
};
|
};
|
||||||
// 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
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ import {
|
|||||||
TGitAppOrg,
|
TGitAppOrg,
|
||||||
TGitAppOrgInsert,
|
TGitAppOrgInsert,
|
||||||
TGitAppOrgUpdate,
|
TGitAppOrgUpdate,
|
||||||
|
TGithubOrgSyncConfigs,
|
||||||
|
TGithubOrgSyncConfigsInsert,
|
||||||
|
TGithubOrgSyncConfigsUpdate,
|
||||||
TGroupProjectMembershipRoles,
|
TGroupProjectMembershipRoles,
|
||||||
TGroupProjectMembershipRolesInsert,
|
TGroupProjectMembershipRolesInsert,
|
||||||
TGroupProjectMembershipRolesUpdate,
|
TGroupProjectMembershipRolesUpdate,
|
||||||
@@ -433,6 +436,11 @@ import {
|
|||||||
TProjectMicrosoftTeamsConfigsInsert,
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
TProjectMicrosoftTeamsConfigsUpdate
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
} from "@app/db/schemas/project-microsoft-teams-configs";
|
} from "@app/db/schemas/project-microsoft-teams-configs";
|
||||||
|
import {
|
||||||
|
TSecretReminderRecipients,
|
||||||
|
TSecretReminderRecipientsInsert,
|
||||||
|
TSecretReminderRecipientsUpdate
|
||||||
|
} from "@app/db/schemas/secret-reminder-recipients";
|
||||||
|
|
||||||
declare module "knex" {
|
declare module "knex" {
|
||||||
namespace Knex {
|
namespace Knex {
|
||||||
@@ -1014,5 +1022,15 @@ declare module "knex/types/tables" {
|
|||||||
TProjectMicrosoftTeamsConfigsInsert,
|
TProjectMicrosoftTeamsConfigsInsert,
|
||||||
TProjectMicrosoftTeamsConfigsUpdate
|
TProjectMicrosoftTeamsConfigsUpdate
|
||||||
>;
|
>;
|
||||||
|
[TableName.SecretReminderRecipients]: KnexOriginal.CompositeTableType<
|
||||||
|
TSecretReminderRecipients,
|
||||||
|
TSecretReminderRecipientsInsert,
|
||||||
|
TSecretReminderRecipientsUpdate
|
||||||
|
>;
|
||||||
|
[TableName.GithubOrgSyncConfig]: KnexOriginal.CompositeTableType<
|
||||||
|
TGithubOrgSyncConfigs,
|
||||||
|
TGithubOrgSyncConfigsInsert,
|
||||||
|
TGithubOrgSyncConfigsUpdate
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
if (!hasSecretReminderRecipientsTable) {
|
||||||
|
await knex.schema.createTable(TableName.SecretReminderRecipients, (table) => {
|
||||||
|
table.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
table.timestamps(true, true, true);
|
||||||
|
table.uuid("secretId").notNullable();
|
||||||
|
table.uuid("userId").notNullable();
|
||||||
|
table.string("projectId").notNullable();
|
||||||
|
|
||||||
|
// Based on userId rather than project membership ID so we can easily extend group support in the future if need be.
|
||||||
|
// This does however mean we need to manually clean up once a user is removed from a project.
|
||||||
|
table.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
|
table.foreign("secretId").references("id").inTable(TableName.SecretV2).onDelete("CASCADE");
|
||||||
|
table.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
|
||||||
|
|
||||||
|
table.index("secretId");
|
||||||
|
table.unique(["secretId", "userId"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasSecretReminderRecipientsTable = await knex.schema.hasTable(TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
if (hasSecretReminderRecipientsTable) {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.SecretReminderRecipients);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasAliasColumn = await knex.schema.hasColumn(TableName.SshHost, "alias");
|
||||||
|
if (!hasAliasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SshHost, (t) => {
|
||||||
|
t.string("alias").nullable();
|
||||||
|
t.unique(["projectId", "alias"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasAliasColumn = await knex.schema.hasColumn(TableName.SshHost, "alias");
|
||||||
|
if (hasAliasColumn) {
|
||||||
|
await knex.schema.alterTable(TableName.SshHost, (t) => {
|
||||||
|
t.dropUnique(["projectId", "alias"]);
|
||||||
|
t.dropColumn("alias");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasTable = await knex.schema.hasTable(TableName.GithubOrgSyncConfig);
|
||||||
|
if (!hasTable) {
|
||||||
|
await knex.schema.createTable(TableName.GithubOrgSyncConfig, (t) => {
|
||||||
|
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
|
t.string("githubOrgName").notNullable();
|
||||||
|
t.boolean("isActive").defaultTo(false);
|
||||||
|
t.binary("encryptedGithubOrgAccessToken");
|
||||||
|
t.uuid("orgId").notNullable().unique();
|
||||||
|
t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
|
t.timestamps(true, true, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await createOnUpdateTrigger(knex, TableName.GithubOrgSyncConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.dropTableIfExists(TableName.GithubOrgSyncConfig);
|
||||||
|
await dropOnUpdateTrigger(knex, TableName.GithubOrgSyncConfig);
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ export const CertificatesSchema = z.object({
|
|||||||
notAfter: z.date(),
|
notAfter: z.date(),
|
||||||
revokedAt: z.date().nullable().optional(),
|
revokedAt: z.date().nullable().optional(),
|
||||||
revocationReason: z.number().nullable().optional(),
|
revocationReason: z.number().nullable().optional(),
|
||||||
altNames: z.string().default("").nullable().optional(),
|
altNames: z.string().nullable().optional(),
|
||||||
caCertId: z.string().uuid(),
|
caCertId: z.string().uuid(),
|
||||||
certificateTemplateId: z.string().uuid().nullable().optional(),
|
certificateTemplateId: z.string().uuid().nullable().optional(),
|
||||||
keyUsages: z.string().array().nullable().optional(),
|
keyUsages: z.string().array().nullable().optional(),
|
||||||
|
|||||||
@@ -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 GithubOrgSyncConfigsSchema = z.object({
|
||||||
|
id: z.string().uuid(),
|
||||||
|
githubOrgName: z.string(),
|
||||||
|
isActive: z.boolean().default(false).nullable().optional(),
|
||||||
|
encryptedGithubOrgAccessToken: zodBuffer.nullable().optional(),
|
||||||
|
orgId: z.string().uuid(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type TGithubOrgSyncConfigs = z.infer<typeof GithubOrgSyncConfigsSchema>;
|
||||||
|
export type TGithubOrgSyncConfigsInsert = Omit<z.input<typeof GithubOrgSyncConfigsSchema>, TImmutableDBKeys>;
|
||||||
|
export type TGithubOrgSyncConfigsUpdate = Partial<Omit<z.input<typeof GithubOrgSyncConfigsSchema>, TImmutableDBKeys>>;
|
||||||
@@ -25,6 +25,7 @@ export * from "./external-kms";
|
|||||||
export * from "./gateways";
|
export * from "./gateways";
|
||||||
export * from "./git-app-install-sessions";
|
export * from "./git-app-install-sessions";
|
||||||
export * from "./git-app-org";
|
export * from "./git-app-org";
|
||||||
|
export * from "./github-org-sync-configs";
|
||||||
export * from "./group-project-membership-roles";
|
export * from "./group-project-membership-roles";
|
||||||
export * from "./group-project-memberships";
|
export * from "./group-project-memberships";
|
||||||
export * from "./groups";
|
export * from "./groups";
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const KmipOrgServerCertificatesSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
commonName: z.string(),
|
commonName: z.string(),
|
||||||
altNames: z.string(),
|
altNames: z.string().nullable().optional(),
|
||||||
serialNumber: z.string(),
|
serialNumber: z.string(),
|
||||||
keyAlgorithm: z.string(),
|
keyAlgorithm: z.string(),
|
||||||
issuedAt: z.date(),
|
issuedAt: z.date(),
|
||||||
|
|||||||
@@ -148,7 +148,9 @@ export enum TableName {
|
|||||||
SecretRotationV2 = "secret_rotations_v2",
|
SecretRotationV2 = "secret_rotations_v2",
|
||||||
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
|
||||||
MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
|
MicrosoftTeamsIntegrations = "microsoft_teams_integrations",
|
||||||
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs"
|
ProjectMicrosoftTeamsConfigs = "project_microsoft_teams_configs",
|
||||||
|
SecretReminderRecipients = "secret_reminder_recipients",
|
||||||
|
GithubOrgSyncConfig = "github_org_sync_configs"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const OidcConfigsSchema = z.object({
|
|||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
orgId: z.string().uuid(),
|
orgId: z.string().uuid(),
|
||||||
lastUsed: z.date().nullable().optional(),
|
lastUsed: z.date().nullable().optional(),
|
||||||
|
manageGroupMemberships: z.boolean().default(false),
|
||||||
encryptedOidcClientId: zodBuffer,
|
encryptedOidcClientId: zodBuffer,
|
||||||
encryptedOidcClientSecret: zodBuffer,
|
encryptedOidcClientSecret: zodBuffer,
|
||||||
jwtSignatureAlgorithm: z.string().default("RS256")
|
jwtSignatureAlgorithm: z.string().default("RS256")
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export const OrganizationsSchema = z.object({
|
|||||||
defaultMembershipRole: z.string().default("member"),
|
defaultMembershipRole: z.string().default("member"),
|
||||||
enforceMfa: z.boolean().default(false),
|
enforceMfa: z.boolean().default(false),
|
||||||
selectedMfaMethod: z.string().nullable().optional(),
|
selectedMfaMethod: z.string().nullable().optional(),
|
||||||
|
secretShareSendToAnyone: z.boolean().default(true).nullable().optional(),
|
||||||
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
allowSecretSharingOutsideOrganization: z.boolean().default(true).nullable().optional(),
|
||||||
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
||||||
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export const SshHostsSchema = z.object({
|
|||||||
userCertTtl: z.string(),
|
userCertTtl: z.string(),
|
||||||
hostCertTtl: z.string(),
|
hostCertTtl: z.string(),
|
||||||
userSshCaId: z.string().uuid(),
|
userSshCaId: z.string().uuid(),
|
||||||
hostSshCaId: z.string().uuid()
|
hostSshCaId: z.string().uuid(),
|
||||||
|
alias: z.string().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TSshHosts = z.infer<typeof SshHostsSchema>;
|
export type TSshHosts = z.infer<typeof SshHostsSchema>;
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const registerAssumePrivilegeRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/:projectId/assume-privileges",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
actorType: z.enum([ActorType.USER, ActorType.IDENTITY]),
|
||||||
|
actorId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req, res) => {
|
||||||
|
if (req.auth.authMode === AuthMode.JWT) {
|
||||||
|
const payload = await server.services.assumePrivileges.assumeProjectPrivileges({
|
||||||
|
targetActorType: req.body.actorType,
|
||||||
|
targetActorId: req.body.actorId,
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
actorPermissionDetails: req.permission,
|
||||||
|
tokenVersionId: req.auth.tokenVersionId
|
||||||
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
void res.setCookie("infisical-project-assume-privileges", payload.assumePrivilegesToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 3600 // 1 hour in seconds
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START,
|
||||||
|
metadata: {
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
requesterEmail: req.auth.user.username,
|
||||||
|
requesterId: req.auth.user.id,
|
||||||
|
targetActorType: req.body.actorType,
|
||||||
|
targetActorId: req.body.actorId,
|
||||||
|
duration: "1hr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { message: "Successfully assumed role" };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "Invalid auth mode" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "DELETE",
|
||||||
|
url: "/:projectId/assume-privileges",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
params: z.object({
|
||||||
|
projectId: z.string()
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
message: z.string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req, res) => {
|
||||||
|
const assumedPrivilegeDetails = requestContext.get("assumedPrivilegeDetails");
|
||||||
|
if (req.auth.authMode === AuthMode.JWT && assumedPrivilegeDetails) {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
void res.setCookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
expires: new Date(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
orgId: req.permission.orgId,
|
||||||
|
event: {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END,
|
||||||
|
metadata: {
|
||||||
|
projectId: req.params.projectId,
|
||||||
|
requesterEmail: req.auth.user.username,
|
||||||
|
requesterId: req.auth.user.id,
|
||||||
|
targetActorId: assumedPrivilegeDetails.actorId,
|
||||||
|
targetActorType: assumedPrivilegeDetails.actorType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { message: "Successfully exited assumed role" };
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "Invalid auth mode" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { GithubOrgSyncConfigsSchema } from "@app/db/schemas";
|
||||||
|
import { CharacterType, zodValidateCharacters } from "@app/lib/validator/validate-string";
|
||||||
|
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 SanitizedGithubOrgSyncSchema = GithubOrgSyncConfigsSchema.pick({
|
||||||
|
isActive: true,
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
orgId: true,
|
||||||
|
githubOrgName: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const githubOrgNameValidator = zodValidateCharacters([CharacterType.AlphaNumeric, CharacterType.Hyphen]);
|
||||||
|
export const registerGithubOrgSyncRouter = async (server: FastifyZodProvider) => {
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "POST",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z.object({
|
||||||
|
githubOrgName: githubOrgNameValidator(z.string().trim(), "GitHub Org Name"),
|
||||||
|
githubOrgAccessToken: z.string().trim().max(1000).optional(),
|
||||||
|
isActive: z.boolean().default(false)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.createGithubOrgSync({
|
||||||
|
orgPermission: req.permission,
|
||||||
|
githubOrgName: req.body.githubOrgName,
|
||||||
|
githubOrgAccessToken: req.body.githubOrgAccessToken,
|
||||||
|
isActive: req.body.isActive
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "PATCH",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
body: z
|
||||||
|
.object({
|
||||||
|
githubOrgName: githubOrgNameValidator(z.string().trim(), "GitHub Org Name"),
|
||||||
|
githubOrgAccessToken: z.string().trim().max(1000),
|
||||||
|
isActive: z.boolean()
|
||||||
|
})
|
||||||
|
.partial(),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.updateGithubOrgSync({
|
||||||
|
orgPermission: req.permission,
|
||||||
|
githubOrgName: req.body.githubOrgName,
|
||||||
|
githubOrgAccessToken: req.body.githubOrgAccessToken,
|
||||||
|
isActive: req.body.isActive
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "DELETE",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.deleteGithubOrgSync({
|
||||||
|
orgPermission: req.permission
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
url: "/",
|
||||||
|
method: "GET",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
githubOrgSyncConfig: SanitizedGithubOrgSyncSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const githubOrgSyncConfig = await server.services.githubOrgSync.getGithubOrgSync({
|
||||||
|
orgPermission: req.permission
|
||||||
|
});
|
||||||
|
|
||||||
|
return { githubOrgSyncConfig };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -2,12 +2,14 @@ import { registerProjectTemplateRouter } from "@app/ee/routes/v1/project-templat
|
|||||||
|
|
||||||
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
|
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
|
||||||
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
|
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
|
||||||
|
import { registerAssumePrivilegeRouter } from "./assume-privilege-router";
|
||||||
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
import { registerAuditLogStreamRouter } from "./audit-log-stream-router";
|
||||||
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
import { registerCaCrlRouter } from "./certificate-authority-crl-router";
|
||||||
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
import { registerDynamicSecretLeaseRouter } from "./dynamic-secret-lease-router";
|
||||||
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
import { registerDynamicSecretRouter } from "./dynamic-secret-router";
|
||||||
import { registerExternalKmsRouter } from "./external-kms-router";
|
import { registerExternalKmsRouter } from "./external-kms-router";
|
||||||
import { registerGatewayRouter } from "./gateway-router";
|
import { registerGatewayRouter } from "./gateway-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 { registerKmipRouter } from "./kmip-router";
|
import { registerKmipRouter } from "./kmip-router";
|
||||||
@@ -45,6 +47,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
await projectRouter.register(registerProjectRoleRouter);
|
await projectRouter.register(registerProjectRoleRouter);
|
||||||
await projectRouter.register(registerProjectRouter);
|
await projectRouter.register(registerProjectRouter);
|
||||||
await projectRouter.register(registerTrustedIpRouter);
|
await projectRouter.register(registerTrustedIpRouter);
|
||||||
|
await projectRouter.register(registerAssumePrivilegeRouter);
|
||||||
},
|
},
|
||||||
{ prefix: "/workspace" }
|
{ prefix: "/workspace" }
|
||||||
);
|
);
|
||||||
@@ -70,6 +73,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await server.register(registerGatewayRouter, { prefix: "/gateways" });
|
await server.register(registerGatewayRouter, { prefix: "/gateways" });
|
||||||
|
await server.register(registerGithubOrgSyncRouter, { prefix: "/github-org-sync-config" });
|
||||||
|
|
||||||
await server.register(
|
await server.register(
|
||||||
async (pkiRouter) => {
|
async (pkiRouter) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { packRules } from "@casl/ability/extra";
|
import { packRules } from "@casl/ability/extra";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectMembershipRole, ProjectMembershipsSchema, ProjectRolesSchema } from "@app/db/schemas";
|
import { ProjectMembershipRole, ProjectRolesSchema } from "@app/db/schemas";
|
||||||
import {
|
import {
|
||||||
backfillPermissionV1SchemaToV2Schema,
|
backfillPermissionV1SchemaToV2Schema,
|
||||||
ProjectPermissionV1Schema
|
ProjectPermissionV1Schema
|
||||||
@@ -245,13 +245,22 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
data: z.object({
|
data: z.object({
|
||||||
membership: ProjectMembershipsSchema.extend({
|
membership: z.object({
|
||||||
|
id: z.string(),
|
||||||
roles: z
|
roles: z
|
||||||
.object({
|
.object({
|
||||||
role: z.string()
|
role: z.string()
|
||||||
})
|
})
|
||||||
.array()
|
.array()
|
||||||
}),
|
}),
|
||||||
|
assumedPrivilegeDetails: z
|
||||||
|
.object({
|
||||||
|
actorId: z.string(),
|
||||||
|
actorType: z.string(),
|
||||||
|
actorName: z.string(),
|
||||||
|
actorEmail: z.string().optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
permissions: z.any().array()
|
permissions: z.any().array()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -259,14 +268,20 @@ export const registerProjectRoleRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT]),
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { permissions, membership } = await server.services.projectRole.getUserPermission(
|
const { permissions, membership, assumedPrivilegeDetails } = await server.services.projectRole.getUserPermission(
|
||||||
req.permission.id,
|
req.permission.id,
|
||||||
req.params.projectId,
|
req.params.projectId,
|
||||||
req.permission.authMethod,
|
req.permission.authMethod,
|
||||||
req.permission.orgId
|
req.permission.orgId
|
||||||
);
|
);
|
||||||
|
|
||||||
return { data: { permissions, membership } };
|
return {
|
||||||
|
data: {
|
||||||
|
permissions,
|
||||||
|
membership,
|
||||||
|
assumedPrivilegeDetails
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { isValidHostname } from "@app/ee/services/ssh-host/ssh-host-validators";
|
|||||||
import { SSH_HOSTS } from "@app/lib/api-docs";
|
import { SSH_HOSTS } from "@app/lib/api-docs";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { publicSshCaLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { publicSshCaLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
|
import { slugSchema } from "@app/server/lib/schemas";
|
||||||
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
@@ -96,10 +97,12 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
hostname: z
|
hostname: z
|
||||||
.string()
|
.string()
|
||||||
.min(1)
|
.min(1)
|
||||||
|
.trim()
|
||||||
.refine((v) => isValidHostname(v), {
|
.refine((v) => isValidHostname(v), {
|
||||||
message: "Hostname must be a valid hostname"
|
message: "Hostname must be a valid hostname"
|
||||||
})
|
})
|
||||||
.describe(SSH_HOSTS.CREATE.hostname),
|
.describe(SSH_HOSTS.CREATE.hostname),
|
||||||
|
alias: slugSchema({ min: 0, max: 64, field: "alias" }).describe(SSH_HOSTS.CREATE.alias).default(""),
|
||||||
userCertTtl: z
|
userCertTtl: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
@@ -138,6 +141,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
sshHostId: host.id,
|
sshHostId: host.id,
|
||||||
hostname: host.hostname,
|
hostname: host.hostname,
|
||||||
|
alias: host.alias ?? null,
|
||||||
userCertTtl: host.userCertTtl,
|
userCertTtl: host.userCertTtl,
|
||||||
hostCertTtl: host.hostCertTtl,
|
hostCertTtl: host.hostCertTtl,
|
||||||
loginMappings: host.loginMappings,
|
loginMappings: host.loginMappings,
|
||||||
@@ -166,12 +170,14 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
hostname: z
|
hostname: z
|
||||||
.string()
|
.string()
|
||||||
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
.refine((v) => isValidHostname(v), {
|
.refine((v) => isValidHostname(v), {
|
||||||
message: "Hostname must be a valid hostname"
|
message: "Hostname must be a valid hostname"
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.describe(SSH_HOSTS.UPDATE.hostname),
|
.describe(SSH_HOSTS.UPDATE.hostname),
|
||||||
|
alias: slugSchema({ min: 0, max: 64, field: "alias" }).describe(SSH_HOSTS.UPDATE.alias).optional(),
|
||||||
userCertTtl: z
|
userCertTtl: z
|
||||||
.string()
|
.string()
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||||
@@ -208,6 +214,7 @@ export const registerSshHostRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
sshHostId: host.id,
|
sshHostId: host.id,
|
||||||
hostname: host.hostname,
|
hostname: host.hostname,
|
||||||
|
alias: host.alias,
|
||||||
userCertTtl: host.userCertTtl,
|
userCertTtl: host.userCertTtl,
|
||||||
hostCertTtl: host.hostCertTtl,
|
hostCertTtl: host.hostCertTtl,
|
||||||
loginMappings: host.loginMappings,
|
loginMappings: host.loginMappings,
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import {
|
||||||
|
ProjectPermissionIdentityActions,
|
||||||
|
ProjectPermissionMemberActions,
|
||||||
|
ProjectPermissionSub
|
||||||
|
} from "../permission/project-permission";
|
||||||
|
import { TAssumeProjectPrivilegeDTO } from "./assume-privilege-types";
|
||||||
|
|
||||||
|
type TAssumePrivilegeServiceFactoryDep = {
|
||||||
|
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TAssumePrivilegeServiceFactory = ReturnType<typeof assumePrivilegeServiceFactory>;
|
||||||
|
|
||||||
|
export const assumePrivilegeServiceFactory = ({ projectDAL, permissionService }: TAssumePrivilegeServiceFactoryDep) => {
|
||||||
|
const assumeProjectPrivileges = async ({
|
||||||
|
targetActorType,
|
||||||
|
targetActorId,
|
||||||
|
projectId,
|
||||||
|
actorPermissionDetails,
|
||||||
|
tokenVersionId
|
||||||
|
}: TAssumeProjectPrivilegeDTO) => {
|
||||||
|
const project = await projectDAL.findById(projectId);
|
||||||
|
if (!project) throw new NotFoundError({ message: `Project with ID '${projectId}' not found` });
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor: actorPermissionDetails.type,
|
||||||
|
actorId: actorPermissionDetails.id,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||||
|
actorOrgId: actorPermissionDetails.orgId,
|
||||||
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetActorType === ActorType.USER) {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionMemberActions.AssumePrivileges,
|
||||||
|
ProjectPermissionSub.Member
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionIdentityActions.AssumePrivileges,
|
||||||
|
ProjectPermissionSub.Identity
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check entity is part of project
|
||||||
|
await permissionService.getProjectPermission({
|
||||||
|
actor: targetActorType,
|
||||||
|
actorId: targetActorId,
|
||||||
|
projectId,
|
||||||
|
actorAuthMethod: actorPermissionDetails.authMethod,
|
||||||
|
actorOrgId: actorPermissionDetails.orgId,
|
||||||
|
actionProjectType: ActionProjectType.Any
|
||||||
|
});
|
||||||
|
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const assumePrivilegesToken = jwt.sign(
|
||||||
|
{
|
||||||
|
tokenVersionId,
|
||||||
|
actorType: targetActorType,
|
||||||
|
actorId: targetActorId,
|
||||||
|
projectId,
|
||||||
|
requesterId: actorPermissionDetails.id
|
||||||
|
},
|
||||||
|
appCfg.AUTH_SECRET,
|
||||||
|
{ expiresIn: "1hr" }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { actorType: targetActorType, actorId: targetActorId, projectId, assumePrivilegesToken };
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyAssumePrivilegeToken = (token: string, tokenVersionId: string) => {
|
||||||
|
const appCfg = getConfig();
|
||||||
|
const decodedToken = jwt.verify(token, appCfg.AUTH_SECRET) as {
|
||||||
|
tokenVersionId: string;
|
||||||
|
projectId: string;
|
||||||
|
requesterId: string;
|
||||||
|
actorType: ActorType;
|
||||||
|
actorId: string;
|
||||||
|
};
|
||||||
|
if (decodedToken.tokenVersionId !== tokenVersionId) {
|
||||||
|
throw new ForbiddenRequestError({ message: "Invalid token version" });
|
||||||
|
}
|
||||||
|
return decodedToken;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
assumeProjectPrivileges,
|
||||||
|
verifyAssumePrivilegeToken
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
import { ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export type TAssumeProjectPrivilegeDTO = {
|
||||||
|
targetActorType: ActorType.USER | ActorType.IDENTITY;
|
||||||
|
targetActorId: string;
|
||||||
|
projectId: string;
|
||||||
|
tokenVersionId: string;
|
||||||
|
actorPermissionDetails: OrgServiceActor;
|
||||||
|
};
|
||||||
@@ -332,7 +332,10 @@ export enum EventType {
|
|||||||
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS = "microsoft-teams-workflow-integration-check-installation-status",
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_CHECK_INSTALLATION_STATUS = "microsoft-teams-workflow-integration-check-installation-status",
|
||||||
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS = "microsoft-teams-workflow-integration-get-teams",
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET_TEAMS = "microsoft-teams-workflow-integration-get-teams",
|
||||||
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get",
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_GET = "microsoft-teams-workflow-integration-get",
|
||||||
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list"
|
MICROSOFT_TEAMS_WORKFLOW_INTEGRATION_LIST = "microsoft-teams-workflow-integration-list",
|
||||||
|
|
||||||
|
PROJECT_ASSUME_PRIVILEGE_SESSION_START = "project-assume-privileges-session-start",
|
||||||
|
PROJECT_ASSUME_PRIVILEGE_SESSION_END = "project-assume-privileges-session-end"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterableSecretEvents: EventType[] = [
|
export const filterableSecretEvents: EventType[] = [
|
||||||
@@ -1506,6 +1509,7 @@ interface CreateSshHost {
|
|||||||
metadata: {
|
metadata: {
|
||||||
sshHostId: string;
|
sshHostId: string;
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
alias: string | null;
|
||||||
userCertTtl: string;
|
userCertTtl: string;
|
||||||
hostCertTtl: string;
|
hostCertTtl: string;
|
||||||
loginMappings: {
|
loginMappings: {
|
||||||
@@ -1524,6 +1528,7 @@ interface UpdateSshHost {
|
|||||||
metadata: {
|
metadata: {
|
||||||
sshHostId: string;
|
sshHostId: string;
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
|
alias?: string | null;
|
||||||
userCertTtl?: string;
|
userCertTtl?: string;
|
||||||
hostCertTtl?: string;
|
hostCertTtl?: string;
|
||||||
loginMappings?: {
|
loginMappings?: {
|
||||||
@@ -2466,6 +2471,29 @@ interface ProjectAccessRequestEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProjectAssumePrivilegesEvent {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_START;
|
||||||
|
metadata: {
|
||||||
|
projectId: string;
|
||||||
|
requesterId: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
targetActorType: ActorType;
|
||||||
|
targetActorId: string;
|
||||||
|
duration: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectAssumePrivilegesExitEvent {
|
||||||
|
type: EventType.PROJECT_ASSUME_PRIVILEGE_SESSION_END;
|
||||||
|
metadata: {
|
||||||
|
projectId: string;
|
||||||
|
requesterId: string;
|
||||||
|
requesterEmail: string;
|
||||||
|
targetActorType: ActorType;
|
||||||
|
targetActorId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface SetupKmipEvent {
|
interface SetupKmipEvent {
|
||||||
type: EventType.SETUP_KMIP;
|
type: EventType.SETUP_KMIP;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -2831,6 +2859,8 @@ export type Event =
|
|||||||
| KmipOperationLocateEvent
|
| KmipOperationLocateEvent
|
||||||
| KmipOperationRegisterEvent
|
| KmipOperationRegisterEvent
|
||||||
| ProjectAccessRequestEvent
|
| ProjectAccessRequestEvent
|
||||||
|
| ProjectAssumePrivilegesEvent
|
||||||
|
| ProjectAssumePrivilegesExitEvent
|
||||||
| CreateSecretRequestEvent
|
| CreateSecretRequestEvent
|
||||||
| SecretApprovalRequestReview
|
| SecretApprovalRequestReview
|
||||||
| GetSecretRotationsEvent
|
| GetSecretRotationsEvent
|
||||||
|
|||||||
@@ -83,18 +83,26 @@ export const externalKmsServiceFactory = ({
|
|||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
|
||||||
// if missing kms key this generate a new kms key id and returns new provider input
|
try {
|
||||||
const newProviderInput = await externalKms.generateInputKmsKey();
|
// if missing kms key this generate a new kms key id and returns new provider input
|
||||||
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
const newProviderInput = await externalKms.generateInputKmsKey();
|
||||||
|
sanitizedProviderInput = JSON.stringify(newProviderInput);
|
||||||
|
|
||||||
await externalKms.validateConnection();
|
await externalKms.validateConnection();
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KmsProviders.Gcp:
|
case KmsProviders.Gcp:
|
||||||
{
|
{
|
||||||
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
|
const externalKms = await GcpKmsProviderFactory({ inputs: provider.inputs });
|
||||||
await externalKms.validateConnection();
|
try {
|
||||||
sanitizedProviderInput = JSON.stringify(provider.inputs);
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(provider.inputs);
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -186,8 +194,12 @@ export const externalKmsServiceFactory = ({
|
|||||||
);
|
);
|
||||||
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
||||||
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
const externalKms = await AwsKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
await externalKms.validateConnection();
|
try {
|
||||||
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case KmsProviders.Gcp:
|
case KmsProviders.Gcp:
|
||||||
@@ -197,8 +209,12 @@ export const externalKmsServiceFactory = ({
|
|||||||
);
|
);
|
||||||
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
const updatedProviderInput = { ...decryptedProviderInput, ...provider.inputs };
|
||||||
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
|
const externalKms = await GcpKmsProviderFactory({ inputs: updatedProviderInput });
|
||||||
await externalKms.validateConnection();
|
try {
|
||||||
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
await externalKms.validateConnection();
|
||||||
|
sanitizedProviderInput = JSON.stringify(updatedProviderInput);
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -368,7 +384,11 @@ export const externalKmsServiceFactory = ({
|
|||||||
|
|
||||||
const fetchGcpKeys = async ({ credential, gcpRegion }: Pick<TExternalKmsGcpSchema, "credential" | "gcpRegion">) => {
|
const fetchGcpKeys = async ({ credential, gcpRegion }: Pick<TExternalKmsGcpSchema, "credential" | "gcpRegion">) => {
|
||||||
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
|
const externalKms = await GcpKmsProviderFactory({ inputs: { credential, gcpRegion, keyName: "" } });
|
||||||
return externalKms.getKeysList();
|
try {
|
||||||
|
return await externalKms.getKeysList();
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -102,10 +102,19 @@ export const AwsKmsProviderFactory = async ({ inputs }: AwsKmsProviderArgs): Pro
|
|||||||
return { data: Buffer.from(decryptionCommand.Plaintext) };
|
return { data: Buffer.from(decryptionCommand.Plaintext) };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanup = async () => {
|
||||||
|
try {
|
||||||
|
awsClient.destroy();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Failed to cleanup AWS KMS client", { cause: error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
generateInputKmsKey,
|
generateInputKmsKey,
|
||||||
validateConnection,
|
validateConnection,
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt
|
decrypt,
|
||||||
|
cleanup
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Pro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const cleanup = async () => {
|
||||||
|
try {
|
||||||
|
await gcpKmsClient.close();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error("Failed to cleanup GCP KMS client", { cause: error });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Used when adding the KMS to fetch the list of keys in specified region
|
// Used when adding the KMS to fetch the list of keys in specified region
|
||||||
const getKeysList = async () => {
|
const getKeysList = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -108,6 +116,7 @@ export const GcpKmsProviderFactory = async ({ inputs }: GcpKmsProviderArgs): Pro
|
|||||||
validateConnection,
|
validateConnection,
|
||||||
getKeysList,
|
getKeysList,
|
||||||
encrypt,
|
encrypt,
|
||||||
decrypt
|
decrypt,
|
||||||
|
cleanup
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,4 +98,5 @@ export type TExternalKmsProviderFns = {
|
|||||||
validateConnection: () => Promise<boolean>;
|
validateConnection: () => Promise<boolean>;
|
||||||
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
|
encrypt: (data: Buffer) => Promise<{ encryptedBlob: Buffer }>;
|
||||||
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
|
decrypt: (encryptedBlob: Buffer) => Promise<{ data: Buffer }>;
|
||||||
|
cleanup: () => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TGithubOrgSyncDALFactory = ReturnType<typeof githubOrgSyncDALFactory>;
|
||||||
|
|
||||||
|
export const githubOrgSyncDALFactory = (db: TDbClient) => {
|
||||||
|
const orm = ormify(db, TableName.GithubOrgSyncConfig);
|
||||||
|
return orm;
|
||||||
|
};
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { Octokit } from "@octokit/core";
|
||||||
|
import { paginateGraphQL } from "@octokit/plugin-paginate-graphql";
|
||||||
|
import { Octokit as OctokitRest } from "@octokit/rest";
|
||||||
|
|
||||||
|
import { OrgMembershipRole } from "@app/db/schemas";
|
||||||
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { groupBy } from "@app/lib/fn";
|
||||||
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
||||||
|
|
||||||
|
import { TGroupDALFactory } from "../group/group-dal";
|
||||||
|
import { TUserGroupMembershipDALFactory } from "../group/user-group-membership-dal";
|
||||||
|
import { TLicenseServiceFactory } from "../license/license-service";
|
||||||
|
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||||
|
import { TPermissionServiceFactory } from "../permission/permission-service";
|
||||||
|
import { TGithubOrgSyncDALFactory } from "./github-org-sync-dal";
|
||||||
|
import { TCreateGithubOrgSyncDTO, TDeleteGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./github-org-sync-types";
|
||||||
|
|
||||||
|
const OctokitWithPlugin = Octokit.plugin(paginateGraphQL);
|
||||||
|
|
||||||
|
type TGithubOrgSyncServiceFactoryDep = {
|
||||||
|
githubOrgSyncDAL: TGithubOrgSyncDALFactory;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
userGroupMembershipDAL: Pick<
|
||||||
|
TUserGroupMembershipDALFactory,
|
||||||
|
"findGroupMembershipsByUserIdInOrg" | "insertMany" | "delete"
|
||||||
|
>;
|
||||||
|
groupDAL: Pick<TGroupDALFactory, "insertMany" | "transaction" | "find">;
|
||||||
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TGithubOrgSyncServiceFactory = ReturnType<typeof githubOrgSyncServiceFactory>;
|
||||||
|
|
||||||
|
export const githubOrgSyncServiceFactory = ({
|
||||||
|
githubOrgSyncDAL,
|
||||||
|
permissionService,
|
||||||
|
kmsService,
|
||||||
|
userGroupMembershipDAL,
|
||||||
|
groupDAL,
|
||||||
|
licenseService
|
||||||
|
}: TGithubOrgSyncServiceFactoryDep) => {
|
||||||
|
const createGithubOrgSync = async ({
|
||||||
|
githubOrgName,
|
||||||
|
orgPermission,
|
||||||
|
githubOrgAccessToken,
|
||||||
|
isActive
|
||||||
|
}: TCreateGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
const plan = await licenseService.getPlan(orgPermission.orgId);
|
||||||
|
if (!plan.githubOrgSync) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to create github organization team sync due to plan restriction. Upgrade plan to create github organization sync."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (existingConfig)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Organization ${orgPermission.orgId} already has GitHub Organization sync config.`
|
||||||
|
});
|
||||||
|
|
||||||
|
const octokit = new OctokitRest({
|
||||||
|
auth: githubOrgAccessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data } = await octokit.rest.orgs.get({
|
||||||
|
org: githubOrgName
|
||||||
|
});
|
||||||
|
if (data.login.toLowerCase() !== githubOrgName.toLowerCase())
|
||||||
|
throw new BadRequestError({ message: "Invalid GitHub organisation" });
|
||||||
|
|
||||||
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: orgPermission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await githubOrgSyncDAL.create({
|
||||||
|
orgId: orgPermission.orgId,
|
||||||
|
githubOrgName,
|
||||||
|
isActive,
|
||||||
|
encryptedGithubOrgAccessToken: githubOrgAccessToken
|
||||||
|
? encryptor({ plainText: Buffer.from(githubOrgAccessToken) }).cipherTextBlob
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateGithubOrgSync = async ({
|
||||||
|
githubOrgName,
|
||||||
|
orgPermission,
|
||||||
|
githubOrgAccessToken,
|
||||||
|
isActive
|
||||||
|
}: TUpdateGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
const plan = await licenseService.getPlan(orgPermission.orgId);
|
||||||
|
if (!plan.githubOrgSync) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to update github organization team sync due to plan restriction. Upgrade plan to update github organization sync."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!existingConfig)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Organization ${orgPermission.orgId} GitHub organization sync config missing.`
|
||||||
|
});
|
||||||
|
|
||||||
|
const { encryptor, decryptor } = await kmsService.createCipherPairWithDataKey({
|
||||||
|
type: KmsDataKey.Organization,
|
||||||
|
orgId: orgPermission.orgId
|
||||||
|
});
|
||||||
|
const newData = {
|
||||||
|
githubOrgName: githubOrgName || existingConfig.githubOrgName,
|
||||||
|
githubOrgAccessToken:
|
||||||
|
githubOrgAccessToken ||
|
||||||
|
(existingConfig.encryptedGithubOrgAccessToken
|
||||||
|
? decryptor({ cipherTextBlob: existingConfig.encryptedGithubOrgAccessToken }).toString()
|
||||||
|
: null)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (githubOrgName || githubOrgAccessToken) {
|
||||||
|
const octokit = new OctokitRest({
|
||||||
|
auth: newData.githubOrgAccessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data } = await octokit.rest.orgs.get({
|
||||||
|
org: newData.githubOrgName
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.login.toLowerCase() !== newData.githubOrgName.toLowerCase())
|
||||||
|
throw new BadRequestError({ message: "Invalid GitHub organisation" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await githubOrgSyncDAL.updateById(existingConfig.id, {
|
||||||
|
orgId: orgPermission.orgId,
|
||||||
|
githubOrgName: newData.githubOrgName,
|
||||||
|
isActive,
|
||||||
|
encryptedGithubOrgAccessToken: newData.githubOrgAccessToken
|
||||||
|
? encryptor({ plainText: Buffer.from(newData.githubOrgAccessToken) }).cipherTextBlob
|
||||||
|
: null
|
||||||
|
});
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
|
||||||
|
const plan = await licenseService.getPlan(orgPermission.orgId);
|
||||||
|
if (!plan.githubOrgSync) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Failed to delete github organization team sync due to plan restriction. Upgrade plan to delete github organization sync."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!existingConfig)
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: `Organization ${orgPermission.orgId} GitHub organization sync config missing.`
|
||||||
|
});
|
||||||
|
|
||||||
|
const config = await githubOrgSyncDAL.deleteById(existingConfig.id);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getGithubOrgSync = async ({ orgPermission }: TDeleteGithubOrgSyncDTO) => {
|
||||||
|
const { permission } = await permissionService.getOrgPermission(
|
||||||
|
orgPermission.type,
|
||||||
|
orgPermission.id,
|
||||||
|
orgPermission.orgId,
|
||||||
|
orgPermission.authMethod,
|
||||||
|
orgPermission.orgId
|
||||||
|
);
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
|
||||||
|
const existingConfig = await githubOrgSyncDAL.findOne({ orgId: orgPermission.orgId });
|
||||||
|
if (!existingConfig)
|
||||||
|
throw new NotFoundError({
|
||||||
|
message: `Organization ${orgPermission.orgId} GitHub organization sync config missing.`
|
||||||
|
});
|
||||||
|
|
||||||
|
return existingConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
const syncUserGroups = async (orgId: string, userId: string, accessToken: string) => {
|
||||||
|
const config = await githubOrgSyncDAL.findOne({ orgId });
|
||||||
|
if (!config || !config?.isActive) return;
|
||||||
|
|
||||||
|
const infisicalUserGroups = await userGroupMembershipDAL.findGroupMembershipsByUserIdInOrg(userId, orgId);
|
||||||
|
const infisicalUserGroupSet = new Set(infisicalUserGroups.map((el) => el.groupName));
|
||||||
|
|
||||||
|
const octoRest = new OctokitRest({
|
||||||
|
auth: accessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const { data: userOrgMembershipDetails } = await octoRest.rest.orgs
|
||||||
|
.getMembershipForAuthenticatedUser({
|
||||||
|
org: config.githubOrgName
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error(err, "User not part of GitHub synced organization");
|
||||||
|
throw new BadRequestError({ message: "User not part of GitHub synced organization" });
|
||||||
|
});
|
||||||
|
const username = userOrgMembershipDetails?.user?.login;
|
||||||
|
if (!username) throw new BadRequestError({ message: "User not part of GitHub synced organization" });
|
||||||
|
|
||||||
|
const octokit = new OctokitWithPlugin({
|
||||||
|
auth: accessToken,
|
||||||
|
request: {
|
||||||
|
signal: AbortSignal.timeout(5000)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await octokit.graphql
|
||||||
|
.paginate<{
|
||||||
|
organization: { teams: { totalCount: number; edges: { node: { name: string; description: string } }[] } };
|
||||||
|
}>(
|
||||||
|
`
|
||||||
|
query orgTeams($cursor: String,$org: String!, $username: String!){
|
||||||
|
organization(login: $org) {
|
||||||
|
teams(first: 100, userLogins: [$username], after: $cursor) {
|
||||||
|
totalCount
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
name
|
||||||
|
description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
org: config.githubOrgName,
|
||||||
|
username
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
if ((err as Error)?.message?.includes("Although you appear to have the correct authorization credential")) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message:
|
||||||
|
"Please check your organization have approved Infisical Oauth application. For more info: https://infisical.com/docs/documentation/platform/github-org-sync#troubleshooting"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new BadRequestError({ message: (err as Error)?.message });
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
organization: { teams }
|
||||||
|
} = data;
|
||||||
|
const githubUserTeams = teams?.edges?.map((el) => el.node.name.toLowerCase()) || [];
|
||||||
|
const githubUserTeamSet = new Set(githubUserTeams);
|
||||||
|
const githubUserTeamOnInfisical = await groupDAL.find({ orgId, $in: { name: githubUserTeams } });
|
||||||
|
const githubUserTeamOnInfisicalGroupByName = groupBy(githubUserTeamOnInfisical, (i) => i.name);
|
||||||
|
|
||||||
|
const newTeams = githubUserTeams.filter(
|
||||||
|
(el) => !infisicalUserGroupSet.has(el) && !Object.hasOwn(githubUserTeamOnInfisicalGroupByName, el)
|
||||||
|
);
|
||||||
|
const updateTeams = githubUserTeams.filter(
|
||||||
|
(el) => !infisicalUserGroupSet.has(el) && Object.hasOwn(githubUserTeamOnInfisicalGroupByName, el)
|
||||||
|
);
|
||||||
|
const removeFromTeams = infisicalUserGroups.filter((el) => !githubUserTeamSet.has(el.groupName));
|
||||||
|
|
||||||
|
if (newTeams.length || updateTeams.length || removeFromTeams.length) {
|
||||||
|
await groupDAL.transaction(async (tx) => {
|
||||||
|
if (newTeams.length) {
|
||||||
|
const newGroups = await groupDAL.insertMany(
|
||||||
|
newTeams.map((newGroupName) => ({
|
||||||
|
name: newGroupName,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
slug: newGroupName,
|
||||||
|
orgId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
await userGroupMembershipDAL.insertMany(
|
||||||
|
newGroups.map((el) => ({
|
||||||
|
groupId: el.id,
|
||||||
|
userId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updateTeams.length) {
|
||||||
|
await userGroupMembershipDAL.insertMany(
|
||||||
|
updateTeams.map((el) => ({
|
||||||
|
groupId: githubUserTeamOnInfisicalGroupByName[el][0].id,
|
||||||
|
userId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeFromTeams.length) {
|
||||||
|
await userGroupMembershipDAL.delete(
|
||||||
|
{ userId, $in: { groupId: removeFromTeams.map((el) => el.groupId) } },
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
createGithubOrgSync,
|
||||||
|
updateGithubOrgSync,
|
||||||
|
deleteGithubOrgSync,
|
||||||
|
getGithubOrgSync,
|
||||||
|
syncUserGroups
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
|
|
||||||
|
export interface TCreateGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
githubOrgName: string;
|
||||||
|
githubOrgAccessToken?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TUpdateGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
githubOrgName?: string;
|
||||||
|
githubOrgAccessToken?: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TDeleteGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TGetGithubOrgSyncDTO {
|
||||||
|
orgPermission: OrgServiceActor;
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
pitRecovery: false,
|
pitRecovery: false,
|
||||||
ipAllowlisting: false,
|
ipAllowlisting: false,
|
||||||
rbac: false,
|
rbac: false,
|
||||||
|
githubOrgSync: false,
|
||||||
customRateLimits: false,
|
customRateLimits: false,
|
||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
secretAccessInsights: false,
|
secretAccessInsights: false,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export type TFeatureSet = {
|
|||||||
auditLogsRetentionDays: 0;
|
auditLogsRetentionDays: 0;
|
||||||
auditLogStreams: false;
|
auditLogStreams: false;
|
||||||
auditLogStreamLimit: 3;
|
auditLogStreamLimit: 3;
|
||||||
|
githubOrgSync: false;
|
||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
hsm: false;
|
hsm: false;
|
||||||
oidcSSO: false;
|
oidcSSO: false;
|
||||||
|
|||||||
@@ -685,10 +685,16 @@ export const oidcConfigServiceFactory = ({
|
|||||||
id_token_signed_response_alg: oidcCfg.jwtSignatureAlgorithm
|
id_token_signed_response_alg: oidcCfg.jwtSignatureAlgorithm
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Check if the OIDC provider supports PKCE
|
||||||
|
const codeChallengeMethods = client.issuer.metadata.code_challenge_methods_supported;
|
||||||
|
const supportsPKCE = Array.isArray(codeChallengeMethods) && codeChallengeMethods.includes("S256");
|
||||||
|
|
||||||
const strategy = new OpenIdStrategy(
|
const strategy = new OpenIdStrategy(
|
||||||
{
|
{
|
||||||
client,
|
client,
|
||||||
passReqToCallback: true
|
passReqToCallback: true,
|
||||||
|
usePKCE: supportsPKCE,
|
||||||
|
params: supportsPKCE ? { code_challenge_method: "S256" } : undefined
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(_req: any, tokenSet: TokenSet, cb: any) => {
|
(_req: any, tokenSet: TokenSet, cb: any) => {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ export enum OIDCConfigurationType {
|
|||||||
export enum OIDCJWTSignatureAlgorithm {
|
export enum OIDCJWTSignatureAlgorithm {
|
||||||
RS256 = "RS256",
|
RS256 = "RS256",
|
||||||
HS256 = "HS256",
|
HS256 = "HS256",
|
||||||
RS512 = "RS512"
|
RS512 = "RS512",
|
||||||
|
EDDSA = "EdDSA"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TOidcLoginDTO = {
|
export type TOidcLoginDTO = {
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ export enum OrgPermissionSubjects {
|
|||||||
IncidentAccount = "incident-contact",
|
IncidentAccount = "incident-contact",
|
||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Scim = "scim",
|
Scim = "scim",
|
||||||
|
GithubOrgSync = "github-org-sync",
|
||||||
Ldap = "ldap",
|
Ldap = "ldap",
|
||||||
Groups = "groups",
|
Groups = "groups",
|
||||||
Billing = "billing",
|
Billing = "billing",
|
||||||
@@ -101,6 +102,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.GithubOrgSync]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
@@ -165,6 +167,10 @@ export const OrgPermissionSchema = z.discriminatedUnion("subject", [
|
|||||||
subject: z.literal(OrgPermissionSubjects.Scim).describe("The entity this permission pertains to."),
|
subject: z.literal(OrgPermissionSubjects.Scim).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(OrgPermissionSubjects.GithubOrgSync).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(OrgPermissionSubjects.Ldap).describe("The entity this permission pertains to."),
|
subject: z.literal(OrgPermissionSubjects.Ldap).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(OrgPermissionActions).describe("Describe what action an entity can take.")
|
||||||
@@ -273,6 +279,11 @@ const buildAdminPermission = () => {
|
|||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Scim);
|
||||||
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.Scim);
|
||||||
|
|
||||||
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
can(OrgPermissionActions.Delete, OrgPermissionSubjects.GithubOrgSync);
|
||||||
|
|
||||||
can(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Read, OrgPermissionSubjects.Ldap);
|
||||||
can(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Create, OrgPermissionSubjects.Ldap);
|
||||||
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
can(OrgPermissionActions.Edit, OrgPermissionSubjects.Ldap);
|
||||||
|
|||||||
@@ -551,13 +551,26 @@ export const permissionServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getProjectPermission = async <T extends ActorType>({
|
const getProjectPermission = async <T extends ActorType>({
|
||||||
actor,
|
actor: inputActor,
|
||||||
actorId,
|
actorId: inputActorId,
|
||||||
projectId,
|
projectId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType
|
actionProjectType
|
||||||
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
}: TGetProjectPermissionArg): Promise<TProjectPermissionRT<T>> => {
|
||||||
|
let actor = inputActor;
|
||||||
|
let actorId = inputActorId;
|
||||||
|
const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails");
|
||||||
|
if (
|
||||||
|
assumedPrivilegeDetailsCtx &&
|
||||||
|
actor === ActorType.USER &&
|
||||||
|
actorId === assumedPrivilegeDetailsCtx.requesterId &&
|
||||||
|
projectId === assumedPrivilegeDetailsCtx.projectId
|
||||||
|
) {
|
||||||
|
actor = assumedPrivilegeDetailsCtx.actorType;
|
||||||
|
actorId = assumedPrivilegeDetailsCtx.actorId;
|
||||||
|
}
|
||||||
|
|
||||||
switch (actor) {
|
switch (actor) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
return getUserProjectPermission({
|
return getUserProjectPermission({
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ export enum ProjectPermissionIdentityActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
GrantPrivileges = "grant-privileges"
|
GrantPrivileges = "grant-privileges",
|
||||||
|
AssumePrivileges = "assume-privileges"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionMemberActions {
|
export enum ProjectPermissionMemberActions {
|
||||||
@@ -58,7 +59,8 @@ export enum ProjectPermissionMemberActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
GrantPrivileges = "grant-privileges"
|
GrantPrivileges = "grant-privileges",
|
||||||
|
AssumePrivileges = "assume-privileges"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionGroupActions {
|
export enum ProjectPermissionGroupActions {
|
||||||
@@ -714,7 +716,8 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionMemberActions.Edit,
|
ProjectPermissionMemberActions.Edit,
|
||||||
ProjectPermissionMemberActions.Delete,
|
ProjectPermissionMemberActions.Delete,
|
||||||
ProjectPermissionMemberActions.Read,
|
ProjectPermissionMemberActions.Read,
|
||||||
ProjectPermissionMemberActions.GrantPrivileges
|
ProjectPermissionMemberActions.GrantPrivileges,
|
||||||
|
ProjectPermissionMemberActions.AssumePrivileges
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Member
|
ProjectPermissionSub.Member
|
||||||
);
|
);
|
||||||
@@ -736,7 +739,8 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionIdentityActions.Edit,
|
ProjectPermissionIdentityActions.Edit,
|
||||||
ProjectPermissionIdentityActions.Delete,
|
ProjectPermissionIdentityActions.Delete,
|
||||||
ProjectPermissionIdentityActions.Read,
|
ProjectPermissionIdentityActions.Read,
|
||||||
ProjectPermissionIdentityActions.GrantPrivileges
|
ProjectPermissionIdentityActions.GrantPrivileges,
|
||||||
|
ProjectPermissionIdentityActions.AssumePrivileges
|
||||||
],
|
],
|
||||||
ProjectPermissionSub.Identity
|
ProjectPermissionSub.Identity
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export type TApprovalCreateSecretV2Bridge = {
|
|||||||
secretComment?: string;
|
secretComment?: string;
|
||||||
reminderNote?: string | null;
|
reminderNote?: string | null;
|
||||||
reminderRepeatDays?: number | null;
|
reminderRepeatDays?: number | null;
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
metadata?: Record<string, string>;
|
metadata?: Record<string, string>;
|
||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||||
db.ref("projectId").withSchema(TableName.SshHost),
|
db.ref("projectId").withSchema(TableName.SshHost),
|
||||||
db.ref("hostname").withSchema(TableName.SshHost),
|
db.ref("hostname").withSchema(TableName.SshHost),
|
||||||
|
db.ref("alias").withSchema(TableName.SshHost),
|
||||||
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
@@ -45,7 +46,8 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const grouped = groupBy(rows, (r) => r.sshHostId);
|
const grouped = groupBy(rows, (r) => r.sshHostId);
|
||||||
return Object.values(grouped).map((hostRows) => {
|
return Object.values(grouped).map((hostRows) => {
|
||||||
const { sshHostId, hostname, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId, projectId } = hostRows[0];
|
const { sshHostId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId, projectId } =
|
||||||
|
hostRows[0];
|
||||||
|
|
||||||
const loginMappingGrouped = groupBy(hostRows, (r) => r.loginUser);
|
const loginMappingGrouped = groupBy(hostRows, (r) => r.loginUser);
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
return {
|
return {
|
||||||
id: sshHostId,
|
id: sshHostId,
|
||||||
hostname,
|
hostname,
|
||||||
|
alias,
|
||||||
projectId,
|
projectId,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
@@ -87,6 +90,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||||
db.ref("projectId").withSchema(TableName.SshHost),
|
db.ref("projectId").withSchema(TableName.SshHost),
|
||||||
db.ref("hostname").withSchema(TableName.SshHost),
|
db.ref("hostname").withSchema(TableName.SshHost),
|
||||||
|
db.ref("alias").withSchema(TableName.SshHost),
|
||||||
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
@@ -99,7 +103,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const hostsGrouped = groupBy(rows, (r) => r.sshHostId);
|
const hostsGrouped = groupBy(rows, (r) => r.sshHostId);
|
||||||
return Object.values(hostsGrouped).map((hostRows) => {
|
return Object.values(hostsGrouped).map((hostRows) => {
|
||||||
const { sshHostId, hostname, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = hostRows[0];
|
const { sshHostId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = hostRows[0];
|
||||||
|
|
||||||
const loginMappingGrouped = groupBy(
|
const loginMappingGrouped = groupBy(
|
||||||
hostRows.filter((r) => r.loginUser),
|
hostRows.filter((r) => r.loginUser),
|
||||||
@@ -116,6 +120,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
return {
|
return {
|
||||||
id: sshHostId,
|
id: sshHostId,
|
||||||
hostname,
|
hostname,
|
||||||
|
alias,
|
||||||
projectId,
|
projectId,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
@@ -144,6 +149,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
db.ref("id").withSchema(TableName.SshHost).as("sshHostId"),
|
||||||
db.ref("projectId").withSchema(TableName.SshHost),
|
db.ref("projectId").withSchema(TableName.SshHost),
|
||||||
db.ref("hostname").withSchema(TableName.SshHost),
|
db.ref("hostname").withSchema(TableName.SshHost),
|
||||||
|
db.ref("alias").withSchema(TableName.SshHost),
|
||||||
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
db.ref("userCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
db.ref("hostCertTtl").withSchema(TableName.SshHost),
|
||||||
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
db.ref("loginUser").withSchema(TableName.SshHostLoginUser),
|
||||||
@@ -155,7 +161,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
if (rows.length === 0) return null;
|
if (rows.length === 0) return null;
|
||||||
|
|
||||||
const { sshHostId: id, projectId, hostname, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = rows[0];
|
const { sshHostId: id, projectId, hostname, alias, userCertTtl, hostCertTtl, userSshCaId, hostSshCaId } = rows[0];
|
||||||
|
|
||||||
const loginMappingGrouped = groupBy(
|
const loginMappingGrouped = groupBy(
|
||||||
rows.filter((r) => r.loginUser),
|
rows.filter((r) => r.loginUser),
|
||||||
@@ -173,6 +179,7 @@ export const sshHostDALFactory = (db: TDbClient) => {
|
|||||||
id,
|
id,
|
||||||
projectId,
|
projectId,
|
||||||
hostname,
|
hostname,
|
||||||
|
alias,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
loginMappings,
|
loginMappings,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const sanitizedSshHost = SshHostsSchema.pick({
|
|||||||
id: true,
|
id: true,
|
||||||
projectId: true,
|
projectId: true,
|
||||||
hostname: true,
|
hostname: true,
|
||||||
|
alias: true,
|
||||||
userCertTtl: true,
|
userCertTtl: true,
|
||||||
hostCertTtl: true,
|
hostCertTtl: true,
|
||||||
userSshCaId: true,
|
userSshCaId: true,
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ export const sshHostServiceFactory = ({
|
|||||||
const createSshHost = async ({
|
const createSshHost = async ({
|
||||||
projectId,
|
projectId,
|
||||||
hostname,
|
hostname,
|
||||||
|
alias,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
loginMappings,
|
loginMappings,
|
||||||
@@ -192,6 +193,7 @@ export const sshHostServiceFactory = ({
|
|||||||
{
|
{
|
||||||
projectId,
|
projectId,
|
||||||
hostname,
|
hostname,
|
||||||
|
alias: alias === "" ? null : alias,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
userSshCaId,
|
userSshCaId,
|
||||||
@@ -265,6 +267,7 @@ export const sshHostServiceFactory = ({
|
|||||||
const updateSshHost = async ({
|
const updateSshHost = async ({
|
||||||
sshHostId,
|
sshHostId,
|
||||||
hostname,
|
hostname,
|
||||||
|
alias,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl,
|
hostCertTtl,
|
||||||
loginMappings,
|
loginMappings,
|
||||||
@@ -297,6 +300,7 @@ export const sshHostServiceFactory = ({
|
|||||||
sshHostId,
|
sshHostId,
|
||||||
{
|
{
|
||||||
hostname,
|
hostname,
|
||||||
|
alias: alias === "" ? null : alias,
|
||||||
userCertTtl,
|
userCertTtl,
|
||||||
hostCertTtl
|
hostCertTtl
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export type TListSshHostsDTO = Omit<TProjectPermission, "projectId">;
|
|||||||
|
|
||||||
export type TCreateSshHostDTO = {
|
export type TCreateSshHostDTO = {
|
||||||
hostname: string;
|
hostname: string;
|
||||||
|
alias?: string;
|
||||||
userCertTtl: string;
|
userCertTtl: string;
|
||||||
hostCertTtl: string;
|
hostCertTtl: string;
|
||||||
loginMappings: {
|
loginMappings: {
|
||||||
@@ -19,6 +20,7 @@ export type TCreateSshHostDTO = {
|
|||||||
export type TUpdateSshHostDTO = {
|
export type TUpdateSshHostDTO = {
|
||||||
sshHostId: string;
|
sshHostId: string;
|
||||||
hostname?: string;
|
hostname?: string;
|
||||||
|
alias?: string;
|
||||||
userCertTtl?: string;
|
userCertTtl?: string;
|
||||||
hostCertTtl?: string;
|
hostCertTtl?: string;
|
||||||
loginMappings?: {
|
loginMappings?: {
|
||||||
|
|||||||
@@ -807,6 +807,8 @@ export const RAW_SECRETS = {
|
|||||||
tagIds: "The ID of the tags to be attached to the updated secret.",
|
tagIds: "The ID of the tags to be attached to the updated secret.",
|
||||||
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days.",
|
||||||
secretReminderNote: "Note to be attached in notification email.",
|
secretReminderNote: "Note to be attached in notification email.",
|
||||||
|
secretReminderRecipients:
|
||||||
|
"An array of user IDs that will receive the reminder email. If not specified, all project members will receive the reminder email.",
|
||||||
newSecretName: "The new name for the secret."
|
newSecretName: "The new name for the secret."
|
||||||
},
|
},
|
||||||
DELETE: {
|
DELETE: {
|
||||||
@@ -1387,6 +1389,7 @@ export const SSH_HOSTS = {
|
|||||||
CREATE: {
|
CREATE: {
|
||||||
projectId: "The ID of the project to create the SSH host in.",
|
projectId: "The ID of the project to create the SSH host in.",
|
||||||
hostname: "The hostname of the SSH host.",
|
hostname: "The hostname of the SSH host.",
|
||||||
|
alias: "The alias for the SSH host.",
|
||||||
userCertTtl: "The time to live for user certificates issued under this host.",
|
userCertTtl: "The time to live for user certificates issued under this host.",
|
||||||
hostCertTtl: "The time to live for host certificates issued under this host.",
|
hostCertTtl: "The time to live for host certificates issued under this host.",
|
||||||
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
||||||
@@ -1401,6 +1404,7 @@ export const SSH_HOSTS = {
|
|||||||
UPDATE: {
|
UPDATE: {
|
||||||
sshHostId: "The ID of the SSH host to update.",
|
sshHostId: "The ID of the SSH host to update.",
|
||||||
hostname: "The hostname of the SSH host to update to.",
|
hostname: "The hostname of the SSH host to update to.",
|
||||||
|
alias: "The alias for the SSH host to update to.",
|
||||||
userCertTtl: "The time to live for user certificates issued under this host to update to.",
|
userCertTtl: "The time to live for user certificates issued under this host to update to.",
|
||||||
hostCertTtl: "The time to live for host certificates issued under this host to update to.",
|
hostCertTtl: "The time to live for host certificates issued under this host to update to.",
|
||||||
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
loginUser: "A login user on the remote machine (e.g. 'ec2-user', 'deploy', 'admin')",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN = "x-infisical-github-auth-access-token";
|
||||||
@@ -2,7 +2,7 @@ export const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000;
|
|||||||
|
|
||||||
export const secondsToMillis = (seconds: number) => seconds * 1000;
|
export const secondsToMillis = (seconds: number) => seconds * 1000;
|
||||||
|
|
||||||
export const applyJitter = (delayMs: number, jitterMs: number) => {
|
export const applyJitter = (delay: number, jitter: number) => {
|
||||||
const jitter = Math.floor(Math.random() * (2 * jitterMs)) - jitterMs;
|
const jitterTime = Math.floor(Math.random() * (2 * jitter)) - jitter;
|
||||||
return delayMs + jitter;
|
return delay + jitterTime;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
import { Tables } from "knex/types/tables";
|
import { Tables } from "knex/types/tables";
|
||||||
|
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
|
||||||
import { DatabaseError } from "../errors";
|
import { DatabaseError } from "../errors";
|
||||||
import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic";
|
||||||
|
|
||||||
@@ -25,28 +27,41 @@ export type TFindFilter<R extends object = object> = Partial<R> & {
|
|||||||
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
$search?: Partial<{ [k in keyof R]: R[k] }>;
|
||||||
$complex?: TKnexDynamicOperator<R>;
|
$complex?: TKnexDynamicOperator<R>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildFindFilter =
|
export const buildFindFilter =
|
||||||
<R extends object = object>({ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>) =>
|
<R extends object = object>(
|
||||||
|
{ $in, $notNull, $search, $complex, ...filter }: TFindFilter<R>,
|
||||||
|
tableName?: TableName,
|
||||||
|
excludeKeys?: Array<keyof R>
|
||||||
|
) =>
|
||||||
(bd: Knex.QueryBuilder<R, R>) => {
|
(bd: Knex.QueryBuilder<R, R>) => {
|
||||||
void bd.where(filter);
|
const processedFilter = tableName
|
||||||
|
? Object.fromEntries(
|
||||||
|
Object.entries(filter)
|
||||||
|
.filter(([key]) => !excludeKeys || !excludeKeys.includes(key as keyof R))
|
||||||
|
.map(([key, value]) => [`${tableName}.${key}`, value])
|
||||||
|
)
|
||||||
|
: filter;
|
||||||
|
|
||||||
|
void bd.where(processedFilter);
|
||||||
if ($in) {
|
if ($in) {
|
||||||
Object.entries($in).forEach(([key, val]) => {
|
Object.entries($in).forEach(([key, val]) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
void bd.whereIn(key as never, val as never);
|
void bd.whereIn([`${tableName ? `${tableName}.` : ""}${key}`] as never, val as never);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($notNull?.length) {
|
if ($notNull?.length) {
|
||||||
$notNull.forEach((key) => {
|
$notNull.forEach((key) => {
|
||||||
void bd.whereNotNull(key as never);
|
void bd.whereNotNull([`${tableName ? `${tableName}.` : ""}${key as string}`] as never);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($search) {
|
if ($search) {
|
||||||
Object.entries($search).forEach(([key, val]) => {
|
Object.entries($search).forEach(([key, val]) => {
|
||||||
if (val) {
|
if (val) {
|
||||||
void bd.whereILike(key as never, val as never);
|
void bd.whereILike([`${tableName ? `${tableName}.` : ""}${key}`] as never, val as never);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,17 @@ export const fetchGithubEmails = async (accessToken: string) => {
|
|||||||
});
|
});
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TGithubUser = {
|
||||||
|
name?: string;
|
||||||
|
login: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchGithubUser = async (accessToken: string) => {
|
||||||
|
const { data } = await request.get<TGithubUser>(`${INTEGRATION_GITHUB_API_URL}/user`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ export const blockLocalAndPrivateIpAddresses = async (url: string) => {
|
|||||||
|
|
||||||
const validUrl = new URL(url);
|
const validUrl = new URL(url);
|
||||||
const inputHostIps: string[] = [];
|
const inputHostIps: string[] = [];
|
||||||
if (isIPv4(validUrl.host)) {
|
if (isIPv4(validUrl.hostname)) {
|
||||||
inputHostIps.push(validUrl.host);
|
inputHostIps.push(validUrl.hostname);
|
||||||
} else {
|
} else {
|
||||||
if (validUrl.host === "localhost" || validUrl.host === "host.docker.internal") {
|
if (validUrl.hostname === "localhost" || validUrl.hostname === "host.docker.internal") {
|
||||||
throw new BadRequestError({ message: "Local IPs not allowed as URL" });
|
throw new BadRequestError({ message: "Local IPs not allowed as URL" });
|
||||||
}
|
}
|
||||||
const resolvedIps = await dns.resolve4(validUrl.host);
|
const resolvedIps = await dns.resolve4(validUrl.hostname);
|
||||||
inputHostIps.push(...resolvedIps);
|
inputHostIps.push(...resolvedIps);
|
||||||
}
|
}
|
||||||
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
|
const isInternalIp = inputHostIps.some((el) => isPrivateIp(el));
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
|
import fp from "fastify-plugin";
|
||||||
|
|
||||||
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
|
export const injectAssumePrivilege = fp(async (server: FastifyZodProvider) => {
|
||||||
|
server.addHook("onRequest", async (req, res) => {
|
||||||
|
const assumeRoleCookie = req.cookies["infisical-project-assume-privileges"];
|
||||||
|
try {
|
||||||
|
if (req?.auth?.authMode === AuthMode.JWT && assumeRoleCookie) {
|
||||||
|
const decodedToken = server.services.assumePrivileges.verifyAssumePrivilegeToken(
|
||||||
|
assumeRoleCookie,
|
||||||
|
req.auth.tokenVersionId
|
||||||
|
);
|
||||||
|
if (decodedToken) {
|
||||||
|
requestContext.set("assumedPrivilegeDetails", decodedToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
req.log.error({ error }, "Failed to verify assume privilege token");
|
||||||
|
void res.clearCookie("infisical-project-assume-privileges");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -12,6 +12,7 @@ import { accessApprovalPolicyServiceFactory } from "@app/ee/services/access-appr
|
|||||||
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
import { accessApprovalRequestDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-dal";
|
||||||
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
|
import { accessApprovalRequestReviewerDALFactory } from "@app/ee/services/access-approval-request/access-approval-request-reviewer-dal";
|
||||||
import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
import { accessApprovalRequestServiceFactory } from "@app/ee/services/access-approval-request/access-approval-request-service";
|
||||||
|
import { assumePrivilegeServiceFactory } from "@app/ee/services/assume-privilege/assume-privilege-service";
|
||||||
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
import { auditLogDALFactory } from "@app/ee/services/audit-log/audit-log-dal";
|
||||||
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
import { auditLogQueueServiceFactory } from "@app/ee/services/audit-log/audit-log-queue";
|
||||||
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
import { auditLogServiceFactory } from "@app/ee/services/audit-log/audit-log-service";
|
||||||
@@ -32,6 +33,8 @@ import { gatewayDALFactory } from "@app/ee/services/gateway/gateway-dal";
|
|||||||
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
import { gatewayServiceFactory } from "@app/ee/services/gateway/gateway-service";
|
||||||
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
|
import { orgGatewayConfigDALFactory } from "@app/ee/services/gateway/org-gateway-config-dal";
|
||||||
import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal";
|
import { projectGatewayDALFactory } from "@app/ee/services/gateway/project-gateway-dal";
|
||||||
|
import { githubOrgSyncDALFactory } from "@app/ee/services/github-org-sync/github-org-sync-dal";
|
||||||
|
import { githubOrgSyncServiceFactory } from "@app/ee/services/github-org-sync/github-org-sync-service";
|
||||||
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
import { groupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { groupServiceFactory } from "@app/ee/services/group/group-service";
|
import { groupServiceFactory } from "@app/ee/services/group/group-service";
|
||||||
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { userGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
@@ -217,6 +220,7 @@ import { secretFolderServiceFactory } from "@app/services/secret-folder/secret-f
|
|||||||
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal";
|
||||||
import { secretImportDALFactory } from "@app/services/secret-import/secret-import-dal";
|
import { secretImportDALFactory } from "@app/services/secret-import/secret-import-dal";
|
||||||
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
import { secretImportServiceFactory } from "@app/services/secret-import/secret-import-service";
|
||||||
|
import { secretReminderRecipientsDALFactory } from "@app/services/secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||||
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
import { secretSharingDALFactory } from "@app/services/secret-sharing/secret-sharing-dal";
|
||||||
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
import { secretSharingServiceFactory } from "@app/services/secret-sharing/secret-sharing-service";
|
||||||
import { secretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
import { secretSyncDALFactory } from "@app/services/secret-sync/secret-sync-dal";
|
||||||
@@ -251,6 +255,7 @@ import { workflowIntegrationDALFactory } from "@app/services/workflow-integratio
|
|||||||
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
import { workflowIntegrationServiceFactory } from "@app/services/workflow-integration/workflow-integration-service";
|
||||||
|
|
||||||
import { injectAuditLogInfo } from "../plugins/audit-log";
|
import { injectAuditLogInfo } from "../plugins/audit-log";
|
||||||
|
import { injectAssumePrivilege } from "../plugins/auth/inject-assume-privilege";
|
||||||
import { injectIdentity } from "../plugins/auth/inject-identity";
|
import { injectIdentity } from "../plugins/auth/inject-identity";
|
||||||
import { injectPermission } from "../plugins/auth/inject-permission";
|
import { injectPermission } from "../plugins/auth/inject-permission";
|
||||||
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
import { injectRateLimits } from "../plugins/inject-rate-limits";
|
||||||
@@ -420,6 +425,8 @@ export const registerRoutes = async (
|
|||||||
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
const orgGatewayConfigDAL = orgGatewayConfigDALFactory(db);
|
||||||
const gatewayDAL = gatewayDALFactory(db);
|
const gatewayDAL = gatewayDALFactory(db);
|
||||||
const projectGatewayDAL = projectGatewayDALFactory(db);
|
const projectGatewayDAL = projectGatewayDALFactory(db);
|
||||||
|
const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db);
|
||||||
|
const githubOrgSyncDAL = githubOrgSyncDALFactory(db);
|
||||||
|
|
||||||
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
|
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
|
||||||
const microsoftTeamsIntegrationDAL = microsoftTeamsIntegrationDALFactory(db);
|
const microsoftTeamsIntegrationDAL = microsoftTeamsIntegrationDALFactory(db);
|
||||||
@@ -432,6 +439,11 @@ export const registerRoutes = async (
|
|||||||
serviceTokenDAL,
|
serviceTokenDAL,
|
||||||
projectDAL
|
projectDAL
|
||||||
});
|
});
|
||||||
|
const assumePrivilegeService = assumePrivilegeServiceFactory({
|
||||||
|
projectDAL,
|
||||||
|
permissionService
|
||||||
|
});
|
||||||
|
|
||||||
const licenseService = licenseServiceFactory({
|
const licenseService = licenseServiceFactory({
|
||||||
permissionService,
|
permissionService,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
@@ -554,6 +566,15 @@ export const registerRoutes = async (
|
|||||||
externalGroupOrgRoleMappingDAL
|
externalGroupOrgRoleMappingDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const githubOrgSyncConfigService = githubOrgSyncServiceFactory({
|
||||||
|
licenseService,
|
||||||
|
githubOrgSyncDAL,
|
||||||
|
kmsService,
|
||||||
|
permissionService,
|
||||||
|
groupDAL,
|
||||||
|
userGroupMembershipDAL
|
||||||
|
});
|
||||||
|
|
||||||
const ldapService = ldapConfigServiceFactory({
|
const ldapService = ldapConfigServiceFactory({
|
||||||
ldapConfigDAL,
|
ldapConfigDAL,
|
||||||
ldapGroupMapDAL,
|
ldapGroupMapDAL,
|
||||||
@@ -743,6 +764,7 @@ export const registerRoutes = async (
|
|||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
licenseService
|
licenseService
|
||||||
});
|
});
|
||||||
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
const projectUserAdditionalPrivilegeService = projectUserAdditionalPrivilegeServiceFactory({
|
||||||
@@ -976,6 +998,7 @@ export const registerRoutes = async (
|
|||||||
secretApprovalRequestDAL,
|
secretApprovalRequestDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
orgService,
|
orgService,
|
||||||
resourceMetadataDAL,
|
resourceMetadataDAL,
|
||||||
secretSyncQueue
|
secretSyncQueue
|
||||||
@@ -1039,7 +1062,9 @@ export const registerRoutes = async (
|
|||||||
projectRoleDAL,
|
projectRoleDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
identityDAL,
|
||||||
|
userDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
const snapshotService = secretSnapshotServiceFactory({
|
const snapshotService = secretSnapshotServiceFactory({
|
||||||
@@ -1698,7 +1723,9 @@ export const registerRoutes = async (
|
|||||||
kmipOperation: kmipOperationService,
|
kmipOperation: kmipOperationService,
|
||||||
gateway: gatewayService,
|
gateway: gatewayService,
|
||||||
secretRotationV2: secretRotationV2Service,
|
secretRotationV2: secretRotationV2Service,
|
||||||
microsoftTeams: microsoftTeamsService
|
microsoftTeams: microsoftTeamsService,
|
||||||
|
assumePrivileges: assumePrivilegeService,
|
||||||
|
githubOrgSync: githubOrgSyncConfigService
|
||||||
});
|
});
|
||||||
|
|
||||||
const cronJobs: CronJob[] = [];
|
const cronJobs: CronJob[] = [];
|
||||||
@@ -1719,6 +1746,7 @@ export const registerRoutes = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
await server.register(injectIdentity, { userDAL, serviceTokenDAL });
|
||||||
|
await server.register(injectAssumePrivilege);
|
||||||
await server.register(injectPermission);
|
await server.register(injectPermission);
|
||||||
await server.register(injectRateLimits);
|
await server.register(injectRateLimits);
|
||||||
await server.register(injectAuditLogInfo);
|
await server.register(injectAuditLogInfo);
|
||||||
@@ -1758,30 +1786,6 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
logger.info(`Raw event loop stats: ${JSON.stringify(histogram, null, 2)}`);
|
logger.info(`Raw event loop stats: ${JSON.stringify(histogram, null, 2)}`);
|
||||||
|
|
||||||
// try {
|
|
||||||
// await db.raw("SELECT NOW()");
|
|
||||||
// } catch (err) {
|
|
||||||
// logger.error("Health check: database connection failed", err);
|
|
||||||
// return reply.code(503).send({
|
|
||||||
// date: new Date(),
|
|
||||||
// message: "Service unavailable"
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (cfg.isRedisConfigured) {
|
|
||||||
// const redis = new Redis(cfg.REDIS_URL);
|
|
||||||
// try {
|
|
||||||
// await redis.ping();
|
|
||||||
// redis.disconnect();
|
|
||||||
// } catch (err) {
|
|
||||||
// logger.error("Health check: redis connection failed", err);
|
|
||||||
// return reply.code(503).send({
|
|
||||||
// date: new Date(),
|
|
||||||
// message: "Service unavailable"
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: new Date(),
|
date: new Date(),
|
||||||
message: "Ok",
|
message: "Ok",
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ export const registerAuthRoutes = async (server: FastifyZodProvider) => {
|
|||||||
secure: appCfg.HTTPS_ENABLED
|
secure: appCfg.HTTPS_ENABLED
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void res.cookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
|
||||||
return { message: "Successfully logged out" };
|
return { message: "Successfully logged out" };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretFoldersSchema, SecretImportsSchema } from "@app/db/schemas";
|
import { SecretFoldersSchema, SecretImportsSchema, UsersSchema } from "@app/db/schemas";
|
||||||
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType, UserAgentType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
import { ProjectPermissionSecretActions } from "@app/ee/services/permission/project-permission";
|
||||||
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
import { SecretRotationV2Schema } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-union-schema";
|
||||||
@@ -594,6 +594,12 @@ export const registerDashboardRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional(),
|
.optional(),
|
||||||
secrets: secretRawSchema
|
secrets: secretRawSchema
|
||||||
.extend({
|
.extend({
|
||||||
|
secretReminderRecipients: z
|
||||||
|
.object({
|
||||||
|
user: UsersSchema.pick({ id: true, email: true, username: true }),
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
secretValueHidden: z.boolean(),
|
secretValueHidden: z.boolean(),
|
||||||
secretPath: z.string().optional(),
|
secretPath: z.string().optional(),
|
||||||
secretMetadata: ResourceMetadataSchema.optional(),
|
secretMetadata: ResourceMetadataSchema.optional(),
|
||||||
|
|||||||
@@ -9,15 +9,17 @@
|
|||||||
import { Authenticator } from "@fastify/passport";
|
import { Authenticator } from "@fastify/passport";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
import RedisStore from "connect-redis";
|
import RedisStore from "connect-redis";
|
||||||
import { Strategy as GitHubStrategy } from "passport-github";
|
|
||||||
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
import { Strategy as GitLabStrategy } from "passport-gitlab2";
|
||||||
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
|
||||||
|
import { Strategy as OAuth2Strategy } from "passport-oauth2";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN } from "@app/lib/config/const";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { fetchGithubEmails } from "@app/lib/requests/github";
|
import { ms } from "@app/lib/ms";
|
||||||
|
import { fetchGithubEmails, fetchGithubUser } from "@app/lib/requests/github";
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { AuthMethod } from "@app/services/auth/auth-type";
|
import { AuthMethod } from "@app/services/auth/auth-type";
|
||||||
import { OrgAuthMethod } from "@app/services/org/org-types";
|
import { OrgAuthMethod } from "@app/services/org/org-types";
|
||||||
@@ -42,6 +44,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
await server.register(passport.initialize());
|
await server.register(passport.initialize());
|
||||||
await server.register(passport.secureSession());
|
await server.register(passport.secureSession());
|
||||||
|
|
||||||
// passport oauth strategy for Google
|
// passport oauth strategy for Google
|
||||||
const isGoogleOauthActive = Boolean(appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN);
|
const isGoogleOauthActive = Boolean(appCfg.CLIENT_ID_GOOGLE_LOGIN && appCfg.CLIENT_SECRET_GOOGLE_LOGIN);
|
||||||
if (isGoogleOauthActive) {
|
if (isGoogleOauthActive) {
|
||||||
@@ -52,8 +55,9 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
|
clientID: appCfg.CLIENT_ID_GOOGLE_LOGIN as string,
|
||||||
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
|
clientSecret: appCfg.CLIENT_SECRET_GOOGLE_LOGIN as string,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/google`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/google`,
|
||||||
scope: ["profile", " email"],
|
scope: ["profile", "email"],
|
||||||
state: true
|
state: true,
|
||||||
|
pkce: true
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, _accessToken, _refreshToken, profile, cb) => {
|
async (req, _accessToken, _refreshToken, profile, cb) => {
|
||||||
@@ -89,34 +93,44 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
const isGithubOauthActive = Boolean(appCfg.CLIENT_SECRET_GITHUB_LOGIN && appCfg.CLIENT_ID_GITHUB_LOGIN);
|
const isGithubOauthActive = Boolean(appCfg.CLIENT_SECRET_GITHUB_LOGIN && appCfg.CLIENT_ID_GITHUB_LOGIN);
|
||||||
if (isGithubOauthActive) {
|
if (isGithubOauthActive) {
|
||||||
passport.use(
|
passport.use(
|
||||||
new GitHubStrategy(
|
"github",
|
||||||
|
new OAuth2Strategy(
|
||||||
{
|
{
|
||||||
passReqToCallback: true,
|
authorizationURL: "https://github.com/login/oauth/authorize",
|
||||||
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
|
tokenURL: "https://github.com/login/oauth/access_token",
|
||||||
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
|
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN!,
|
||||||
|
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN!,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/github`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/github`,
|
||||||
scope: ["user:email"],
|
scope: ["user:email", "read:org"],
|
||||||
// akhilmhdh: because the ts type for this is outdated by the maintainer
|
state: true,
|
||||||
state: true as unknown as string
|
pkce: true,
|
||||||
|
passReqToCallback: true
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, accessToken, _refreshToken, profile, cb) => {
|
async (req: any, accessToken: string, _refreshToken: string, _profile: any, done: Function) => {
|
||||||
// @ts-expect-error this is because this is express type and not fastify
|
|
||||||
const callbackPort = req.session.get("callbackPort");
|
|
||||||
try {
|
try {
|
||||||
const ghEmails = await fetchGithubEmails(accessToken);
|
const ghEmails = await fetchGithubEmails(accessToken);
|
||||||
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
||||||
|
|
||||||
|
if (!email) throw new Error("No primary email found");
|
||||||
|
|
||||||
|
// profile does not get automatically populated so we need to manually fetch user info
|
||||||
|
const user = await fetchGithubUser(accessToken);
|
||||||
|
|
||||||
|
const callbackPort = req.session.get("callbackPort");
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
||||||
email,
|
email,
|
||||||
firstName: profile.displayName || profile.username || "",
|
firstName: user.name || user.login,
|
||||||
lastName: "",
|
lastName: "",
|
||||||
authMethod: AuthMethod.GITHUB,
|
authMethod: AuthMethod.GITHUB,
|
||||||
callbackPort
|
callbackPort
|
||||||
});
|
});
|
||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
|
||||||
} catch (error) {
|
done(null, { isUserCompleted, providerAuthToken, externalProviderAccessToken: accessToken });
|
||||||
logger.error(error);
|
} catch (err) {
|
||||||
cb(error as Error, false);
|
logger.error(err);
|
||||||
|
done(err as Error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -136,7 +150,8 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
|
clientSecret: appCfg.CLIENT_SECRET_GITLAB_LOGIN,
|
||||||
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/gitlab`,
|
callbackURL: `${appCfg.SITE_URL}/api/v1/sso/gitlab`,
|
||||||
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL,
|
baseURL: appCfg.CLIENT_GITLAB_LOGIN_URL,
|
||||||
state: true
|
state: true,
|
||||||
|
pkce: true
|
||||||
},
|
},
|
||||||
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
||||||
try {
|
try {
|
||||||
@@ -166,17 +181,24 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional(),
|
||||||
|
is_admin_login: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: [
|
preValidation: [
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { callback_port: callbackPort } = req.query;
|
const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query;
|
||||||
// ensure fresh session state per login attempt
|
// ensure fresh session state per login attempt
|
||||||
await req.session.regenerate();
|
await req.session.regenerate();
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
req.session.set("callbackPort", callbackPort);
|
req.session.set("callbackPort", callbackPort);
|
||||||
}
|
}
|
||||||
|
if (isAdminLogin) {
|
||||||
|
req.session.set("isAdminLogin", isAdminLogin);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
passport.authenticate("google", {
|
passport.authenticate("google", {
|
||||||
scope: ["profile", "email"],
|
scope: ["profile", "email"],
|
||||||
@@ -200,10 +222,13 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
// this is due to zod type difference
|
// this is due to zod type difference
|
||||||
}) as never,
|
}) as never,
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
const isAdminLogin = req.session.get("isAdminLogin");
|
||||||
await req.session.destroy();
|
await req.session.destroy();
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${
|
||||||
|
isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : ""
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
@@ -217,18 +242,26 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional(),
|
||||||
|
is_admin_login: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: [
|
preValidation: [
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { callback_port: callbackPort } = req.query;
|
const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query;
|
||||||
// ensure fresh session state per login attempt
|
// ensure fresh session state per login attempt
|
||||||
await req.session.regenerate();
|
await req.session.regenerate();
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
req.session.set("callbackPort", callbackPort);
|
req.session.set("callbackPort", callbackPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdminLogin) {
|
||||||
|
req.session.set("isAdminLogin", isAdminLogin);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
passport.authenticate("github", {
|
passport.authenticate("github", {
|
||||||
session: false,
|
session: false,
|
||||||
@@ -289,10 +322,24 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
// this is due to zod type difference
|
// this is due to zod type difference
|
||||||
}) as any,
|
}) as any,
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
const isAdminLogin = req.session.get("isAdminLogin");
|
||||||
await req.session.destroy();
|
await req.session.destroy();
|
||||||
|
|
||||||
|
if (req.passportUser.externalProviderAccessToken) {
|
||||||
|
void res.cookie(INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN, req.passportUser.externalProviderAccessToken, {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
expires: new Date(Date.now() + ms(appCfg.JWT_PROVIDER_AUTH_LIFETIME))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${
|
||||||
|
isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : ""
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
@@ -306,18 +353,26 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
method: "GET",
|
method: "GET",
|
||||||
schema: {
|
schema: {
|
||||||
querystring: z.object({
|
querystring: z.object({
|
||||||
callback_port: z.string().optional()
|
callback_port: z.string().optional(),
|
||||||
|
is_admin_login: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((val) => val === "true")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
preValidation: [
|
preValidation: [
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
const { callback_port: callbackPort } = req.query;
|
const { callback_port: callbackPort, is_admin_login: isAdminLogin } = req.query;
|
||||||
// ensure fresh session state per login attempt
|
// ensure fresh session state per login attempt
|
||||||
await req.session.regenerate();
|
await req.session.regenerate();
|
||||||
if (callbackPort) {
|
if (callbackPort) {
|
||||||
req.session.set("callbackPort", callbackPort);
|
req.session.set("callbackPort", callbackPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isAdminLogin) {
|
||||||
|
req.session.set("isAdminLogin", isAdminLogin);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
passport.authenticate("gitlab", {
|
passport.authenticate("gitlab", {
|
||||||
session: false,
|
session: false,
|
||||||
@@ -342,10 +397,13 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
}) as any,
|
}) as any,
|
||||||
handler: async (req, res) => {
|
handler: async (req, res) => {
|
||||||
|
const isAdminLogin = req.session.get("isAdminLogin");
|
||||||
await req.session.destroy();
|
await req.session.destroy();
|
||||||
if (req.passportUser.isUserCompleted) {
|
if (req.passportUser.isUserCompleted) {
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}`
|
`${appCfg.SITE_URL}/login/sso?token=${encodeURIComponent(req.passportUser.providerAuthToken)}${
|
||||||
|
isAdminLogin ? `&isAdminLogin=${isAdminLogin}` : ""
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return res.redirect(
|
return res.redirect(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN } from "@app/lib/config/const";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||||
|
|
||||||
@@ -70,6 +71,21 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const githubOauthAccessToken = req.cookies[INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN];
|
||||||
|
if (githubOauthAccessToken) {
|
||||||
|
await server.services.githubOrgSync
|
||||||
|
.syncUserGroups(req.body.organizationId, tokens.user.userId, githubOauthAccessToken)
|
||||||
|
.finally(() => {
|
||||||
|
void res.setCookie(INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN, "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: cfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void res.setCookie("jid", tokens.refresh, {
|
void res.setCookie("jid", tokens.refresh, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: "/",
|
path: "/",
|
||||||
@@ -77,6 +93,14 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
secure: cfg.HTTPS_ENABLED
|
secure: cfg.HTTPS_ENABLED
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void res.cookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: cfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
|
||||||
return { token: tokens.access, isMfaEnabled: false };
|
return { token: tokens.access, isMfaEnabled: false };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -131,6 +155,14 @@ export const registerLoginRouter = async (server: FastifyZodProvider) => {
|
|||||||
secure: appCfg.HTTPS_ENABLED
|
secure: appCfg.HTTPS_ENABLED
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void res.cookie("infisical-project-assume-privileges", "", {
|
||||||
|
httpOnly: true,
|
||||||
|
path: "/",
|
||||||
|
sameSite: "strict",
|
||||||
|
secure: appCfg.HTTPS_ENABLED,
|
||||||
|
maxAge: 0
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
encryptionVersion: data.user.encryptionVersion,
|
encryptionVersion: data.user.encryptionVersion,
|
||||||
token: data.token.access,
|
token: data.token.access,
|
||||||
|
|||||||
@@ -662,6 +662,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
.optional()
|
.optional()
|
||||||
.nullable()
|
.nullable()
|
||||||
.describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays),
|
.describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays),
|
||||||
|
secretReminderRecipients: z.string().array().optional().describe(RAW_SECRETS.UPDATE.secretReminderRecipients),
|
||||||
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
newSecretName: SecretNameSchema.optional().describe(RAW_SECRETS.UPDATE.newSecretName),
|
||||||
secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment)
|
secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment)
|
||||||
}),
|
}),
|
||||||
@@ -692,6 +693,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
skipMultilineEncoding: req.body.skipMultilineEncoding,
|
||||||
tagIds: req.body.tagIds,
|
tagIds: req.body.tagIds,
|
||||||
secretReminderRepeatDays: req.body.secretReminderRepeatDays,
|
secretReminderRepeatDays: req.body.secretReminderRepeatDays,
|
||||||
|
secretReminderRecipients: req.body.secretReminderRecipients,
|
||||||
secretReminderNote: req.body.secretReminderNote,
|
secretReminderNote: req.body.secretReminderNote,
|
||||||
metadata: req.body.metadata,
|
metadata: req.body.metadata,
|
||||||
newSecretName: req.body.newSecretName,
|
newSecretName: req.body.newSecretName,
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ export const authLoginServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...tokens,
|
...tokens,
|
||||||
|
user,
|
||||||
isMfaEnabled: false
|
isMfaEnabled: false
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -784,7 +785,7 @@ export const authLoginServiceFactory = ({
|
|||||||
organizationId
|
organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return { token, isMfaEnabled: false, user: userEnc } as const;
|
return { token, isMfaEnabled: false, user: userEnc, decodedProviderToken } as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ export const deleteGithubSecrets = async ({
|
|||||||
selected_repositories_url?: string | undefined;
|
selected_repositories_url?: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error just octokit ts compatiability issue
|
||||||
const OctokitWithRetry = Octokit.plugin(retry);
|
const OctokitWithRetry = Octokit.plugin(retry);
|
||||||
let octokit: Octokit;
|
let octokit: Octokit;
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|||||||
@@ -342,9 +342,12 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return async ({ cipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
return async ({ cipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
||||||
const { data } = await externalKms.decrypt(cipherTextBlob);
|
try {
|
||||||
|
const { data } = await externalKms.decrypt(cipherTextBlob);
|
||||||
return data;
|
return data;
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,9 +560,12 @@ export const kmsServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return async ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
return async ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||||
const { encryptedBlob } = await externalKms.encrypt(plainText);
|
try {
|
||||||
|
const { encryptedBlob } = await externalKms.encrypt(plainText);
|
||||||
return { cipherTextBlob: encryptedBlob };
|
return { cipherTextBlob: encryptedBlob };
|
||||||
|
} finally {
|
||||||
|
await externalKms.cleanup();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { TProjectDALFactory } from "../project/project-dal";
|
|||||||
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 { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
import { TProjectRoleDALFactory } from "../project-role/project-role-dal";
|
||||||
|
import { TSecretReminderRecipientsDALFactory } from "../secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||||
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
import { SmtpTemplates, TSmtpService } from "../smtp/smtp-service";
|
||||||
import { TUserDALFactory } from "../user/user-dal";
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
import { TProjectMembershipDALFactory } from "./project-membership-dal";
|
||||||
@@ -53,6 +54,7 @@ type TProjectMembershipServiceFactoryDep = {
|
|||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "findLatestProjectKey" | "delete" | "insertMany">;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
projectUserAdditionalPrivilegeDAL: Pick<TProjectUserAdditionalPrivilegeDALFactory, "delete">;
|
||||||
|
secretReminderRecipientsDAL: Pick<TSecretReminderRecipientsDALFactory, "delete">;
|
||||||
groupProjectDAL: TGroupProjectDALFactory;
|
groupProjectDAL: TGroupProjectDALFactory;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,6 +73,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
licenseService
|
licenseService
|
||||||
}: TProjectMembershipServiceFactoryDep) => {
|
}: TProjectMembershipServiceFactoryDep) => {
|
||||||
const getProjectMemberships = async ({
|
const getProjectMemberships = async ({
|
||||||
@@ -389,6 +392,13 @@ export const projectMembershipServiceFactory = ({
|
|||||||
const membership = await projectMembershipDAL.transaction(async (tx) => {
|
const membership = await projectMembershipDAL.transaction(async (tx) => {
|
||||||
const [deletedMembership] = await projectMembershipDAL.delete({ projectId, id: membershipId }, tx);
|
const [deletedMembership] = await projectMembershipDAL.delete({ projectId, id: membershipId }, tx);
|
||||||
await projectKeyDAL.delete({ receiverId: deletedMembership.userId, projectId }, tx);
|
await projectKeyDAL.delete({ receiverId: deletedMembership.userId, projectId }, tx);
|
||||||
|
await secretReminderRecipientsDAL.delete(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
userId: deletedMembership.userId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
return deletedMembership;
|
return deletedMembership;
|
||||||
});
|
});
|
||||||
return membership;
|
return membership;
|
||||||
@@ -466,6 +476,16 @@ export const projectMembershipServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await secretReminderRecipientsDAL.delete(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
$in: {
|
||||||
|
userId: projectMembers.map(({ user }) => user.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
// delete project keys belonging to users that are not part of any other groups in the project
|
// delete project keys belonging to users that are not part of any other groups in the project
|
||||||
await projectKeyDAL.delete(
|
await projectKeyDAL.delete(
|
||||||
{
|
{
|
||||||
@@ -526,6 +546,15 @@ export const projectMembershipServiceFactory = ({
|
|||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await secretReminderRecipientsDAL.delete(
|
||||||
|
{
|
||||||
|
projectId,
|
||||||
|
userId: actorId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
const membership = (
|
const membership = (
|
||||||
await projectMembershipDAL.delete(
|
await projectMembershipDAL.delete(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
import { ForbiddenError, MongoAbility, RawRuleOf } from "@casl/ability";
|
||||||
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
import { PackRule, packRules, unpackRules } from "@casl/ability/extra";
|
||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
|
|
||||||
import { ActionProjectType, ProjectMembershipRole, TableName } from "@app/db/schemas";
|
import { ActionProjectType, ProjectMembershipRole, TableName } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||||
@@ -12,10 +13,12 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|||||||
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars";
|
||||||
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
import { UnpackedPermissionSchema } from "@app/server/routes/sanitizedSchema/permission";
|
||||||
|
|
||||||
import { ActorAuthMethod } from "../auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "../auth/auth-type";
|
||||||
|
import { TIdentityDALFactory } from "../identity/identity-dal";
|
||||||
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal";
|
||||||
|
import { TUserDALFactory } from "../user/user-dal";
|
||||||
import { TProjectRoleDALFactory } from "./project-role-dal";
|
import { TProjectRoleDALFactory } from "./project-role-dal";
|
||||||
import { getPredefinedRoles } from "./project-role-fns";
|
import { getPredefinedRoles } from "./project-role-fns";
|
||||||
import {
|
import {
|
||||||
@@ -29,6 +32,8 @@ import {
|
|||||||
|
|
||||||
type TProjectRoleServiceFactoryDep = {
|
type TProjectRoleServiceFactoryDep = {
|
||||||
projectRoleDAL: TProjectRoleDALFactory;
|
projectRoleDAL: TProjectRoleDALFactory;
|
||||||
|
identityDAL: Pick<TIdentityDALFactory, "findById">;
|
||||||
|
userDAL: Pick<TUserDALFactory, "findById">;
|
||||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getUserProjectPermission">;
|
||||||
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
|
identityProjectMembershipRoleDAL: TIdentityProjectMembershipRoleDALFactory;
|
||||||
@@ -47,7 +52,9 @@ export const projectRoleServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
identityProjectMembershipRoleDAL,
|
identityProjectMembershipRoleDAL,
|
||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
projectDAL
|
projectDAL,
|
||||||
|
identityDAL,
|
||||||
|
userDAL
|
||||||
}: TProjectRoleServiceFactoryDep) => {
|
}: TProjectRoleServiceFactoryDep) => {
|
||||||
const createRole = async ({ data, actor, actorId, actorAuthMethod, actorOrgId, filter }: TCreateRoleDTO) => {
|
const createRole = async ({ data, actor, actorId, actorAuthMethod, actorOrgId, filter }: TCreateRoleDTO) => {
|
||||||
let projectId = "";
|
let projectId = "";
|
||||||
@@ -220,14 +227,42 @@ export const projectRoleServiceFactory = ({
|
|||||||
actorAuthMethod: ActorAuthMethod,
|
actorAuthMethod: ActorAuthMethod,
|
||||||
actorOrgId: string | undefined
|
actorOrgId: string | undefined
|
||||||
) => {
|
) => {
|
||||||
const { permission, membership } = await permissionService.getUserProjectPermission({
|
const { permission, membership } = await permissionService.getProjectPermission({
|
||||||
userId,
|
actor: ActorType.USER,
|
||||||
|
actorId: userId,
|
||||||
projectId,
|
projectId,
|
||||||
authMethod: actorAuthMethod,
|
actorAuthMethod,
|
||||||
userOrgId: actorOrgId,
|
actorOrgId,
|
||||||
actionProjectType: ActionProjectType.Any
|
actionProjectType: ActionProjectType.Any
|
||||||
});
|
});
|
||||||
return { permissions: packRules(permission.rules), membership };
|
// just to satisfy ts
|
||||||
|
if (!("roles" in membership)) throw new BadRequestError({ message: "Service token not allowed" });
|
||||||
|
|
||||||
|
const assumedPrivilegeDetailsCtx = requestContext.get("assumedPrivilegeDetails");
|
||||||
|
const isAssumingPrivilege = assumedPrivilegeDetailsCtx?.projectId === projectId;
|
||||||
|
const assumedPrivilegeDetails = isAssumingPrivilege
|
||||||
|
? {
|
||||||
|
actorId: assumedPrivilegeDetailsCtx?.actorId,
|
||||||
|
actorType: assumedPrivilegeDetailsCtx?.actorType,
|
||||||
|
actorName: "",
|
||||||
|
actorEmail: ""
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (assumedPrivilegeDetails?.actorType === ActorType.IDENTITY) {
|
||||||
|
const identityDetails = await identityDAL.findById(assumedPrivilegeDetails.actorId);
|
||||||
|
if (!identityDetails)
|
||||||
|
throw new NotFoundError({ message: `Identity with ID ${assumedPrivilegeDetails.actorId} not found` });
|
||||||
|
assumedPrivilegeDetails.actorName = identityDetails.name;
|
||||||
|
} else if (assumedPrivilegeDetails?.actorType === ActorType.USER) {
|
||||||
|
const userDetails = await userDAL.findById(assumedPrivilegeDetails?.actorId);
|
||||||
|
if (!userDetails)
|
||||||
|
throw new NotFoundError({ message: `User with ID ${assumedPrivilegeDetails.actorId} not found` });
|
||||||
|
assumedPrivilegeDetails.actorName = `${userDetails?.firstName} ${userDetails?.lastName || ""}`;
|
||||||
|
assumedPrivilegeDetails.actorEmail = userDetails?.email || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return { permissions: packRules(permission.rules), membership, assumedPrivilegeDetails };
|
||||||
};
|
};
|
||||||
|
|
||||||
return { createRole, updateRole, deleteRole, listRoles, getUserPermission, getRoleBySlug };
|
return { createRole, updateRole, deleteRole, listRoles, getUserPermission, getRoleBySlug };
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TDbClient } from "@app/db";
|
||||||
|
import { TableName } from "@app/db/schemas";
|
||||||
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
|
export type TSecretReminderRecipientsDALFactory = ReturnType<typeof secretReminderRecipientsDALFactory>;
|
||||||
|
|
||||||
|
export const secretReminderRecipientsDALFactory = (db: TDbClient) => {
|
||||||
|
const secretReminderRecipientsOrm = ormify(db, TableName.SecretReminderRecipients);
|
||||||
|
|
||||||
|
const findUsersBySecretId = async (secretId: string, tx?: Knex) => {
|
||||||
|
const res = await (tx || db.replicaNode())(TableName.SecretReminderRecipients)
|
||||||
|
.where({ secretId })
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SecretReminderRecipients}.userId`, `${TableName.Users}.id`)
|
||||||
|
.leftJoin(TableName.Project, `${TableName.SecretReminderRecipients}.projectId`, `${TableName.Project}.id`)
|
||||||
|
.leftJoin(TableName.OrgMembership, (bd) => {
|
||||||
|
void bd
|
||||||
|
.on(`${TableName.OrgMembership}.userId`, "=", `${TableName.SecretReminderRecipients}.userId`)
|
||||||
|
.andOn(`${TableName.OrgMembership}.orgId`, "=", `${TableName.Project}.orgId`);
|
||||||
|
})
|
||||||
|
|
||||||
|
.where(`${TableName.OrgMembership}.isActive`, true)
|
||||||
|
.select(selectAllTableCols(TableName.SecretReminderRecipients))
|
||||||
|
.select(
|
||||||
|
db.ref("email").withSchema(TableName.Users).as("email"),
|
||||||
|
db.ref("username").withSchema(TableName.Users).as("username"),
|
||||||
|
db.ref("firstName").withSchema(TableName.Users).as("firstName"),
|
||||||
|
db.ref("lastName").withSchema(TableName.Users).as("lastName")
|
||||||
|
);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ...secretReminderRecipientsOrm, findUsersBySecretId };
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export type TSecretReminderRecipient = {
|
||||||
|
user: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
email?: string | null;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
TTeamCitySyncWithCredentials
|
TTeamCitySyncWithCredentials
|
||||||
} from "@app/services/secret-sync/teamcity/teamcity-sync-types";
|
} from "@app/services/secret-sync/teamcity/teamcity-sync-types";
|
||||||
|
|
||||||
// Note: Most variables won't be returned with a value due to them being a "password" type (starting with "env.").
|
// Note: Most variables won't be returned with a value due to them being a "password" type.
|
||||||
// TeamCity API returns empty string for password-type variables for security reasons.
|
// TeamCity API returns empty string for password-type variables for security reasons.
|
||||||
const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildConfig }: TTeamCityListVariables) => {
|
const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildConfig }: TTeamCityListVariables) => {
|
||||||
const { data } = await request.get<TTeamCityListVariablesResponse>(
|
const { data } = await request.get<TTeamCityListVariablesResponse>(
|
||||||
@@ -25,12 +25,16 @@ const listTeamCityVariables = async ({ instanceUrl, accessToken, project, buildC
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Filters for only non-inherited environment variables
|
||||||
// Strips out "env." from map key, but the "name" field still has the original unaltered key.
|
// Strips out "env." from map key, but the "name" field still has the original unaltered key.
|
||||||
return Object.fromEntries(
|
return Object.fromEntries(
|
||||||
data.property.map((variable) => [
|
data.property
|
||||||
variable.name.startsWith("env.") ? variable.name.substring(4) : variable.name,
|
.filter((variable) => !variable.inherited)
|
||||||
{ ...variable, value: variable.value || "" } // Password values will be empty strings from the API for security
|
.filter((variable) => variable.name.startsWith("env."))
|
||||||
])
|
.map((variable) => [
|
||||||
|
variable.name.substring(4),
|
||||||
|
{ ...variable, value: variable.value || "" } // Password values will be empty strings from the API for security
|
||||||
|
])
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import type {
|
|||||||
TFindSecretsByFolderIdsFilter,
|
TFindSecretsByFolderIdsFilter,
|
||||||
TGetSecretsDTO
|
TGetSecretsDTO
|
||||||
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
} from "@app/services/secret-v2-bridge/secret-v2-bridge-types";
|
||||||
|
import { applyJitter } from "@app/lib/dates";
|
||||||
|
|
||||||
export const SecretServiceCacheKeys = {
|
export const SecretServiceCacheKeys = {
|
||||||
get productKey() {
|
get productKey() {
|
||||||
@@ -48,7 +49,7 @@ interface TSecretV2DalArg {
|
|||||||
keyStore: TKeyStoreFactory;
|
keyStore: TKeyStoreFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SECRET_DAL_TTL = 5 * 60;
|
export const SECRET_DAL_TTL = () => applyJitter(10 * 60, 2 * 60);
|
||||||
export const SECRET_DAL_VERSION_TTL = 15 * 60;
|
export const SECRET_DAL_VERSION_TTL = 15 * 60;
|
||||||
export const MAX_SECRET_CACHE_BYTES = 25 * 1024 * 1024;
|
export const MAX_SECRET_CACHE_BYTES = 25 * 1024 * 1024;
|
||||||
export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
||||||
@@ -63,7 +64,8 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
const findOne = async (filter: Partial<TSecretsV2>, tx?: Knex) => {
|
const findOne = async (filter: Partial<TSecretsV2>, tx?: Knex) => {
|
||||||
try {
|
try {
|
||||||
const docs = await (tx || db)(TableName.SecretV2)
|
const docs = await (tx || db)(TableName.SecretV2)
|
||||||
.where(filter)
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
.where(buildFindFilter(filter, TableName.SecretV2))
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretV2JnTag,
|
TableName.SecretV2JnTag,
|
||||||
`${TableName.SecretV2}.id`,
|
`${TableName.SecretV2}.id`,
|
||||||
@@ -79,7 +81,17 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
`${TableName.SecretV2}.id`,
|
`${TableName.SecretV2}.id`,
|
||||||
`${TableName.SecretRotationV2SecretMapping}.secretId`
|
`${TableName.SecretRotationV2SecretMapping}.secretId`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretReminderRecipients,
|
||||||
|
`${TableName.SecretV2}.id`,
|
||||||
|
`${TableName.SecretReminderRecipients}.secretId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SecretReminderRecipients}.userId`, `${TableName.Users}.id`)
|
||||||
.select(selectAllTableCols(TableName.SecretV2))
|
.select(selectAllTableCols(TableName.SecretV2))
|
||||||
|
.select(db.ref("id").withSchema(TableName.SecretReminderRecipients).as("reminderRecipientId"))
|
||||||
|
.select(db.ref("username").withSchema(TableName.Users).as("reminderRecipientUsername"))
|
||||||
|
.select(db.ref("email").withSchema(TableName.Users).as("reminderRecipientEmail"))
|
||||||
|
.select(db.ref("id").withSchema(TableName.Users).as("reminderRecipientUserId"))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
@@ -103,6 +115,23 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
slug,
|
slug,
|
||||||
name: slug
|
name: slug
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "reminderRecipientId",
|
||||||
|
label: "secretReminderRecipients" as const,
|
||||||
|
mapper: ({
|
||||||
|
reminderRecipientId,
|
||||||
|
reminderRecipientUsername,
|
||||||
|
reminderRecipientEmail,
|
||||||
|
reminderRecipientUserId
|
||||||
|
}) => ({
|
||||||
|
user: {
|
||||||
|
id: reminderRecipientUserId,
|
||||||
|
username: reminderRecipientUsername,
|
||||||
|
email: reminderRecipientEmail
|
||||||
|
},
|
||||||
|
id: reminderRecipientId
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -484,6 +513,12 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
`${TableName.SecretV2JnTag}.${TableName.SecretTag}Id`,
|
||||||
`${TableName.SecretTag}.id`
|
`${TableName.SecretTag}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
TableName.SecretReminderRecipients,
|
||||||
|
`${TableName.SecretV2}.id`,
|
||||||
|
`${TableName.SecretReminderRecipients}.secretId`
|
||||||
|
)
|
||||||
|
.leftJoin(TableName.Users, `${TableName.SecretReminderRecipients}.userId`, `${TableName.Users}.id`)
|
||||||
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
.leftJoin(TableName.ResourceMetadata, `${TableName.SecretV2}.id`, `${TableName.ResourceMetadata}.secretId`)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
TableName.SecretRotationV2SecretMapping,
|
TableName.SecretRotationV2SecretMapping,
|
||||||
@@ -512,6 +547,10 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
}) as rank`
|
}) as rank`
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.select(db.ref("id").withSchema(TableName.SecretReminderRecipients).as("reminderRecipientId"))
|
||||||
|
.select(db.ref("username").withSchema(TableName.Users).as("reminderRecipientUsername"))
|
||||||
|
.select(db.ref("email").withSchema(TableName.Users).as("reminderRecipientEmail"))
|
||||||
|
.select(db.ref("id").withSchema(TableName.Users).as("reminderRecipientUserId"))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
@@ -556,6 +595,23 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => {
|
|||||||
isRotatedSecret: Boolean(el.rotationId)
|
isRotatedSecret: Boolean(el.rotationId)
|
||||||
}),
|
}),
|
||||||
childrenMapper: [
|
childrenMapper: [
|
||||||
|
{
|
||||||
|
key: "reminderRecipientId",
|
||||||
|
label: "secretReminderRecipients" as const,
|
||||||
|
mapper: ({
|
||||||
|
reminderRecipientId,
|
||||||
|
reminderRecipientUsername,
|
||||||
|
reminderRecipientEmail,
|
||||||
|
reminderRecipientUserId
|
||||||
|
}) => ({
|
||||||
|
user: {
|
||||||
|
id: reminderRecipientUserId,
|
||||||
|
username: reminderRecipientUsername,
|
||||||
|
email: reminderRecipientEmail
|
||||||
|
},
|
||||||
|
id: reminderRecipientId
|
||||||
|
})
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import path from "node:path";
|
|||||||
|
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
|
|
||||||
import { TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
|
import { SecretType, TableName, TSecretFolders, TSecretsV2 } from "@app/db/schemas";
|
||||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { groupBy } from "@app/lib/fn";
|
import { groupBy } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
@@ -12,6 +12,7 @@ import { TProjectEnvDALFactory } from "../project-env/project-env-dal";
|
|||||||
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-schema";
|
||||||
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
import { INFISICAL_SECRET_VALUE_HIDDEN_MASK } from "../secret/secret-fns";
|
||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
|
import { TSecretReminderRecipient } from "../secret-reminder-recipients/secret-reminder-recipients-types";
|
||||||
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "./secret-v2-bridge-dal";
|
||||||
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
|
import { TFnSecretBulkDelete, TFnSecretBulkInsert, TFnSecretBulkUpdate } from "./secret-v2-bridge-types";
|
||||||
|
|
||||||
@@ -353,7 +354,7 @@ export const fnSecretBulkDelete = async ({
|
|||||||
deletedSecrets
|
deletedSecrets
|
||||||
.filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays))
|
.filter(({ reminderRepeatDays }) => Boolean(reminderRepeatDays))
|
||||||
.map(({ id, reminderRepeatDays }) =>
|
.map(({ id, reminderRepeatDays }) =>
|
||||||
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: reminderRepeatDays as number })
|
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: reminderRepeatDays as number }, tx)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -684,6 +685,7 @@ export const reshapeBridgeSecret = (
|
|||||||
secretMetadata?: ResourceMetadataDTO;
|
secretMetadata?: ResourceMetadataDTO;
|
||||||
isRotatedSecret?: boolean;
|
isRotatedSecret?: boolean;
|
||||||
rotationId?: string;
|
rotationId?: string;
|
||||||
|
secretReminderRecipients?: TSecretReminderRecipient[];
|
||||||
},
|
},
|
||||||
secretValueHidden: boolean
|
secretValueHidden: boolean
|
||||||
) => ({
|
) => ({
|
||||||
@@ -715,9 +717,10 @@ export const reshapeBridgeSecret = (
|
|||||||
updatedAt: secret.updatedAt,
|
updatedAt: secret.updatedAt,
|
||||||
isRotatedSecret: secret.isRotatedSecret,
|
isRotatedSecret: secret.isRotatedSecret,
|
||||||
rotationId: secret.rotationId,
|
rotationId: secret.rotationId,
|
||||||
|
secretReminderRecipients: secret.secretReminderRecipients || [],
|
||||||
...(secretValueHidden
|
...(secretValueHidden
|
||||||
? {
|
? {
|
||||||
secretValue: INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
secretValue: secret.type === SecretType.Personal ? secret.value : INFISICAL_SECRET_VALUE_HIDDEN_MASK,
|
||||||
secretValueHidden: true
|
secretValueHidden: true
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
|
|||||||
@@ -544,7 +544,12 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
id: updatedSecret[0].id,
|
id: updatedSecret[0].id,
|
||||||
...inputSecret
|
...inputSecret
|
||||||
},
|
},
|
||||||
oldSecret: secret,
|
oldSecret: {
|
||||||
|
id: secret.id,
|
||||||
|
secretReminderNote: secret.reminderNote,
|
||||||
|
secretReminderRepeatDays: secret.reminderRepeatDays,
|
||||||
|
secretReminderRecipients: secret.secretReminderRecipients?.map((el) => el.user.id)
|
||||||
|
},
|
||||||
projectId
|
projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -957,7 +962,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
const encryptedCachedSecrets = await keyStore.getItem(cacheKey);
|
const encryptedCachedSecrets = await keyStore.getItem(cacheKey);
|
||||||
if (encryptedCachedSecrets) {
|
if (encryptedCachedSecrets) {
|
||||||
try {
|
try {
|
||||||
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL);
|
await keyStore.setExpiry(cacheKey, SECRET_DAL_TTL());
|
||||||
const cachedSecrets = secretManagerDecryptor({ cipherTextBlob: Buffer.from(encryptedCachedSecrets, "base64") });
|
const cachedSecrets = secretManagerDecryptor({ cipherTextBlob: Buffer.from(encryptedCachedSecrets, "base64") });
|
||||||
const { secrets, imports = [] } = JSON.parse(cachedSecrets.toString("utf8")) as {
|
const { secrets, imports = [] } = JSON.parse(cachedSecrets.toString("utf8")) as {
|
||||||
secrets: typeof decryptedSecrets;
|
secrets: typeof decryptedSecrets;
|
||||||
@@ -1127,7 +1132,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
plainText: Buffer.from(JSON.stringify(payload))
|
plainText: Buffer.from(JSON.stringify(payload))
|
||||||
}).cipherTextBlob;
|
}).cipherTextBlob;
|
||||||
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
|
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
|
||||||
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL, encryptedUpdatedCachedSecrets.toString("base64"));
|
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL(), encryptedUpdatedCachedSecrets.toString("base64"));
|
||||||
}
|
}
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
@@ -1174,7 +1179,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
plainText: Buffer.from(JSON.stringify(payload))
|
plainText: Buffer.from(JSON.stringify(payload))
|
||||||
}).cipherTextBlob;
|
}).cipherTextBlob;
|
||||||
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
|
if (encryptedUpdatedCachedSecrets.byteLength < MAX_SECRET_CACHE_BYTES) {
|
||||||
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL, encryptedUpdatedCachedSecrets.toString("base64"));
|
await keyStore.setItemWithExpiry(cacheKey, SECRET_DAL_TTL(), encryptedUpdatedCachedSecrets.toString("base64"));
|
||||||
}
|
}
|
||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ export type TUpdateSecretDTO = TProjectPermission & {
|
|||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
secretReminderRepeatDays?: number | null;
|
secretReminderRepeatDays?: number | null;
|
||||||
secretReminderNote?: string | null;
|
secretReminderNote?: string | null;
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
source?: string;
|
source?: string;
|
||||||
};
|
};
|
||||||
@@ -220,7 +221,7 @@ export type TFnSecretBulkDelete = {
|
|||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
|
secretDAL: Pick<TSecretV2BridgeDALFactory, "deleteMany">;
|
||||||
secretQueueService: {
|
secretQueueService: {
|
||||||
removeSecretReminder: (data: TRemoveSecretReminderDTO) => Promise<void>;
|
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -407,6 +407,7 @@ export const decryptSecretRaw = (
|
|||||||
id: secret.id,
|
id: secret.id,
|
||||||
user: secret.userId,
|
user: secret.userId,
|
||||||
tags: secret.tags?.map((el) => ({ ...el, name: el.slug })),
|
tags: secret.tags?.map((el) => ({ ...el, name: el.slug })),
|
||||||
|
secretReminderRecipients: [],
|
||||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||||
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
||||||
secretReminderNote: secret.secretReminderNote,
|
secretReminderNote: secret.secretReminderNote,
|
||||||
@@ -758,7 +759,7 @@ export const fnSecretBulkDelete = async ({
|
|||||||
deletedSecrets
|
deletedSecrets
|
||||||
.filter(({ secretReminderRepeatDays }) => Boolean(secretReminderRepeatDays))
|
.filter(({ secretReminderRepeatDays }) => Boolean(secretReminderRepeatDays))
|
||||||
.map(({ id, secretReminderRepeatDays }) =>
|
.map(({ id, secretReminderRepeatDays }) =>
|
||||||
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: secretReminderRepeatDays as number })
|
secretQueueService.removeSecretReminder({ secretId: id, repeatDays: secretReminderRepeatDays as number }, tx)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
/* eslint-disable no-await-in-loop */
|
/* eslint-disable no-await-in-loop */
|
||||||
import opentelemetry from "@opentelemetry/api";
|
import opentelemetry from "@opentelemetry/api";
|
||||||
import { AxiosError } from "axios";
|
import { AxiosError } from "axios";
|
||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ProjectMembershipRole,
|
ProjectMembershipRole,
|
||||||
ProjectUpgradeStatus,
|
ProjectUpgradeStatus,
|
||||||
ProjectVersion,
|
ProjectVersion,
|
||||||
|
SecretType,
|
||||||
TSecretSnapshotSecretsV2,
|
TSecretSnapshotSecretsV2,
|
||||||
TSecretVersionsV2
|
TSecretVersionsV2
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
@@ -53,6 +55,7 @@ import { ResourceMetadataDTO } from "../resource-metadata/resource-metadata-sche
|
|||||||
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal";
|
||||||
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
import { TSecretImportDALFactory } from "../secret-import/secret-import-dal";
|
||||||
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
|
import { fnSecretsV2FromImports } from "../secret-import/secret-import-fns";
|
||||||
|
import { TSecretReminderRecipientsDALFactory } from "../secret-reminder-recipients/secret-reminder-recipients-dal";
|
||||||
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
import { TSecretV2BridgeDALFactory } from "../secret-v2-bridge/secret-v2-bridge-dal";
|
||||||
import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
import { expandSecretReferencesFactory, getAllSecretReferences } from "../secret-v2-bridge/secret-v2-bridge-fns";
|
||||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
@@ -109,6 +112,10 @@ type TSecretQueueFactoryDep = {
|
|||||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||||
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
resourceMetadataDAL: Pick<TResourceMetadataDALFactory, "insertMany" | "delete">;
|
||||||
|
secretReminderRecipientsDAL: Pick<
|
||||||
|
TSecretReminderRecipientsDALFactory,
|
||||||
|
"delete" | "findUsersBySecretId" | "insertMany" | "transaction"
|
||||||
|
>;
|
||||||
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
|
secretSyncQueue: Pick<TSecretSyncQueueFactory, "queueSecretSyncsSyncSecretsByPath">;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,6 +177,7 @@ export const secretQueueFactory = ({
|
|||||||
projectUserMembershipRoleDAL,
|
projectUserMembershipRoleDAL,
|
||||||
projectKeyDAL,
|
projectKeyDAL,
|
||||||
resourceMetadataDAL,
|
resourceMetadataDAL,
|
||||||
|
secretReminderRecipientsDAL,
|
||||||
secretSyncQueue
|
secretSyncQueue
|
||||||
}: TSecretQueueFactoryDep) => {
|
}: TSecretQueueFactoryDep) => {
|
||||||
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
const integrationMeter = opentelemetry.metrics.getMeter("Integrations");
|
||||||
@@ -178,7 +186,11 @@ export const secretQueueFactory = ({
|
|||||||
unit: "1"
|
unit: "1"
|
||||||
});
|
});
|
||||||
|
|
||||||
const removeSecretReminder = async (dto: TRemoveSecretReminderDTO) => {
|
const removeSecretReminder = async ({ deleteRecipients = true, ...dto }: TRemoveSecretReminderDTO, tx?: Knex) => {
|
||||||
|
if (deleteRecipients) {
|
||||||
|
await secretReminderRecipientsDAL.delete({ secretId: dto.secretId }, tx);
|
||||||
|
}
|
||||||
|
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
await queueService.stopRepeatableJob(
|
await queueService.stopRepeatableJob(
|
||||||
QueueName.SecretReminder,
|
QueueName.SecretReminder,
|
||||||
@@ -224,7 +236,12 @@ export const secretQueueFactory = ({
|
|||||||
.replace(":", "-");
|
.replace(":", "-");
|
||||||
};
|
};
|
||||||
|
|
||||||
const addSecretReminder = async ({ oldSecret, newSecret, projectId }: TCreateSecretReminderDTO) => {
|
const addSecretReminder = async ({
|
||||||
|
oldSecret,
|
||||||
|
newSecret,
|
||||||
|
projectId,
|
||||||
|
deleteRecipients = true
|
||||||
|
}: TCreateSecretReminderDTO) => {
|
||||||
try {
|
try {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
|
|
||||||
@@ -246,7 +263,8 @@ export const secretQueueFactory = ({
|
|||||||
if (oldSecret.secretReminderRepeatDays) {
|
if (oldSecret.secretReminderRepeatDays) {
|
||||||
await removeSecretReminder({
|
await removeSecretReminder({
|
||||||
repeatDays: oldSecret.secretReminderRepeatDays,
|
repeatDays: oldSecret.secretReminderRepeatDays,
|
||||||
secretId: oldSecret.id
|
secretId: oldSecret.id,
|
||||||
|
deleteRecipients
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,29 +301,57 @@ export const secretQueueFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSecretReminder = async ({ newSecret, oldSecret, projectId }: THandleReminderDTO) => {
|
const handleSecretReminder = async ({ newSecret, oldSecret, projectId }: THandleReminderDTO) => {
|
||||||
const { secretReminderRepeatDays, secretReminderNote } = newSecret;
|
const { secretReminderRepeatDays, secretReminderNote, secretReminderRecipients } = newSecret;
|
||||||
|
|
||||||
if (newSecret.type !== "personal" && secretReminderRepeatDays !== undefined) {
|
const recipientsUpdated =
|
||||||
if (
|
secretReminderRecipients?.some(
|
||||||
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
(newId) => !oldSecret.secretReminderRecipients?.find((oldId) => newId === oldId)
|
||||||
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
|
) || secretReminderRecipients?.length !== oldSecret.secretReminderRecipients?.length;
|
||||||
) {
|
|
||||||
await addSecretReminder({
|
await secretReminderRecipientsDAL.transaction(async (tx) => {
|
||||||
oldSecret,
|
if (newSecret.type !== SecretType.Personal && secretReminderRepeatDays !== undefined) {
|
||||||
newSecret,
|
if (
|
||||||
projectId
|
(secretReminderRepeatDays && oldSecret.secretReminderRepeatDays !== secretReminderRepeatDays) ||
|
||||||
});
|
(secretReminderNote && oldSecret.secretReminderNote !== secretReminderNote)
|
||||||
} else if (
|
) {
|
||||||
secretReminderRepeatDays === null &&
|
await addSecretReminder({
|
||||||
secretReminderNote === null &&
|
oldSecret,
|
||||||
oldSecret.secretReminderRepeatDays
|
newSecret,
|
||||||
) {
|
projectId,
|
||||||
await removeSecretReminder({
|
deleteRecipients: false
|
||||||
secretId: oldSecret.id,
|
});
|
||||||
repeatDays: oldSecret.secretReminderRepeatDays
|
} else if (
|
||||||
});
|
secretReminderRepeatDays === null &&
|
||||||
|
secretReminderNote === null &&
|
||||||
|
oldSecret.secretReminderRepeatDays
|
||||||
|
) {
|
||||||
|
await removeSecretReminder({
|
||||||
|
secretId: oldSecret.id,
|
||||||
|
repeatDays: oldSecret.secretReminderRepeatDays
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (recipientsUpdated) {
|
||||||
|
// if no recipients, delete all existing recipients
|
||||||
|
if (!secretReminderRecipients?.length) {
|
||||||
|
const existingRecipients = await secretReminderRecipientsDAL.findUsersBySecretId(newSecret.id, tx);
|
||||||
|
if (existingRecipients) {
|
||||||
|
await secretReminderRecipientsDAL.delete({ secretId: newSecret.id }, tx);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await secretReminderRecipientsDAL.delete({ secretId: newSecret.id }, tx);
|
||||||
|
await secretReminderRecipientsDAL.insertMany(
|
||||||
|
secretReminderRecipients.map((r) => ({
|
||||||
|
secretId: newSecret.id,
|
||||||
|
userId: r,
|
||||||
|
projectId
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
const createManySecretsRawFn = createManySecretsRawFnFactory({
|
const createManySecretsRawFn = createManySecretsRawFnFactory({
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1071,6 +1117,8 @@ export const secretQueueFactory = ({
|
|||||||
const secret = await secretV2BridgeDAL.findById(data.secretId);
|
const secret = await secretV2BridgeDAL.findById(data.secretId);
|
||||||
const [folder] = await folderDAL.findSecretPathByFolderIds(project.id, [secret.folderId]);
|
const [folder] = await folderDAL.findSecretPathByFolderIds(project.id, [secret.folderId]);
|
||||||
|
|
||||||
|
const recipients = await secretReminderRecipientsDAL.findUsersBySecretId(data.secretId);
|
||||||
|
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`);
|
logger.info(`secretReminderQueue.process: [secretDocument=${data.secretId}] no organization found`);
|
||||||
return;
|
return;
|
||||||
@@ -1088,10 +1136,14 @@ export const secretQueueFactory = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const selectedRecipients = recipients?.length
|
||||||
|
? recipients.map((r) => r.email as string)
|
||||||
|
: projectMembers.map((m) => m.user.email as string);
|
||||||
|
|
||||||
await smtpService.sendMail({
|
await smtpService.sendMail({
|
||||||
template: SmtpTemplates.SecretReminder,
|
template: SmtpTemplates.SecretReminder,
|
||||||
subjectLine: "Infisical secret reminder",
|
subjectLine: "Infisical secret reminder",
|
||||||
recipients: [...projectMembers.map((m) => m.user.email)].filter((email) => email).map((email) => email as string),
|
recipients: selectedRecipients,
|
||||||
substitutions: {
|
substitutions: {
|
||||||
reminderNote: data.note, // May not be present.
|
reminderNote: data.note, // May not be present.
|
||||||
projectName: project.name,
|
projectName: project.name,
|
||||||
|
|||||||
@@ -546,10 +546,13 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
for await (const secret of secrets) {
|
for await (const secret of secrets) {
|
||||||
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
||||||
await secretQueueService.removeSecretReminder({
|
await secretQueueService.removeSecretReminder(
|
||||||
repeatDays: secret.secretReminderRepeatDays,
|
{
|
||||||
secretId: secret.id
|
repeatDays: secret.secretReminderRepeatDays,
|
||||||
});
|
secretId: secret.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +688,7 @@ export const secretServiceFactory = ({
|
|||||||
...secret,
|
...secret,
|
||||||
workspace: projectId,
|
workspace: projectId,
|
||||||
environment,
|
environment,
|
||||||
|
secretReminderRecipients: [],
|
||||||
secretPath: groupedPaths[secret.folderId][0].path
|
secretPath: groupedPaths[secret.folderId][0].path
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
@@ -1073,10 +1077,13 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
for await (const secret of secrets) {
|
for await (const secret of secrets) {
|
||||||
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
if (secret.secretReminderRepeatDays !== null && secret.secretReminderRepeatDays !== undefined) {
|
||||||
await secretQueueService.removeSecretReminder({
|
await secretQueueService.removeSecretReminder(
|
||||||
repeatDays: secret.secretReminderRepeatDays,
|
{
|
||||||
secretId: secret.id
|
repeatDays: secret.secretReminderRepeatDays,
|
||||||
});
|
secretId: secret.id
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
const secretValueHidden = !hasSecretReadValueOrDescribePermission(
|
||||||
@@ -1786,6 +1793,7 @@ export const secretServiceFactory = ({
|
|||||||
tagIds,
|
tagIds,
|
||||||
secretReminderNote,
|
secretReminderNote,
|
||||||
secretReminderRepeatDays,
|
secretReminderRepeatDays,
|
||||||
|
secretReminderRecipients,
|
||||||
metadata,
|
metadata,
|
||||||
secretComment,
|
secretComment,
|
||||||
newSecretName,
|
newSecretName,
|
||||||
@@ -1828,6 +1836,7 @@ export const secretServiceFactory = ({
|
|||||||
tagIds,
|
tagIds,
|
||||||
reminderNote: secretReminderNote,
|
reminderNote: secretReminderNote,
|
||||||
reminderRepeatDays: secretReminderRepeatDays,
|
reminderRepeatDays: secretReminderRepeatDays,
|
||||||
|
secretReminderRecipients,
|
||||||
secretMetadata
|
secretMetadata
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1837,8 +1846,9 @@ export const secretServiceFactory = ({
|
|||||||
}
|
}
|
||||||
const secret = await secretV2BridgeService.updateSecret({
|
const secret = await secretV2BridgeService.updateSecret({
|
||||||
secretReminderRepeatDays,
|
secretReminderRepeatDays,
|
||||||
skipMultilineEncoding,
|
|
||||||
secretReminderNote,
|
secretReminderNote,
|
||||||
|
secretReminderRecipients,
|
||||||
|
skipMultilineEncoding,
|
||||||
tagIds,
|
tagIds,
|
||||||
secretComment,
|
secretComment,
|
||||||
secretPath,
|
secretPath,
|
||||||
|
|||||||
@@ -22,9 +22,13 @@ import { SecretUpdateMode } from "../secret-v2-bridge/secret-v2-bridge-types";
|
|||||||
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
import { TSecretVersionV2DALFactory } from "../secret-v2-bridge/secret-version-dal";
|
||||||
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
import { TSecretVersionV2TagDALFactory } from "../secret-v2-bridge/secret-version-tag-dal";
|
||||||
|
|
||||||
type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote">;
|
type TPartialSecret = Pick<TSecrets, "id" | "secretReminderRepeatDays" | "secretReminderNote"> & {
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
type TPartialInputSecret = Pick<TSecrets, "type" | "secretReminderNote" | "secretReminderRepeatDays" | "id">;
|
type TPartialInputSecret = Pick<TSecrets, "type" | "secretReminderNote" | "secretReminderRepeatDays" | "id"> & {
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
export const FailedIntegrationSyncEmailsPayloadSchema = z.object({
|
export const FailedIntegrationSyncEmailsPayloadSchema = z.object({
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
@@ -258,6 +262,7 @@ export type TUpdateSecretRawDTO = TProjectPermission & {
|
|||||||
skipMultilineEncoding?: boolean;
|
skipMultilineEncoding?: boolean;
|
||||||
secretReminderRepeatDays?: number | null;
|
secretReminderRepeatDays?: number | null;
|
||||||
secretReminderNote?: string | null;
|
secretReminderNote?: string | null;
|
||||||
|
secretReminderRecipients?: string[] | null;
|
||||||
metadata?: {
|
metadata?: {
|
||||||
source?: string;
|
source?: string;
|
||||||
};
|
};
|
||||||
@@ -374,7 +379,7 @@ export type TFnSecretBulkDelete = {
|
|||||||
tx?: Knex;
|
tx?: Knex;
|
||||||
secretDAL: Pick<TSecretDALFactory, "deleteMany">;
|
secretDAL: Pick<TSecretDALFactory, "deleteMany">;
|
||||||
secretQueueService: {
|
secretQueueService: {
|
||||||
removeSecretReminder: (data: TRemoveSecretReminderDTO) => Promise<void>;
|
removeSecretReminder: (data: TRemoveSecretReminderDTO, tx?: Knex) => Promise<void>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -405,11 +410,14 @@ export type TCreateSecretReminderDTO = {
|
|||||||
oldSecret: TPartialSecret;
|
oldSecret: TPartialSecret;
|
||||||
newSecret: TPartialSecret;
|
newSecret: TPartialSecret;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|
||||||
|
deleteRecipients?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TRemoveSecretReminderDTO = {
|
export type TRemoveSecretReminderDTO = {
|
||||||
secretId: string;
|
secretId: string;
|
||||||
repeatDays: number;
|
repeatDays: number;
|
||||||
|
deleteRecipients?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TBackFillSecretReferencesDTO = TProjectPermission;
|
export type TBackFillSecretReferencesDTO = TProjectPermission;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ require (
|
|||||||
github.com/fatih/semgroup v1.2.0
|
github.com/fatih/semgroup v1.2.0
|
||||||
github.com/gitleaks/go-gitdiff v0.8.0
|
github.com/gitleaks/go-gitdiff v0.8.0
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/infisical/go-sdk v0.5.8
|
github.com/infisical/go-sdk v0.5.92
|
||||||
github.com/infisical/infisical-kmip v0.3.5
|
github.com/infisical/infisical-kmip v0.3.5
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
||||||
|
|||||||
@@ -277,8 +277,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/infisical/go-sdk v0.5.8 h1:bCetYLp7HWt8DnU9KPh1n8n3z5pjmunkGDB4bA3lEFs=
|
github.com/infisical/go-sdk v0.5.92 h1:PoCnVndrd6Dbkipuxl9fFiwlD5vCKsabtQo09mo8lUE=
|
||||||
github.com/infisical/go-sdk v0.5.8/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
github.com/infisical/go-sdk v0.5.92/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
|
||||||
github.com/infisical/infisical-kmip v0.3.5 h1:QM3s0e18B+mYv3a9HQNjNAlbwZJBzXq5BAJM2scIeiE=
|
github.com/infisical/infisical-kmip v0.3.5 h1:QM3s0e18B+mYv3a9HQNjNAlbwZJBzXq5BAJM2scIeiE=
|
||||||
github.com/infisical/infisical-kmip v0.3.5/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs=
|
github.com/infisical/infisical-kmip v0.3.5/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs=
|
||||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||||
|
|||||||
@@ -631,18 +631,18 @@ func sshConnect(cmd *cobra.Command, args []string) {
|
|||||||
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
|
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHostCaToFile, err := cmd.Flags().GetBool("writeHostCaToFile")
|
writeHostCaToFile, err := cmd.Flags().GetBool("write-host-ca-to-file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse --writeHostCaToFile flag")
|
util.HandleError(err, "Unable to parse --write-host-ca-to-file flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
outFilePath, err := cmd.Flags().GetString("outFilePath")
|
outFilePath, err := cmd.Flags().GetString("out-file-path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname, _ := cmd.Flags().GetString("hostname")
|
hostname, _ := cmd.Flags().GetString("hostname")
|
||||||
loginUser, _ := cmd.Flags().GetString("loginUser")
|
loginUser, _ := cmd.Flags().GetString("login-user")
|
||||||
|
|
||||||
var outputDir, privateKeyPath, publicKeyPath, signedKeyPath string
|
var outputDir, privateKeyPath, publicKeyPath, signedKeyPath string
|
||||||
if outFilePath != "" {
|
if outFilePath != "" {
|
||||||
@@ -722,17 +722,24 @@ func sshConnect(cmd *cobra.Command, args []string) {
|
|||||||
} else {
|
} else {
|
||||||
hostNames := make([]string, len(hosts))
|
hostNames := make([]string, len(hosts))
|
||||||
for i, h := range hosts {
|
for i, h := range hosts {
|
||||||
hostNames[i] = h.Hostname
|
if h.Alias != "" {
|
||||||
|
hostNames[i] = h.Alias
|
||||||
|
} else {
|
||||||
|
hostNames[i] = h.Hostname
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hostPrompt := promptui.Select{
|
hostPrompt := promptui.Select{
|
||||||
Label: "Select an SSH Host",
|
Label: "Select an SSH Host",
|
||||||
Items: hostNames,
|
Items: hostNames,
|
||||||
Size: 10,
|
Size: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
hostIdx, _, err := hostPrompt.Run()
|
hostIdx, _, err := hostPrompt.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Prompt failed")
|
util.HandleError(err, "Prompt failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedHost = hosts[hostIdx]
|
selectedHost = hosts[hostIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,24 +900,33 @@ func sshAddHost(cmd *cobra.Command, args []string) {
|
|||||||
util.PrintErrorMessageAndExit("You must provide --hostname")
|
util.PrintErrorMessageAndExit("You must provide --hostname")
|
||||||
}
|
}
|
||||||
|
|
||||||
writeUserCaToFile, err := cmd.Flags().GetBool("writeUserCaToFile")
|
alias, err := cmd.Flags().GetString("alias")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse --writeUserCaToFile flag")
|
util.HandleError(err, "Unable to parse --alias flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
userCaOutFilePath, err := cmd.Flags().GetString("userCaOutFilePath")
|
// if alias == "" {
|
||||||
|
// util.PrintErrorMessageAndExit("You must provide --alias")
|
||||||
|
// }
|
||||||
|
|
||||||
|
writeUserCaToFile, err := cmd.Flags().GetBool("write-user-ca-to-file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse --userCaOutFilePath flag")
|
util.HandleError(err, "Unable to parse --write-user-ca-to-file flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHostCertToFile, err := cmd.Flags().GetBool("writeHostCertToFile")
|
userCaOutFilePath, err := cmd.Flags().GetString("user-ca-out-file-path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse --writeHostCertToFile flag")
|
util.HandleError(err, "Unable to parse --user-ca-out-file-path flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
configureSshd, err := cmd.Flags().GetBool("configureSshd")
|
writeHostCertToFile, err := cmd.Flags().GetBool("write-host-cert-to-file")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse --configureSshd flag")
|
util.HandleError(err, "Unable to parse --write-host-cert-to-file flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
configureSshd, err := cmd.Flags().GetBool("configure-sshd")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse --configure-sshd flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
forceOverwrite, err := cmd.Flags().GetBool("force")
|
forceOverwrite, err := cmd.Flags().GetBool("force")
|
||||||
@@ -919,7 +935,7 @@ func sshAddHost(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if configureSshd && (!writeUserCaToFile || !writeHostCertToFile) {
|
if configureSshd && (!writeUserCaToFile || !writeHostCertToFile) {
|
||||||
util.PrintErrorMessageAndExit("--configureSshd requires both --writeUserCaToFile and --writeHostCertToFile to also be set")
|
util.PrintErrorMessageAndExit("--configure-sshd requires both --write-user-ca-to-file and --write-host-cert-to-file to also be set")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-check for file overwrites before proceeding
|
// Pre-check for file overwrites before proceeding
|
||||||
@@ -927,7 +943,7 @@ func sshAddHost(cmd *cobra.Command, args []string) {
|
|||||||
if strings.HasPrefix(userCaOutFilePath, "~") {
|
if strings.HasPrefix(userCaOutFilePath, "~") {
|
||||||
homeDir, err := os.UserHomeDir()
|
homeDir, err := os.UserHomeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to resolve ~ in userCaOutFilePath")
|
util.HandleError(err, "Unable to resolve ~ in user-ca-out-file-path")
|
||||||
}
|
}
|
||||||
userCaOutFilePath = strings.Replace(userCaOutFilePath, "~", homeDir, 1)
|
userCaOutFilePath = strings.Replace(userCaOutFilePath, "~", homeDir, 1)
|
||||||
}
|
}
|
||||||
@@ -998,6 +1014,7 @@ func sshAddHost(cmd *cobra.Command, args []string) {
|
|||||||
host, err := client.Ssh().AddSshHost(infisicalSdk.AddSshHostOptions{
|
host, err := client.Ssh().AddSshHost(infisicalSdk.AddSshHostOptions{
|
||||||
ProjectID: projectId,
|
ProjectID: projectId,
|
||||||
Hostname: hostname,
|
Hostname: hostname,
|
||||||
|
Alias: alias,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Failed to register SSH host")
|
util.HandleError(err, "Failed to register SSH host")
|
||||||
@@ -1112,11 +1129,12 @@ func init() {
|
|||||||
sshAddHostCmd.Flags().String("token", "", "Use a machine identity access token")
|
sshAddHostCmd.Flags().String("token", "", "Use a machine identity access token")
|
||||||
sshAddHostCmd.Flags().String("projectId", "", "Project ID the host belongs to (required)")
|
sshAddHostCmd.Flags().String("projectId", "", "Project ID the host belongs to (required)")
|
||||||
sshAddHostCmd.Flags().String("hostname", "", "Hostname of the SSH host (required)")
|
sshAddHostCmd.Flags().String("hostname", "", "Hostname of the SSH host (required)")
|
||||||
|
sshAddHostCmd.Flags().String("alias", "", "Alias for the SSH host")
|
||||||
sshAddHostCmd.Flags().Bool("write-user-ca-to-file", false, "Write User CA public key to /etc/ssh/infisical_user_ca.pub")
|
sshAddHostCmd.Flags().Bool("write-user-ca-to-file", false, "Write User CA public key to /etc/ssh/infisical_user_ca.pub")
|
||||||
sshAddHostCmd.Flags().String("user-ca-out-file-path", "/etc/ssh/infisical_user_ca.pub", "Custom file path to write the User CA public key")
|
sshAddHostCmd.Flags().String("user-ca-out-file-path", "/etc/ssh/infisical_user_ca.pub", "Custom file path to write the User CA public key")
|
||||||
sshAddHostCmd.Flags().Bool("write-host-cert-to-file", false, "Write SSH host certificate to /etc/ssh/ssh_host_<type>_key-cert.pub")
|
sshAddHostCmd.Flags().Bool("write-host-cert-to-file", false, "Write SSH host certificate to /etc/ssh/ssh_host_<type>_key-cert.pub")
|
||||||
sshAddHostCmd.Flags().Bool("configure-sshd", false, "Update TrustedUserCAKeys, HostKey, and HostCertificate in the sshd_config file")
|
sshAddHostCmd.Flags().Bool("configure-sshd", false, "Update `TrustedUserCAKeys`, `HostKey`, and `HostCertificate` in the `/etc/ssh/sshd_config` file")
|
||||||
sshAddHostCmd.Flags().Bool("force", false, "Force overwrite of existing certificate files as part of writeUserCaToFile and writeHostCertToFile")
|
sshAddHostCmd.Flags().Bool("force", false, "Force overwrite of existing certificate files as part of `--write-user-ca-to-file` and `--write-host-cert-to-file`")
|
||||||
|
|
||||||
sshCmd.AddCommand(sshAddHostCmd)
|
sshCmd.AddCommand(sshAddHostCmd)
|
||||||
|
|
||||||
|
|||||||
@@ -22,125 +22,72 @@ This command enables you to obtain SSH credentials used to access a remote host.
|
|||||||
<Accordion title="--hostname">
|
<Accordion title="--hostname">
|
||||||
The hostname of the SSH host to connect to. If not provided, you will be prompted to select from available hosts.
|
The hostname of the SSH host to connect to. If not provided, you will be prompted to select from available hosts.
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--loginUser">
|
<Accordion title="--login-user">
|
||||||
The login user for the SSH connection. If not provided, you will be prompted to select from available login users.
|
The login user for the SSH connection. If not provided, you will be prompted to select from available login users.
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--writeHostCaToFile">
|
<Accordion title="--write-host-ca-to-file">
|
||||||
Whether to write the Host CA public key to `~/.ssh/known_hosts` if it doesn't already exist.
|
Whether to write the Host CA public key to `~/.ssh/known_hosts` if it doesn't already exist.
|
||||||
|
|
||||||
Default value: `true`
|
Default value: `true`
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--outFilePath">
|
<Accordion title="--out-file-path">
|
||||||
The path to write the SSH credentials to such as `~/.ssh`, `./some_folder`, `./some_folder/id_rsa-cert.pub`. If not provided, the credentials will be added to the SSH agent and used to establish an interactive SSH connection.
|
The path to write the SSH credentials to such as `~/.ssh`, `./some_folder`, `./some_folder/id_rsa-cert.pub`. If not provided, the credentials will be added to the SSH agent and used to establish an interactive SSH connection.
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--token">
|
<Accordion title="--token">
|
||||||
An authenticated token to use to authenticate with Infisical.
|
Use a machine identity access token
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<Accordion title="infisical ssh issue-credentials">
|
<Accordion title="infisical ssh add-host">
|
||||||
This command is used to issue SSH credentials (SSH certificate, public key, and private key) against a certificate template.
|
This command is used to register a new SSH host with Infisical.
|
||||||
|
|
||||||
We recommend using the `--addToAgent` flag to automatically load issued SSH credentials to the SSH agent.
|
This command can be used with the `--write-user-ca-to-file`, `--write-host-cert-to-file`, and `--configure-sshd` flags
|
||||||
|
to also configure the host's SSH daemon with the necessary certificate authority and host certificate settings.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ infisical ssh issue-credentials --certificateTemplateId=<certificate-template-id> --principals=<principals> --addToAgent
|
$ infisical ssh add-host --projectId=<project-id> --hostname=<hostname>
|
||||||
```
|
```
|
||||||
|
|
||||||
### Flags
|
### Flags
|
||||||
<Accordion title="--certificateTemplateId">
|
<Accordion title="--projectId">
|
||||||
The ID of the SSH certificate template to issue SSH credentials for.
|
Project ID the host belongs to (required)
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--principals">
|
<Accordion title="--hostname">
|
||||||
A comma-separated list of principals (i.e. usernames like `ec2-user` or hostnames) to issue SSH credentials for.
|
Hostname of the SSH host (required)
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--addToAgent">
|
<Accordion title="--alias">
|
||||||
Whether to add issued SSH credentials to the SSH agent.
|
Alias for the SSH host (optional)
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="--write-user-ca-to-file">
|
||||||
|
Write User CA public key to `/etc/ssh/infisical_user_ca.pub`
|
||||||
|
|
||||||
|
Default value: `false`
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="--user-ca-out-file-path">
|
||||||
|
Custom file path to write the User CA public key
|
||||||
|
|
||||||
|
Default value: `/etc/ssh/infisical_user_ca.pub`
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="--write-host-cert-to-file">
|
||||||
|
Write SSH host certificate to `/etc/ssh/ssh_host_<type>_key-cert.pub`
|
||||||
|
|
||||||
|
Default value: `false`
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="--configure-sshd">
|
||||||
|
Update `TrustedUserCAKeys`, `HostKey`, and `HostCertificate` in the `/etc/ssh/sshd_config` file
|
||||||
|
|
||||||
Default value: `false`
|
Default value: `false`
|
||||||
|
|
||||||
Note that either the `--outFilePath` or `--addToAgent` flag must be set for the sub-command to execute successfully.
|
Note: This flag requires both --write-user-ca-to-file and --write-host-cert-to-file to be set
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--outFilePath">
|
<Accordion title="--force">
|
||||||
The path to write the SSH credentials to such as `~/.ssh`, `./some_folder`, `./some_folder/id_rsa-cert.pub`. If not provided, the credentials will be saved to the current working directory where the command is run.
|
Force overwrite of existing certificate files as part of `--write-user-ca-to-file` and `--write-host-cert-to-file`
|
||||||
|
|
||||||
Note that either the `--outFilePath` or `--addToAgent` flag must be set for the sub-command to execute successfully.
|
Default value: `false`
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--keyAlgorithm">
|
|
||||||
The key algorithm to issue SSH credentials for.
|
|
||||||
|
|
||||||
Default value: `RSA_2048`
|
|
||||||
|
|
||||||
Available options: `RSA_2048`, `RSA_4096`, `EC_prime256v1`, `EC_secp384r1`.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--certType">
|
|
||||||
The certificate type to issue SSH credentials for.
|
|
||||||
|
|
||||||
Default value: `user`
|
|
||||||
|
|
||||||
Available options: `user` or `host`
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--ttl">
|
|
||||||
The time-to-live (TTL) for the issued SSH certificate (e.g. `2 days`, `1d`, `2h`, `1y`).
|
|
||||||
|
|
||||||
Defaults to the Default TTL value set in the certificate template.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--keyId">
|
|
||||||
A custom Key ID to issue SSH credentials for.
|
|
||||||
|
|
||||||
Defaults to the autogenerated Key ID by Infisical.
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
<Accordion title="--token">
|
<Accordion title="--token">
|
||||||
An authenticated token to use to issue SSH credentials.
|
Use a machine identity access token
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
</Accordion>
|
|
||||||
|
|
||||||
<Accordion title="infisical ssh sign-key">
|
|
||||||
This command is used to sign an existing SSH public key against a certificate template; the command outputs the corresponding signed SSH certificate.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ infisical ssh sign-key --certificateTemplateId=<certificate-template-id> --publicKey=<public-key> --principals=<principals> --outFilePath=<out-file-path>
|
|
||||||
```
|
|
||||||
<Accordion title="--certificateTemplateId">
|
|
||||||
The ID of the SSH certificate template to issue the SSH certificate for.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--publicKey">
|
|
||||||
The public key to sign.
|
|
||||||
|
|
||||||
Note that either the `--publicKey` or `--publicKeyFilePath` flag must be set for the sub-command to execute successfully.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--publicKeyFilePath">
|
|
||||||
The path to the public key file to sign.
|
|
||||||
|
|
||||||
Note that either the `--publicKey` or `--publicKeyFilePath` flag must be set for the sub-command to execute successfully.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--principals">
|
|
||||||
A comma-separated list of principals (i.e. usernames like `ec2-user` or hostnames) to issue SSH credentials for.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--outFilePath">
|
|
||||||
The path to write the SSH certificate to such as `~/.ssh/id_rsa-cert.pub`; the specified file must have the `.pub` extension. If not provided, the credentials will be saved to the directory of the specified `--publicKeyFilePath` or the current working directory where the command is run.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--certType">
|
|
||||||
The certificate type to issue SSH credentials for.
|
|
||||||
|
|
||||||
Default value: `user`
|
|
||||||
|
|
||||||
Available options: `user` or `host`
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--ttl">
|
|
||||||
The time-to-live (TTL) for the issued SSH certificate (e.g. `2 days`, `1d`, `2h`, `1y`).
|
|
||||||
|
|
||||||
Defaults to the Default TTL value set in the certificate template.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--keyId">
|
|
||||||
A custom Key ID to issue SSH credentials for.
|
|
||||||
|
|
||||||
Defaults to the autogenerated Key ID by Infisical.
|
|
||||||
</Accordion>
|
|
||||||
<Accordion title="--token">
|
|
||||||
An authenticated token to use to issue SSH credentials.
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|
||||||
</Accordion>
|
</Accordion>
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
title: "Assume Privileges"
|
||||||
|
description: "Learn how to temporarily assume the privileges of a user or machine identity within a project."
|
||||||
|
---
|
||||||
|
|
||||||
|
This feature allows authorized users to temporarily take on the permissions of another user or identity. It helps administrators and access managers test and verify permissions before granting access, ensuring everything is set up correctly.
|
||||||
|
It also reduces back-and-forth with end users when troubleshooting permission-related issues.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
When an authorized user activates assume privileges mode, they temporarily inherit the target user or identity’s permissions for up to one hour.
|
||||||
|
During this time, they can perform actions within the system with the same level of access as the target user.
|
||||||
|
|
||||||
|
- **Permission-based**: Only permissions are inherited, not the full identity
|
||||||
|
- **Time-limited**: Access automatically expires after one hour
|
||||||
|
- **Audited**: All actions are logged under the original user's account. This means any action taken during the session will be recorded under the entity assuming the privileges, not the target entity.
|
||||||
|
- **Authorization required**: Only users with the specific **assume privilege** permission can use this feature
|
||||||
|
- **Scoped to a single project**: You can only assume privileges for one project at a time
|
||||||
|
|
||||||
|
## How to Assume Privileges
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Go to Project Access">
|
||||||
|
Click on the user or identity you want to assume.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Click Assume Privilege">
|
||||||
|
Click **Assume Privilege**, then type `assume` to confirm and start your session.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
|
||||||
|
<Step title="Session is Active">
|
||||||
|
You will see a yellow banner indicating that your assume privilege session is active. You can exit at any time by clicking **Exit**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: "GitHub Team Sync"
|
||||||
|
description: "Learn how to automatically synchronize your GitHub teams with Infisical Groups."
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The GitHub Organization Synchronization feature streamlines user and group management by automatically syncing users belonging to your specified GitHub organization with corresponding groups within Infisical. This integration ensures that users logging in via GitHub are automatically added to or removed from Infisical groups based on their team memberships within your GitHub organization.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
To enable and configure GitHub Organization Synchronization, follow these steps:
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Set up GitHub organization configuration">
|
||||||
|
1. Navigate to **Organization Settings** and select the **Security Tab**.
|
||||||
|

|
||||||
|
2. Click the **Configure** button and provide the name of your GitHub Organization.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Enable GitHub organization sync">
|
||||||
|
Toggle ON GitHub Organization sync to activate sync.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Approve the Infisical OAuth application on your organization">
|
||||||
|
Connecting the Infisical OAuth application grants it permission to **read:org** details. This approval is done by selecting your organization during the GitHub OAuth login process.
|
||||||
|
|
||||||
|
1. Initiate the login process via the GitHub OAuth flow.
|
||||||
|

|
||||||
|
2. Select the organization you have connected.
|
||||||
|
3. Grant access to Infisical oauth application to your configured organization. Infisical shown here is an organization, just for walkthrough.
|
||||||
|

|
||||||
|
|
||||||
|
<Info>
|
||||||
|
This action only needs to be done once and authorizes the Infisical OAuth app to read organization details, including team information.
|
||||||
|
The following users don't need to select organization in GitHub on login anymore.
|
||||||
|
</Info>
|
||||||
|
</Step>
|
||||||
|
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Working
|
||||||
|
|
||||||
|
Once configured, the GitHub Organization Synchronization feature functions as follows:
|
||||||
|
|
||||||
|
When a user logs in via the GitHub OAuth flow and selects the configured organization, the system will then automatically synchronize the teams they are a part of in GitHub with corresponding groups in Infisical.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
<Accordion title="Please check if your organization has approved the Infisical OAuth application.">
|
||||||
|
If you encounter an error related to this, it indicates that you need to approve the Infisical OAuth application within your GitHub organization.
|
||||||
|
|
||||||
|
You can verify the application's approval status by navigating to **https://github.com/organizations/__your-organization__/settings/oauth_application_policy**. Replace `__your-organization__` with the actual name of your GitHub organization.
|
||||||
|
|
||||||
|

|
||||||
|
</Accordion>
|
||||||
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 439 KiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 595 KiB |
|
After Width: | Height: | Size: 485 KiB |
|
After Width: | Height: | Size: 468 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 452 KiB |
@@ -34,7 +34,7 @@ description: "Learn how to configure a TeamCity Sync for Infisical."
|
|||||||
- **Build Configuration**: The build configuration to sync secrets to.
|
- **Build Configuration**: The build configuration to sync secrets to.
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
Not including a Build Configuration will sync secrets to the entire project.
|
Not including a Build Configuration will sync secrets to the project.
|
||||||
</Note>
|
</Note>
|
||||||
|
|
||||||
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
5. Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||||
@@ -44,6 +44,11 @@ description: "Learn how to configure a TeamCity Sync for Infisical."
|
|||||||
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
- **Overwrite Destination Secrets**: Removes any secrets at the destination endpoint not present in Infisical.
|
||||||
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over TeamCity when keys conflict.
|
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over TeamCity when keys conflict.
|
||||||
- **Import Secrets (Prioritize TeamCity)**: Imports secrets from the destination endpoint before syncing, prioritizing values from TeamCity over Infisical when keys conflict.
|
- **Import Secrets (Prioritize TeamCity)**: Imports secrets from the destination endpoint before syncing, prioritizing values from TeamCity over Infisical when keys conflict.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
Infisical only syncs secrets from within the target scope; inherited secrets will not be imported.
|
||||||
|
</Note>
|
||||||
|
|
||||||
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
|
||||||
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
|
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,7 @@
|
|||||||
},
|
},
|
||||||
"documentation/platform/access-controls/additional-privileges",
|
"documentation/platform/access-controls/additional-privileges",
|
||||||
"documentation/platform/access-controls/temporary-access",
|
"documentation/platform/access-controls/temporary-access",
|
||||||
|
"documentation/platform/access-controls/assume-privilege",
|
||||||
"documentation/platform/access-controls/access-requests",
|
"documentation/platform/access-controls/access-requests",
|
||||||
"documentation/platform/access-controls/project-access-requests",
|
"documentation/platform/access-controls/project-access-requests",
|
||||||
"documentation/platform/pr-workflows",
|
"documentation/platform/pr-workflows",
|
||||||
@@ -300,7 +301,8 @@
|
|||||||
"documentation/platform/scim/jumpcloud",
|
"documentation/platform/scim/jumpcloud",
|
||||||
"documentation/platform/scim/group-mappings"
|
"documentation/platform/scim/group-mappings"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"documentation/platform/github-org-sync"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -887,8 +889,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"group": "LDAP Password",
|
"group": "LDAP Password",
|
||||||
"pages": [
|
"pages": [
|
||||||
"api-reference/endpoints/secret-rotations/ldap-password/create",
|
"api-reference/endpoints/secret-rotations/ldap-password/create",
|
||||||
"api-reference/endpoints/secret-rotations/ldap-password/delete",
|
"api-reference/endpoints/secret-rotations/ldap-password/delete",
|
||||||
"api-reference/endpoints/secret-rotations/ldap-password/get-by-id",
|
"api-reference/endpoints/secret-rotations/ldap-password/get-by-id",
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
|
import { Button } from "../Button";
|
||||||
|
import { FormControl } from "../FormControl";
|
||||||
|
import { Input } from "../Input";
|
||||||
|
import { Modal, ModalClose, ModalContent } from "../Modal";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isOpen?: boolean;
|
||||||
|
onClose?: () => void;
|
||||||
|
onChange?: (isOpen: boolean) => void;
|
||||||
|
confirmKey: string;
|
||||||
|
title: string;
|
||||||
|
subTitle?: string;
|
||||||
|
onConfirmed: () => Promise<void>;
|
||||||
|
buttonText?: string;
|
||||||
|
formContent?: ReactNode;
|
||||||
|
children?: ReactNode;
|
||||||
|
confirmationMessage?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ConfirmActionModal = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onChange,
|
||||||
|
confirmKey,
|
||||||
|
onConfirmed,
|
||||||
|
title,
|
||||||
|
subTitle = "This action is irreversible.",
|
||||||
|
buttonText = "Yes",
|
||||||
|
formContent,
|
||||||
|
confirmationMessage,
|
||||||
|
children
|
||||||
|
}: Props): JSX.Element => {
|
||||||
|
const [inputData, setInputData] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useToggle();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInputData("");
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const onDelete = async () => {
|
||||||
|
setIsLoading.on();
|
||||||
|
try {
|
||||||
|
await onConfirmed();
|
||||||
|
} finally {
|
||||||
|
setIsLoading.off();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
isOpen={isOpen}
|
||||||
|
onOpenChange={(isOpenState) => {
|
||||||
|
setInputData("");
|
||||||
|
if (onChange) onChange(isOpenState);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ModalContent
|
||||||
|
title={title}
|
||||||
|
subTitle={subTitle}
|
||||||
|
footerContent={
|
||||||
|
<div className="mx-2 flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
isDisabled={!(confirmKey === inputData) || isLoading}
|
||||||
|
onClick={onDelete}
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
</Button>
|
||||||
|
<ModalClose asChild>
|
||||||
|
<Button variant="plain" colorSchema="secondary" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalClose>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
{formContent}
|
||||||
|
<form
|
||||||
|
onSubmit={(evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (confirmKey === inputData) onDelete();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
label={
|
||||||
|
<div className="break-words pb-2 text-sm">
|
||||||
|
{confirmationMessage || (
|
||||||
|
<>
|
||||||
|
Type <span className="font-bold">{confirmKey}</span> to perform this action
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
className="mb-0"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={inputData}
|
||||||
|
onChange={(e) => setInputData(e.target.value)}
|
||||||
|
placeholder={`Type ${confirmKey} here`}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
{children}
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ConfirmActionModal } from "./ConfirmActionModal";
|
||||||
@@ -14,7 +14,7 @@ export const PageHeader = ({ title, description, children, className }: Props) =
|
|||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<h1 className="mr-4 text-3xl font-semibold capitalize text-white">{title}</h1>
|
<h1 className="mr-4 text-3xl font-semibold capitalize text-white">{title}</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center">{children}</div>
|
<div className="flex items-center gap-2">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 text-gray-400">{description}</div>
|
<div className="mt-2 text-gray-400">{description}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
onFocus={(evt) => {
|
onFocus={(evt) => {
|
||||||
onFocus?.(evt);
|
onFocus?.(evt);
|
||||||
setIsSecretFocused.on();
|
setIsSecretFocused.on();
|
||||||
|
evt.currentTarget.select();
|
||||||
}}
|
}}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export * from "./Breadcrumb";
|
|||||||
export * from "./Button";
|
export * from "./Button";
|
||||||
export * from "./Card";
|
export * from "./Card";
|
||||||
export * from "./Checkbox";
|
export * from "./Checkbox";
|
||||||
|
export * from "./ConfirmActionModal";
|
||||||
export * from "./ContentLoader";
|
export * from "./ContentLoader";
|
||||||
export * from "./DatePicker";
|
export * from "./DatePicker";
|
||||||
export * from "./DeleteActionModal";
|
export * from "./DeleteActionModal";
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ export enum OrgPermissionSubjects {
|
|||||||
AppConnections = "app-connections",
|
AppConnections = "app-connections",
|
||||||
Kmip = "kmip",
|
Kmip = "kmip",
|
||||||
Gateway = "gateway",
|
Gateway = "gateway",
|
||||||
SecretShare = "secret-share"
|
SecretShare = "secret-share",
|
||||||
|
GithubOrgSync = "github-org-sync"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum OrgPermissionAdminConsoleAction {
|
export enum OrgPermissionAdminConsoleAction {
|
||||||
@@ -93,6 +94,7 @@ export type OrgPermissionSet =
|
|||||||
| [OrgPermissionActions, OrgPermissionSubjects.Settings]
|
| [OrgPermissionActions, OrgPermissionSubjects.Settings]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||||
|
| [OrgPermissionActions, OrgPermissionSubjects.GithubOrgSync]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
| [OrgPermissionActions, OrgPermissionSubjects.Ldap]
|
||||||
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
| [OrgPermissionGroupActions, OrgPermissionSubjects.Groups]
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ export const useProjectPermission = () => {
|
|||||||
strict: false,
|
strict: false,
|
||||||
select: (el) => el?.projectId
|
select: (el) => el?.projectId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
throw new Error("useProjectPermission to be used within <ProjectPermissionContext>");
|
throw new Error("useProjectPermission to be used within <ProjectPermissionContext>");
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: { permission, membership }
|
data: { permission, membership, assumedPrivilegeDetails }
|
||||||
} = useSuspenseQuery({
|
} = useSuspenseQuery({
|
||||||
queryKey: roleQueryKeys.getUserProjectPermissions({ workspaceId: projectId }),
|
queryKey: roleQueryKeys.getUserProjectPermissions({ workspaceId: projectId }),
|
||||||
queryFn: () => fetchUserProjectPermissions({ workspaceId: projectId }),
|
queryFn: () => fetchUserProjectPermissions({ workspaceId: projectId }),
|
||||||
@@ -29,6 +30,7 @@ export const useProjectPermission = () => {
|
|||||||
const ability = evaluatePermissionsAbility(rule);
|
const ability = evaluatePermissionsAbility(rule);
|
||||||
return {
|
return {
|
||||||
permission: ability,
|
permission: ability,
|
||||||
|
assumedPrivilegeDetails: data.assumedPrivilegeDetails,
|
||||||
membership: {
|
membership: {
|
||||||
...data.membership,
|
...data.membership,
|
||||||
roles: data.membership.roles.map(({ role }) => role)
|
roles: data.membership.roles.map(({ role }) => role)
|
||||||
@@ -42,5 +44,5 @@ export const useProjectPermission = () => {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { permission, membership, hasProjectRole };
|
return { permission, membership, hasProjectRole, assumedPrivilegeDetails };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ export enum ProjectPermissionIdentityActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
GrantPrivileges = "grant-privileges"
|
GrantPrivileges = "grant-privileges",
|
||||||
|
AssumePrivileges = "assume-privileges"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionMemberActions {
|
export enum ProjectPermissionMemberActions {
|
||||||
@@ -66,7 +67,8 @@ export enum ProjectPermissionMemberActions {
|
|||||||
Create = "create",
|
Create = "create",
|
||||||
Edit = "edit",
|
Edit = "edit",
|
||||||
Delete = "delete",
|
Delete = "delete",
|
||||||
GrantPrivileges = "grant-privileges"
|
GrantPrivileges = "grant-privileges",
|
||||||
|
AssumePrivileges = "assume-privileges"
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ProjectPermissionGroupActions {
|
export enum ProjectPermissionGroupActions {
|
||||||
@@ -247,7 +249,7 @@ export type ProjectPermissionSet =
|
|||||||
]
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
| [ProjectPermissionActions, ProjectPermissionSub.Role]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
| [ProjectPermissionActions, ProjectPermissionSub.Tags]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Member]
|
| [ProjectPermissionMemberActions, ProjectPermissionSub.Member]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Groups]
|
| [ProjectPermissionActions, ProjectPermissionSub.Groups]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
|
| [ProjectPermissionActions, ProjectPermissionSub.Integrations]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
|
| [ProjectPermissionActions, ProjectPermissionSub.Webhooks]
|
||||||
@@ -258,7 +260,7 @@ export type ProjectPermissionSet =
|
|||||||
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
| [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
| [ProjectPermissionActions, ProjectPermissionSub.SecretApproval]
|
||||||
| [
|
| [
|
||||||
ProjectPermissionActions,
|
ProjectPermissionIdentityActions,
|
||||||
(
|
(
|
||||||
| ProjectPermissionSub.Identity
|
| ProjectPermissionSub.Identity
|
||||||
| (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
| (ForcedSubject<ProjectPermissionSub.Identity> & IdentityManagementSubjectFields)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export { useAssumeProjectPrivileges, useRemoveAssumeProjectPrivilege } from "./mutations";
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { useMutation } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { TProjectAssumePrivilegesDTO } from "./types";
|
||||||
|
|
||||||
|
export const useAssumeProjectPrivileges = () =>
|
||||||
|
useMutation({
|
||||||
|
mutationFn: async ({ projectId, actorId, actorType }: TProjectAssumePrivilegesDTO) => {
|
||||||
|
const { data } = await apiRequest.post<{ message: string }>(
|
||||||
|
`/api/v1/workspace/${projectId}/assume-privileges`,
|
||||||
|
{ actorId, actorType }
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useRemoveAssumeProjectPrivilege = () =>
|
||||||
|
useMutation({
|
||||||
|
mutationFn: async ({ projectId }: { projectId: string }) => {
|
||||||
|
const { data } = await apiRequest.delete<{ message: string }>(
|
||||||
|
`/api/v1/workspace/${projectId}/assume-privileges`
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { ActorType } from "../auditLogs/enums";
|
||||||
|
|
||||||
|
export type TProjectAssumePrivilegesDTO = {
|
||||||
|
projectId: string;
|
||||||
|
actorType: ActorType;
|
||||||
|
actorId: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export {
|
||||||
|
useCreateGithubSyncOrgConfig,
|
||||||
|
useDeleteGithubSyncOrgConfig,
|
||||||
|
useUpdateGithubSyncOrgConfig
|
||||||
|
} from "./mutations";
|
||||||
|
export { githubOrgSyncConfigQueryKeys } from "./queries";
|
||||||