feat: completed github org sync

This commit is contained in:
=
2025-04-27 20:22:26 +05:30
parent dbb0b28453
commit 6d2175cf9f
32 changed files with 1398 additions and 81 deletions

View File

@@ -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",
@@ -7245,47 +7246,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==",
"dependencies": { "license": "MIT"
"@octokit/types": "^12.0.0",
"universal-user-agent": "^6.0.0"
}, },
"engines": { "node_modules/@octokit/endpoint/node_modules/@octokit/types": {
"node": ">= 18" "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/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",
@@ -7380,6 +7581,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",
@@ -7461,28 +7674,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"
}, },
@@ -7491,9 +7690,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",
@@ -7543,6 +7743,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",
@@ -11654,9 +11907,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",
@@ -19667,6 +19922,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",
@@ -19683,6 +19994,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",

View File

@@ -150,6 +150,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",

View File

@@ -15,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";
@@ -140,6 +141,7 @@ declare module "fastify" {
passportUser: { passportUser: {
isUserCompleted: boolean; isUserCompleted: boolean;
providerAuthToken: string; providerAuthToken: string;
externalProviderAccessToken?: string;
}; };
kmipUser: { kmipUser: {
projectId: string; projectId: string;
@@ -244,6 +246,7 @@ declare module "fastify" {
gateway: TGatewayServiceFactory; gateway: TGatewayServiceFactory;
secretRotationV2: TSecretRotationV2ServiceFactory; secretRotationV2: TSecretRotationV2ServiceFactory;
assumePrivileges: TAssumePrivilegeServiceFactory; 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

View File

@@ -83,6 +83,9 @@ import {
TGitAppOrg, TGitAppOrg,
TGitAppOrgInsert, TGitAppOrgInsert,
TGitAppOrgUpdate, TGitAppOrgUpdate,
TGithubOrgSyncConfigs,
TGithubOrgSyncConfigsInsert,
TGithubOrgSyncConfigsUpdate,
TGroupProjectMembershipRoles, TGroupProjectMembershipRoles,
TGroupProjectMembershipRolesInsert, TGroupProjectMembershipRolesInsert,
TGroupProjectMembershipRolesUpdate, TGroupProjectMembershipRolesUpdate,
@@ -1004,5 +1007,10 @@ declare module "knex/types/tables" {
TSecretReminderRecipientsInsert, TSecretReminderRecipientsInsert,
TSecretReminderRecipientsUpdate TSecretReminderRecipientsUpdate
>; >;
[TableName.GithubOrgSyncConfig]: KnexOriginal.CompositeTableType<
TGithubOrgSyncConfigs,
TGithubOrgSyncConfigsInsert,
TGithubOrgSyncConfigsUpdate
>;
} }
} }

View File

@@ -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);
}

View File

@@ -0,0 +1,24 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { 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>>;

View File

@@ -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";

View File

@@ -147,7 +147,8 @@ export enum TableName {
KmipClientCertificates = "kmip_client_certificates", KmipClientCertificates = "kmip_client_certificates",
SecretRotationV2 = "secret_rotations_v2", SecretRotationV2 = "secret_rotations_v2",
SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings", SecretRotationV2SecretMapping = "secret_rotation_v2_secret_mappings",
SecretReminderRecipients = "secret_reminder_recipients" SecretReminderRecipients = "secret_reminder_recipients",
GithubOrgSyncConfig = "github_org_sync_configs"
} }
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt"; export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";

View File

@@ -0,0 +1,127 @@
import { z } from "zod";
import { GithubOrgSyncConfigsSchema } from "@app/db/schemas";
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 SanitiziedGithubOrgSyncSchema = GithubOrgSyncConfigsSchema.pick({
isActive: true,
id: true,
createdAt: true,
updatedAt: true,
orgId: true,
githubOrgName: true
});
export const registerGithubOrgSyncRouter = async (server: FastifyZodProvider) => {
server.route({
url: "/",
method: "POST",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT]),
schema: {
body: z.object({
githubOrgName: z.string().trim(),
githubOrgAccessToken: z.string().trim().max(1000).optional(),
isActive: z.boolean().default(false)
}),
response: {
200: z.object({
githubOrgSyncConfig: SanitiziedGithubOrgSyncSchema
})
}
},
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: z.string().trim(),
githubOrgAccessToken: z.string().trim().max(1000),
isActive: z.boolean().default(false)
})
.partial(),
response: {
200: z.object({
githubOrgSyncConfig: SanitiziedGithubOrgSyncSchema
})
}
},
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: SanitiziedGithubOrgSyncSchema
})
}
},
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: SanitiziedGithubOrgSyncSchema
})
}
},
handler: async (req) => {
const githubOrgSyncConfig = await server.services.githubOrgSync.getGithubOrgSync({
orgPermission: req.permission
});
return { githubOrgSyncConfig };
}
});
};

View File

@@ -9,6 +9,7 @@ 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";
@@ -72,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) => {

View File

@@ -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;
};

View File

@@ -0,0 +1,344 @@
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 { 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)
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)
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: userDetails } = await octoRest.rest.users.getAuthenticated();
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: userDetails.login
}
)
.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
};
};

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -0,0 +1 @@
export const INFISICAL_PROVIDER_GITHUB_ACCESS_TOKEN = "x-infisical-github-auth-access-token";

View File

@@ -33,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";
@@ -421,6 +423,7 @@ export const registerRoutes = async (
const gatewayDAL = gatewayDALFactory(db); const gatewayDAL = gatewayDALFactory(db);
const projectGatewayDAL = projectGatewayDALFactory(db); const projectGatewayDAL = projectGatewayDALFactory(db);
const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db); const secretReminderRecipientsDAL = secretReminderRecipientsDALFactory(db);
const githubOrgSyncDAL = githubOrgSyncDALFactory(db);
const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL); const secretRotationV2DAL = secretRotationV2DALFactory(db, folderDAL);
@@ -558,6 +561,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,
@@ -1689,7 +1701,8 @@ export const registerRoutes = async (
kmipOperation: kmipOperationService, kmipOperation: kmipOperationService,
gateway: gatewayService, gateway: gatewayService,
secretRotationV2: secretRotationV2Service, secretRotationV2: secretRotationV2Service,
assumePrivileges: assumePrivilegeService assumePrivileges: assumePrivilegeService,
githubOrgSync: githubOrgSyncConfigService
}); });
const cronJobs: CronJob[] = []; const cronJobs: CronJob[] = [];
@@ -1750,30 +1763,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",

View File

@@ -14,9 +14,11 @@ import { Strategy as GitLabStrategy } from "passport-gitlab2";
import { Strategy as GoogleStrategy } from "passport-google-oauth20"; import { Strategy as GoogleStrategy } from "passport-google-oauth20";
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 { ms } from "@app/lib/ms";
import { fetchGithubEmails } from "@app/lib/requests/github"; import { fetchGithubEmails } 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";
@@ -95,7 +97,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string, clientID: appCfg.CLIENT_ID_GITHUB_LOGIN as string,
clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string, clientSecret: appCfg.CLIENT_SECRET_GITHUB_LOGIN as string,
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 // akhilmhdh: because the ts type for this is outdated by the maintainer
state: true as unknown as string state: true as unknown as string
}, },
@@ -113,7 +115,7 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
authMethod: AuthMethod.GITHUB, authMethod: AuthMethod.GITHUB,
callbackPort callbackPort
}); });
return cb(null, { isUserCompleted, providerAuthToken }); return cb(null, { isUserCompleted, providerAuthToken, externalProviderAccessToken: accessToken });
} catch (error) { } catch (error) {
logger.error(error); logger.error(error);
cb(error as Error, false); cb(error as Error, false);
@@ -290,6 +292,17 @@ export const registerSsoRouter = async (server: FastifyZodProvider) => {
}) as any, }) as any,
handler: async (req, res) => { handler: async (req, res) => {
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)}`

View File

@@ -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: "/",

View File

@@ -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;
}; };
/* /*

View File

@@ -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();

View File

@@ -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]

View File

@@ -0,0 +1,6 @@
export {
useCreateGithubSyncOrgConfig,
useDeleteGithubSyncOrgConfig,
useUpdateGithubSyncOrgConfig
} from "./mutations";
export { githubOrgSyncConfigQueryKeys } from "./queries";

View File

@@ -0,0 +1,42 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { githubOrgSyncConfigQueryKeys } from "./queries";
import { TCreateGithubOrgSyncDTO, TUpdateGithubOrgSyncDTO } from "./types";
export const useCreateGithubSyncOrgConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (dto: TCreateGithubOrgSyncDTO) => {
return apiRequest.post("/api/v1/github-org-sync-config", dto);
},
onSuccess: () => {
queryClient.invalidateQueries(githubOrgSyncConfigQueryKeys.get());
}
});
};
export const useUpdateGithubSyncOrgConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (dto: TUpdateGithubOrgSyncDTO) => {
return apiRequest.patch("/api/v1/github-org-sync-config", dto);
},
onSuccess: () => {
queryClient.invalidateQueries(githubOrgSyncConfigQueryKeys.get());
}
});
};
export const useDeleteGithubSyncOrgConfig = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => {
return apiRequest.delete("/api/v1/github-org-sync-config");
},
onSuccess: () => {
queryClient.invalidateQueries(githubOrgSyncConfigQueryKeys.get());
}
});
};

View File

@@ -0,0 +1,20 @@
import { queryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TGithubOrgSyncConfig } from "./types";
export const githubOrgSyncConfigQueryKeys = {
allKey: () => ["github-org-sync-config"],
getKey: () => [...githubOrgSyncConfigQueryKeys.allKey(), "list"],
get: () =>
queryOptions({
queryKey: githubOrgSyncConfigQueryKeys.getKey(),
queryFn: async () => {
const { data } = await apiRequest.get<{ githubOrgSyncConfig: TGithubOrgSyncConfig }>(
"/api/v1/github-org-sync-config"
);
return data.githubOrgSyncConfig;
}
})
};

View File

@@ -0,0 +1,20 @@
export type TGithubOrgSyncConfig = {
id: string;
orgId: string;
githubAccessToken: string;
githubOrgName: string;
createdAt: string;
isActive?: boolean;
};
export interface TCreateGithubOrgSyncDTO {
githubOrgName: string;
githubOrgAccessToken?: string;
isActive?: boolean;
}
export interface TUpdateGithubOrgSyncDTO {
githubOrgName?: string;
githubOrgAccessToken?: string;
isActive?: boolean;
}

View File

@@ -12,6 +12,7 @@ export * from "./certificateTemplates";
export * from "./dynamicSecret"; export * from "./dynamicSecret";
export * from "./dynamicSecretLease"; export * from "./dynamicSecretLease";
export * from "./gateways"; export * from "./gateways";
export * from "./githubOrgSyncConfig";
export * from "./groups"; export * from "./groups";
export * from "./identities"; export * from "./identities";
export * from "./identityProjectAdditionalPrivilege"; export * from "./identityProjectAdditionalPrivilege";

View File

@@ -12,6 +12,7 @@ export type SubscriptionPlan = {
customAlerts: boolean; customAlerts: boolean;
customRateLimits: boolean; customRateLimits: boolean;
pitRecovery: boolean; pitRecovery: boolean;
githubOrgSync: boolean;
ipAllowlisting: boolean; ipAllowlisting: boolean;
rbac: boolean; rbac: boolean;
secretVersioning: boolean; secretVersioning: boolean;

View File

@@ -0,0 +1,172 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, DeleteActionModal, FormControl, Input } from "@app/components/v2";
import {
useCreateGithubSyncOrgConfig,
useDeleteGithubSyncOrgConfig,
useUpdateGithubSyncOrgConfig
} from "@app/hooks/api";
import { TGithubOrgSyncConfig } from "@app/hooks/api/githubOrgSyncConfig/types";
import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = z.object({
githubOrgName: z.string(),
githubOrgAccessToken: z.string().optional()
});
export type FormData = z.infer<typeof schema>;
type Props = {
data?: TGithubOrgSyncConfig;
popUp: UsePopUpState<["githubOrgSyncConfig", "deleteGithubOrgSyncConfig"]>;
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deleteGithubOrgSyncConfig"]>,
data?: {
scimTokenId: string;
}
) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["githubOrgSyncConfig", "deleteGithubOrgSyncConfig"]>,
state?: boolean
) => void;
};
export const GithubOrgSyncConfigModal = ({
popUp,
handlePopUpOpen,
handlePopUpToggle,
data
}: Props) => {
const isUpdate = Boolean(data);
const { mutateAsync: createGithubSyncOrgConfig } = useCreateGithubSyncOrgConfig();
const { mutateAsync: updateGithubSyncOrgConfig } = useUpdateGithubSyncOrgConfig();
const { mutateAsync: deleteGithubSyncOrgConfig } = useDeleteGithubSyncOrgConfig();
const {
control,
handleSubmit,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
values: data ? { githubOrgName: data.githubOrgName } : undefined
});
const onFormSubmit = async ({ githubOrgName, githubOrgAccessToken }: FormData) => {
try {
if (isUpdate) {
await updateGithubSyncOrgConfig({
githubOrgName,
githubOrgAccessToken
});
createNotification({
text: "Successfully updated GitHub Organization Sync",
type: "success"
});
} else {
await createGithubSyncOrgConfig({
githubOrgName,
githubOrgAccessToken,
isActive: false
});
createNotification({
text: "Successfully created GitHub Organization Sync",
type: "success"
});
}
handlePopUpToggle("githubOrgSyncConfig");
} catch {
createNotification({
text: "Failed to setup GitHub Organization Sync",
type: "error"
});
}
};
const onDelete = async () => {
try {
await deleteGithubSyncOrgConfig();
handlePopUpToggle("deleteGithubOrgSyncConfig", false);
handlePopUpToggle("githubOrgSyncConfig", false);
createNotification({
text: "Successfully deleted GitHub Organization Sync",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to delete GitHub Organization Sync",
type: "error"
});
}
};
return (
<>
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="githubOrgName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="GitHub Organization Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="example" />
</FormControl>
)}
/>
{/* <Controller
control={control}
name="githubOrgAccessToken"
render={({ field, fieldState: { error } }) => (
<FormControl
label="GitHub Org Scoped Access Token"
isError={Boolean(error)}
isOptional
errorText={error?.message}
helperText="A GitHub access token is required only for private organizations. It will not be visible after saving."
>
<Input {...field} placeholder="example" />
</FormControl>
)}
/> */}
<div className="flex gap-8 pt-4">
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
{isUpdate ? "Update" : "Configure"}
</Button>
<Button
variant="plain"
colorSchema="secondary"
onClick={() => handlePopUpToggle("deleteGithubOrgSyncConfig", false)}
>
Cancel
</Button>
<div className="flex-grow" />
{isUpdate && (
<Button
onClick={() => handlePopUpOpen("deleteGithubOrgSyncConfig")}
colorSchema="danger"
>
Delete
</Button>
)}
</div>
</form>
<DeleteActionModal
isOpen={popUp.deleteGithubOrgSyncConfig.isOpen}
title="Are you sure want to remove GitHub organization sync?"
onChange={(isOpen) => handlePopUpToggle("githubOrgSyncConfig", isOpen)}
deleteKey="confirm"
onDeleteApproved={onDelete}
/>
</>
);
};

View File

@@ -18,6 +18,7 @@ import { LDAPModal } from "./LDAPModal";
import { OIDCModal } from "./OIDCModal"; import { OIDCModal } from "./OIDCModal";
import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection"; import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection";
import { OrgGenericAuthSection } from "./OrgGenericAuthSection"; import { OrgGenericAuthSection } from "./OrgGenericAuthSection";
import { OrgGithubSyncSection } from "./OrgGithubSyncSection";
import { OrgLDAPSection } from "./OrgLDAPSection"; import { OrgLDAPSection } from "./OrgLDAPSection";
import { OrgOIDCSection } from "./OrgOIDCSection"; import { OrgOIDCSection } from "./OrgOIDCSection";
import { OrgScimSection } from "./OrgSCIMSection"; import { OrgScimSection } from "./OrgSCIMSection";
@@ -181,6 +182,7 @@ export const OrgAuthTab = withPermission(
</> </>
)} )}
<OrgScimSection /> <OrgScimSection />
<OrgGithubSyncSection />
<UpgradePlanModal <UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen} isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}

View File

@@ -0,0 +1,114 @@
import { useQuery } from "@tanstack/react-query";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, Modal, ModalContent, Skeleton, Spinner, Switch } from "@app/components/v2";
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
import { githubOrgSyncConfigQueryKeys, useUpdateGithubSyncOrgConfig } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { GithubOrgSyncConfigModal } from "./GithubOrgSyncConfigModal";
export const OrgGithubSyncSection = () => {
const { subscription } = useSubscription();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"upgradePlan",
"githubOrgSyncConfig",
"deleteGithubOrgSyncConfig"
] as const);
const githubOrgSyncConfig = useQuery({
...githubOrgSyncConfigQueryKeys.get(),
enabled: subscription.githubOrgSync,
retry: false
});
const updateGithubSyncOrgConfig = useUpdateGithubSyncOrgConfig();
const isPending = subscription.githubOrgSync && githubOrgSyncConfig.isPending;
const data = !isPending && !githubOrgSyncConfig?.isError ? githubOrgSyncConfig?.data : undefined;
return (
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
<p className="text-xl font-semibold text-gray-200">
Sync user groups from your GitHub Organization
</p>
<div className="py-4">
<div className="mb-2 flex items-center justify-between">
<h2 className="text-md text-mineshaft-100">GitHub Organization</h2>
<div className="flex gap-4">
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.GithubOrgSync}>
{(isAllowed) => (
<Button
onClick={() =>
subscription.githubOrgSync
? handlePopUpOpen("githubOrgSyncConfig")
: handlePopUpOpen("upgradePlan")
}
colorSchema="secondary"
isDisabled={!isAllowed}
isLoading={isPending}
>
Configure
</Button>
)}
</OrgPermissionCan>
</div>
</div>
<p className="text-sm text-mineshaft-300">
{isPending ? <Skeleton /> : null}
{data ? data?.githubOrgName : "Not configured"}
</p>
</div>
{data && (
<div className="py-4">
<div className="mb-2 flex items-center justify-between">
<h2 className="text-md text-mineshaft-100">Enable GitHub Sync</h2>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.GithubOrgSync}>
{(isAllowed) => (
<Switch
id="enable-sync"
onCheckedChange={(value) =>
updateGithubSyncOrgConfig.mutate({
isActive: value
})
}
isChecked={githubOrgSyncConfig?.data?.isActive ?? false}
isDisabled={!isAllowed}
>
{updateGithubSyncOrgConfig?.isPending && <Spinner size="xs" />}
</Switch>
)}
</OrgPermissionCan>
</div>
<p className="text-sm text-mineshaft-300">
Allow group provisioning/deprovisioning with GitHub
</p>
</div>
)}
<Modal
isOpen={popUp?.githubOrgSyncConfig?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("githubOrgSyncConfig", isOpen);
}}
>
<ModalContent
title="Manage GitHub Organization Sync"
subTitle="Sync your GitHub teams to Infisical organization groups"
>
<GithubOrgSyncConfigModal
data={data}
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
</ModalContent>
</Modal>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use GitHub Organization Plan if you switch to Infisical's Enterprise plan."
/>
</div>
);
};