mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
fix(deps): migrate SSO OIDC code to openid-client v6 (#492)
* chore(deps): bump the all-npm-backend group across 1 directory with 4 updates Bumps the all-npm-backend group with 4 updates in the /backend directory: [@aws-sdk/client-ecr](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-ecr), [openid-client](https://github.com/panva/openid-client), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). Updates `@aws-sdk/client-ecr` from 3.1026.0 to 3.1028.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-ecr/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1028.0/clients/client-ecr) Updates `openid-client` from 5.7.1 to 6.8.2 - [Release notes](https://github.com/panva/openid-client/releases) - [Changelog](https://github.com/panva/openid-client/blob/main/CHANGELOG.md) - [Commits](https://github.com/panva/openid-client/compare/v5.7.1...v6.8.2) Updates `@types/node` from 25.5.2 to 25.6.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `vitest` from 4.1.3 to 4.1.4 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.4/packages/vitest) --- updated-dependencies: - dependency-name: "@aws-sdk/client-ecr" dependency-version: 3.1028.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: all-npm-backend - dependency-name: openid-client dependency-version: 6.8.2 dependency-type: direct:production update-type: version-update:semver-major dependency-group: all-npm-backend - dependency-name: "@types/node" dependency-version: 25.6.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: all-npm-backend - dependency-name: vitest dependency-version: 4.1.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: all-npm-backend ... Signed-off-by: dependabot[bot] <support@github.com> * fix(deps): migrate SSO OIDC code to openid-client v6 The v5 Issuer/Client/generators API was removed upstream. Rewrite the OIDC auth flow in SSOService to use the v6 functional API: * discovery()/Configuration replace Issuer.discover + new issuer.Client * buildAuthorizationUrl replaces client.authorizationUrl * authorizationCodeGrant replaces client.callback, and handles the state check internally so the manual CSRF guard is removed * fetchUserInfo replaces client.userinfo and now receives claims.sub so v6 can reject userinfo/id_token subject mismatches Also: * Cache the discovered Configuration per provider in CacheService (TTL 5 min) so a single login flow does not pay the HTTPS discovery round trip twice. Invalidation wired into saveProviderConfig, deleteProviderConfig, and seedOidcFromEnv via a private helper. * Fix the testOidcDiscovery hack that passed a "discovery-probe" placeholder when clientId was missing; validate clientId upfront instead and return a clear error. * Log fetchUserInfo failures at warn level before falling back to id_token claims so a subject-mismatch rejection is not silently hidden. Unblocks dependabot PR #470. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
* **deps:** migrate SSO OIDC integration from `openid-client` v5 to v6. The v5 `Issuer` / `Client` / `generators` API was removed upstream; the service now uses `Configuration`, `discovery`, `buildAuthorizationUrl`, `authorizationCodeGrant`, and `fetchUserInfo`. Discovery metadata is cached for 5 minutes via `CacheService` so a single login flow does not pay the HTTPS round trip twice, and the cache is invalidated on every SSO config write. `fetchUserInfo` failures now log the underlying error instead of silently falling back to id_token claims.
|
||||
|
||||
## [0.43.0](https://github.com/AnsoCode/Sencho/compare/v0.42.7...v0.43.0) (2026-04-10)
|
||||
|
||||
|
||||
|
||||
Generated
+72
-101
@@ -35,7 +35,7 @@
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"ldapts": "^8.1.7",
|
||||
"node-pty": "^1.1.0",
|
||||
"openid-client": "^5.7.1",
|
||||
"openid-client": "^6.8.2",
|
||||
"semver": "^7.7.4",
|
||||
"systeminformation": "^5.31.1",
|
||||
"ws": "^8.19.0",
|
||||
@@ -187,9 +187,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@aws-sdk/client-ecr": {
|
||||
"version": "3.1026.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ecr/-/client-ecr-3.1026.0.tgz",
|
||||
"integrity": "sha512-w9goy0kMoOeAT2HcZEpczW0kCrv2EcnS5qyehd6rU+OlbdjfY42gOKdUOy02RvS2SUqycQnecKgU7Q09gHdKuA==",
|
||||
"version": "3.1028.0",
|
||||
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ecr/-/client-ecr-3.1028.0.tgz",
|
||||
"integrity": "sha512-NvnqmQxFtBC6eQKW5leLIfE8Go7GgBlIiFb9sfCVEMXg4i9rYAlb/Sd4reiH0AdWY/L2p+fCqOsG7Dw/DVwCoQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-browser": "5.2.0",
|
||||
@@ -2168,12 +2168,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.2.tgz",
|
||||
"integrity": "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==",
|
||||
"version": "25.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
|
||||
"integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
"undici-types": "~7.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
@@ -2512,16 +2512,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.3.tgz",
|
||||
"integrity": "sha512-CW8Q9KMtXDGHj0vCsqui0M5KqRsu0zm0GNDW7Gd3U7nZ2RFpPKSCpeCXoT+/+5zr1TNlsoQRDEz+LzZUyq6gnQ==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz",
|
||||
"integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "4.1.3",
|
||||
"@vitest/utils": "4.1.3",
|
||||
"@vitest/spy": "4.1.4",
|
||||
"@vitest/utils": "4.1.4",
|
||||
"chai": "^6.2.2",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -2530,13 +2530,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.3.tgz",
|
||||
"integrity": "sha512-XN3TrycitDQSzGRnec/YWgoofkYRhouyVQj4YNsJ5r/STCUFqMrP4+oxEv3e7ZbLi4og5kIHrZwekDJgw6hcjw==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz",
|
||||
"integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "4.1.3",
|
||||
"@vitest/spy": "4.1.4",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.21"
|
||||
},
|
||||
@@ -2557,9 +2557,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.3.tgz",
|
||||
"integrity": "sha512-hYqqwuMbpkkBodpRh4k4cQSOELxXky1NfMmQvOfKvV8zQHz8x8Dla+2wzElkMkBvSAJX5TRGHJAQvK0TcOafwg==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz",
|
||||
"integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2570,13 +2570,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.3.tgz",
|
||||
"integrity": "sha512-VwgOz5MmT0KhlUj40h02LWDpUBVpflZ/b7xZFA25F29AJzIrE+SMuwzFf0b7t4EXdwRNX61C3B6auIXQTR3ttA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz",
|
||||
"integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "4.1.3",
|
||||
"@vitest/utils": "4.1.4",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
"funding": {
|
||||
@@ -2584,14 +2584,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.3.tgz",
|
||||
"integrity": "sha512-9l+k/J9KG5wPJDX9BcFFzhhwNjwkRb8RsnYhaT1vPY7OufxmQFc9sZzScRCPTiETzl37mrIWVY9zxzmdVeJwDQ==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz",
|
||||
"integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.3",
|
||||
"@vitest/utils": "4.1.3",
|
||||
"@vitest/pretty-format": "4.1.4",
|
||||
"@vitest/utils": "4.1.4",
|
||||
"magic-string": "^0.30.21",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -2600,9 +2600,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.3.tgz",
|
||||
"integrity": "sha512-ujj5Uwxagg4XUIfAUyRQxAg631BP6e9joRiN99mr48Bg9fRs+5mdUElhOoZ6rP5mBr8Bs3lmrREnkrQWkrsTCw==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz",
|
||||
"integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
@@ -2610,13 +2610,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.3.tgz",
|
||||
"integrity": "sha512-Pc/Oexse/khOWsGB+w3q4yzA4te7W4gpZZAvk+fr8qXfTURZUMj5i7kuxsNK5mP/dEB6ao3jfr0rs17fHhbHdw==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz",
|
||||
"integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "4.1.3",
|
||||
"@vitest/pretty-format": "4.1.4",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"tinyrainbow": "^3.1.0"
|
||||
},
|
||||
@@ -4588,9 +4588,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "4.15.9",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
|
||||
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz",
|
||||
"integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
@@ -5044,18 +5044,6 @@
|
||||
"loose-envify": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
|
||||
@@ -5357,6 +5345,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/oauth4webapi": {
|
||||
"version": "3.8.5",
|
||||
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.5.tgz",
|
||||
"integrity": "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -5366,15 +5363,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-hash": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
@@ -5398,15 +5386,6 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/oidc-token-hash": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz",
|
||||
"integrity": "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^10.13.0 || >=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
@@ -5438,15 +5417,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/openid-client": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz",
|
||||
"integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==",
|
||||
"version": "6.8.2",
|
||||
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-6.8.2.tgz",
|
||||
"integrity": "sha512-uOvTCndr4udZsKihJ68H9bUICrriHdUVJ6Az+4Ns6cW55rwM5h0bjVIzDz2SxgOI84LKjFyjOFvERLzdTUROGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jose": "^4.15.9",
|
||||
"lru-cache": "^6.0.0",
|
||||
"object-hash": "^2.2.0",
|
||||
"oidc-token-hash": "^5.0.3"
|
||||
"jose": "^6.1.3",
|
||||
"oauth4webapi": "^3.8.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
@@ -6606,9 +6583,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"version": "7.19.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
@@ -6757,19 +6734,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.3.tgz",
|
||||
"integrity": "sha512-DBc4Tx0MPNsqb9isoyOq00lHftVx/KIU44QOm2q59npZyLUkENn8TMFsuzuO+4U2FUa9rgbbPt3udrP25GcjXw==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz",
|
||||
"integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/expect": "4.1.3",
|
||||
"@vitest/mocker": "4.1.3",
|
||||
"@vitest/pretty-format": "4.1.3",
|
||||
"@vitest/runner": "4.1.3",
|
||||
"@vitest/snapshot": "4.1.3",
|
||||
"@vitest/spy": "4.1.3",
|
||||
"@vitest/utils": "4.1.3",
|
||||
"@vitest/expect": "4.1.4",
|
||||
"@vitest/mocker": "4.1.4",
|
||||
"@vitest/pretty-format": "4.1.4",
|
||||
"@vitest/runner": "4.1.4",
|
||||
"@vitest/snapshot": "4.1.4",
|
||||
"@vitest/spy": "4.1.4",
|
||||
"@vitest/utils": "4.1.4",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"magic-string": "^0.30.21",
|
||||
@@ -6797,12 +6774,12 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||
"@vitest/browser-playwright": "4.1.3",
|
||||
"@vitest/browser-preview": "4.1.3",
|
||||
"@vitest/browser-webdriverio": "4.1.3",
|
||||
"@vitest/coverage-istanbul": "4.1.3",
|
||||
"@vitest/coverage-v8": "4.1.3",
|
||||
"@vitest/ui": "4.1.3",
|
||||
"@vitest/browser-playwright": "4.1.4",
|
||||
"@vitest/browser-preview": "4.1.4",
|
||||
"@vitest/browser-webdriverio": "4.1.4",
|
||||
"@vitest/coverage-istanbul": "4.1.4",
|
||||
"@vitest/coverage-v8": "4.1.4",
|
||||
"@vitest/ui": "4.1.4",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*",
|
||||
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
@@ -6955,12 +6932,6 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"ldapts": "^8.1.7",
|
||||
"node-pty": "^1.1.0",
|
||||
"openid-client": "^5.7.1",
|
||||
"openid-client": "^6.8.2",
|
||||
"semver": "^7.7.4",
|
||||
"systeminformation": "^5.31.1",
|
||||
"ws": "^8.19.0",
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
import crypto from 'crypto';
|
||||
import { Client as LdapClient } from 'ldapts';
|
||||
import { Issuer, Client as OIDCClient, generators } from 'openid-client';
|
||||
import {
|
||||
Configuration,
|
||||
discovery,
|
||||
buildAuthorizationUrl,
|
||||
authorizationCodeGrant,
|
||||
fetchUserInfo,
|
||||
randomState,
|
||||
randomPKCECodeVerifier,
|
||||
calculatePKCECodeChallenge,
|
||||
} from 'openid-client';
|
||||
import { DatabaseService, User, AuthProvider } from './DatabaseService';
|
||||
import { CryptoService } from './CryptoService';
|
||||
import { LicenseService } from './LicenseService';
|
||||
import { CacheService } from './CacheService';
|
||||
|
||||
// OIDC discovery metadata changes rarely; caching it eliminates the redundant
|
||||
// HTTPS round-trip between getOIDCAuthorizationUrl and handleOIDCCallback in
|
||||
// the same login flow, and across back-to-back logins.
|
||||
const OIDC_CONFIG_TTL_MS = 5 * 60 * 1000;
|
||||
const OIDC_CONFIG_NS = 'oidc-config';
|
||||
|
||||
export interface SSOProviderConfig {
|
||||
provider: string;
|
||||
@@ -121,6 +137,11 @@ export class SSOService {
|
||||
configForStorage.oidcClientSecret = cryptoSvc.encrypt(configForStorage.oidcClientSecret);
|
||||
}
|
||||
db.upsertSSOConfig(provider, true, JSON.stringify(configForStorage));
|
||||
this.invalidateOIDCConfigCache(provider);
|
||||
}
|
||||
|
||||
private invalidateOIDCConfigCache(provider: string): void {
|
||||
CacheService.getInstance().invalidate(`${OIDC_CONFIG_NS}:${provider}`);
|
||||
}
|
||||
|
||||
// --- Config Management ---
|
||||
@@ -173,10 +194,13 @@ export class SSOService {
|
||||
config.enabled,
|
||||
JSON.stringify(configForStorage)
|
||||
);
|
||||
// Client ID, secret, or issuer URL may have changed, so drop the cached Configuration.
|
||||
this.invalidateOIDCConfigCache(config.provider);
|
||||
}
|
||||
|
||||
public deleteProviderConfig(provider: string): void {
|
||||
DatabaseService.getInstance().deleteSSOConfig(provider);
|
||||
this.invalidateOIDCConfigCache(provider);
|
||||
}
|
||||
|
||||
// --- LDAP Authentication ---
|
||||
@@ -313,21 +337,22 @@ export class SSOService {
|
||||
throw new Error(`SSO provider ${provider} is missing client ID`);
|
||||
}
|
||||
|
||||
const { client } = await this.getOIDCClient(provider, config, callbackUrl);
|
||||
const state = generators.state();
|
||||
const codeVerifier = generators.codeVerifier();
|
||||
const codeChallenge = generators.codeChallenge(codeVerifier);
|
||||
const oidcConfig = await this.getOIDCConfig(provider, config);
|
||||
const state = randomState();
|
||||
const codeVerifier = randomPKCECodeVerifier();
|
||||
const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
|
||||
|
||||
const scopes = config.oidcScopes || 'openid email profile';
|
||||
|
||||
const url = client.authorizationUrl({
|
||||
const url = buildAuthorizationUrl(oidcConfig, {
|
||||
redirect_uri: callbackUrl,
|
||||
scope: scopes,
|
||||
state,
|
||||
code_challenge: codeChallenge,
|
||||
code_challenge_method: 'S256',
|
||||
});
|
||||
|
||||
return { url, state, codeVerifier };
|
||||
return { url: url.href, state, codeVerifier };
|
||||
}
|
||||
|
||||
public async handleOIDCCallback(
|
||||
@@ -337,39 +362,43 @@ export class SSOService {
|
||||
expectedState: string,
|
||||
codeVerifier: string
|
||||
): Promise<SSOAuthResult> {
|
||||
if (params.state !== expectedState) {
|
||||
return { success: false, error: 'Invalid state parameter (possible CSRF attack)' };
|
||||
}
|
||||
|
||||
const config = this.getProviderConfigDecrypted(provider);
|
||||
if (!config || !config.enabled) {
|
||||
return { success: false, error: `SSO provider ${provider} is not configured` };
|
||||
}
|
||||
|
||||
try {
|
||||
const { client } = await this.getOIDCClient(provider, config, callbackUrl);
|
||||
const oidcConfig = await this.getOIDCConfig(provider, config);
|
||||
|
||||
const tokenSet = await client.callback(callbackUrl, { code: params.code, state: params.state }, {
|
||||
state: expectedState,
|
||||
code_verifier: codeVerifier,
|
||||
const currentUrl = new URL(callbackUrl);
|
||||
currentUrl.searchParams.set('code', params.code);
|
||||
currentUrl.searchParams.set('state', params.state);
|
||||
|
||||
const tokens = await authorizationCodeGrant(oidcConfig, currentUrl, {
|
||||
pkceCodeVerifier: codeVerifier,
|
||||
expectedState,
|
||||
});
|
||||
|
||||
let userInfo: Record<string, unknown>;
|
||||
|
||||
if (provider === 'oidc_github') {
|
||||
// GitHub doesn't support standard OIDC userinfo; use their API
|
||||
userInfo = await this.fetchGitHubUserInfo(tokenSet.access_token as string);
|
||||
} else if (tokenSet.id_token) {
|
||||
const claims = tokenSet.claims();
|
||||
// Also fetch userinfo for complete profile
|
||||
try {
|
||||
const info = await client.userinfo(tokenSet.access_token as string);
|
||||
userInfo = { ...claims, ...info };
|
||||
} catch {
|
||||
userInfo = claims as Record<string, unknown>;
|
||||
}
|
||||
userInfo = await this.fetchGitHubUserInfo(tokens.access_token);
|
||||
} else {
|
||||
userInfo = await client.userinfo(tokenSet.access_token as string) as Record<string, unknown>;
|
||||
const claims = tokens.claims();
|
||||
if (!claims) {
|
||||
return { success: false, error: 'OIDC provider did not return an ID token' };
|
||||
}
|
||||
// Pass claims.sub so v6 rejects userinfo/id_token subject mismatches.
|
||||
try {
|
||||
const info = await fetchUserInfo(oidcConfig, tokens.access_token, String(claims.sub));
|
||||
userInfo = { ...claims, ...info };
|
||||
} catch (err) {
|
||||
// Log so a subject-mismatch rejection is not silently hidden.
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.warn(`[SSO] fetchUserInfo failed, falling back to id_token claims: ${message}`);
|
||||
userInfo = { ...claims };
|
||||
}
|
||||
}
|
||||
|
||||
const sub = String(userInfo.sub || userInfo.id || '');
|
||||
@@ -424,38 +453,37 @@ export class SSOService {
|
||||
};
|
||||
}
|
||||
|
||||
private async getOIDCClient(
|
||||
private async getOIDCConfig(
|
||||
provider: string,
|
||||
config: SSOProviderConfig,
|
||||
callbackUrl: string
|
||||
): Promise<{ client: OIDCClient; issuer: InstanceType<typeof Issuer> }> {
|
||||
let issuer: InstanceType<typeof Issuer>;
|
||||
): Promise<Configuration> {
|
||||
const clientId = config.oidcClientId || '';
|
||||
const clientSecret = config.oidcClientSecret || undefined;
|
||||
|
||||
if (provider === 'oidc_github') {
|
||||
// GitHub is not a standard OIDC provider - manually configure
|
||||
issuer = new Issuer({
|
||||
issuer: 'https://github.com',
|
||||
authorization_endpoint: 'https://github.com/login/oauth/authorize',
|
||||
token_endpoint: 'https://github.com/login/oauth/access_token',
|
||||
userinfo_endpoint: 'https://api.github.com/user',
|
||||
});
|
||||
} else {
|
||||
const issuerUrl = config.oidcIssuerUrl || WELL_KNOWN_ISSUERS[provider];
|
||||
if (!issuerUrl) {
|
||||
throw new Error(`Issuer URL not configured for ${provider}`);
|
||||
}
|
||||
issuer = await Issuer.discover(issuerUrl);
|
||||
// GitHub is not a standard OIDC provider. Construct Configuration
|
||||
// directly from known endpoints instead of going through discovery.
|
||||
return new Configuration(
|
||||
{
|
||||
issuer: 'https://github.com',
|
||||
authorization_endpoint: 'https://github.com/login/oauth/authorize',
|
||||
token_endpoint: 'https://github.com/login/oauth/access_token',
|
||||
userinfo_endpoint: 'https://api.github.com/user',
|
||||
},
|
||||
clientId,
|
||||
clientSecret,
|
||||
);
|
||||
}
|
||||
|
||||
const client = new issuer.Client({
|
||||
client_id: config.oidcClientId || '',
|
||||
client_secret: config.oidcClientSecret || '',
|
||||
redirect_uris: [callbackUrl],
|
||||
response_types: ['code'],
|
||||
token_endpoint_auth_method: 'client_secret_post',
|
||||
});
|
||||
|
||||
return { client, issuer };
|
||||
const issuerUrl = config.oidcIssuerUrl || WELL_KNOWN_ISSUERS[provider];
|
||||
if (!issuerUrl) {
|
||||
throw new Error(`Issuer URL not configured for ${provider}`);
|
||||
}
|
||||
return CacheService.getInstance().getOrFetch(
|
||||
`${OIDC_CONFIG_NS}:${provider}`,
|
||||
OIDC_CONFIG_TTL_MS,
|
||||
() => discovery(new URL(issuerUrl), clientId, clientSecret),
|
||||
);
|
||||
}
|
||||
|
||||
private resolveRoleFromOidc(userInfo: Record<string, unknown>, config: SSOProviderConfig): 'admin' | 'viewer' {
|
||||
@@ -568,17 +596,24 @@ export class SSOService {
|
||||
if (!config) {
|
||||
return { success: false, error: `Provider ${provider} not configured` };
|
||||
}
|
||||
if (provider === 'oidc_github') {
|
||||
return { success: true, issuer: 'https://github.com (OAuth2, non-standard OIDC)' };
|
||||
}
|
||||
const issuerUrl = config.oidcIssuerUrl || WELL_KNOWN_ISSUERS[provider];
|
||||
if (!issuerUrl) {
|
||||
return { success: false, error: 'Issuer URL not configured' };
|
||||
}
|
||||
if (!config.oidcClientId) {
|
||||
return { success: false, error: 'Client ID is required to test discovery' };
|
||||
}
|
||||
|
||||
try {
|
||||
if (provider === 'oidc_github') {
|
||||
return { success: true, issuer: 'https://github.com (OAuth2, non-standard OIDC)' };
|
||||
}
|
||||
const issuerUrl = config.oidcIssuerUrl || WELL_KNOWN_ISSUERS[provider];
|
||||
if (!issuerUrl) {
|
||||
return { success: false, error: 'Issuer URL not configured' };
|
||||
}
|
||||
const issuer = await Issuer.discover(issuerUrl);
|
||||
return { success: true, issuer: issuer.metadata.issuer };
|
||||
const oidcConfig = await discovery(
|
||||
new URL(issuerUrl),
|
||||
config.oidcClientId,
|
||||
config.oidcClientSecret || undefined,
|
||||
);
|
||||
return { success: true, issuer: oidcConfig.serverMetadata().issuer };
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Discovery failed';
|
||||
return { success: false, error: message };
|
||||
|
||||
Reference in New Issue
Block a user