From e8d719c438f773c6442d7ff17cabfef99e343fab Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 18 Oct 2023 15:39:54 +0200 Subject: [PATCH] Fix OIDC sso issues --- config/sso.example.js | 3 +- customize.dist/login.js | 4 +- lib/plugins/sso/oidc.js | 54 +++++--- lib/sso-utils.js | 12 +- package-lock.json | 249 +++++++++++++++++++++++++++++++++++++ package.json | 1 + www/common/common-login.js | 2 +- www/ssoauth/main.js | 8 +- 8 files changed, 306 insertions(+), 27 deletions(-) diff --git a/config/sso.example.js b/config/sso.example.js index a1b7289ab..35d1f2b98 100644 --- a/config/sso.example.js +++ b/config/sso.example.js @@ -13,7 +13,8 @@ module.exports = { type: 'oidc', url: 'https://accounts.google.com', client_id: "{your_client_id}", - client_secret: "{your_client_secret}" + client_secret: "{your_client_secret}", + jwt_alg: 'RS256' (optional) } */ ] diff --git a/customize.dist/login.js b/customize.dist/login.js index 60559fc31..0f102023a 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -113,8 +113,8 @@ define([ // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed // after hashing the password window.setTimeout(function () { - Exports.loginOrRegister({ - uname, + Login.loginOrRegister({ + uname, passwd, isRegister, onOTP diff --git a/lib/plugins/sso/oidc.js b/lib/plugins/sso/oidc.js index 8d2d72f3f..380c214b8 100644 --- a/lib/plugins/sso/oidc.js +++ b/lib/plugins/sso/oidc.js @@ -1,14 +1,22 @@ const OID = require('openid-client'); +const SSOUtils = require('../../sso-utils'); const TYPE = 'oidc'; +const opts = SSOUtils.getoptions(); const getClient = (cfg, cb) => { OID.Issuer.discover(cfg.url).then((issuer) => { // XXX Only once for all users? + let alg = cfg.jwt_alg || 'PS256'; + if (Array.isArray(issuer.id_token_signing_alg_values_supported) && + !issuer.id_token_signing_alg_values_supported.includes(alg)) { + alg = issuer.id_token_signing_alg_values_supported[0] || 'RS256'; + } const client = new issuer.Client({ client_id: cfg.client_id, client_secret: cfg.client_secret, - redirect_uris: ['http://localhost:3000/ssoauth'], // XXX Use httpUnsafeOrigin or... + redirect_uris: [opts.callbackURL], response_types: ['code'], + id_token_signed_response_alg: alg }); cb(void 0, client); }, (err) => { @@ -28,8 +36,8 @@ module.exports = { const code_verifier = generators.codeVerifier(); const code_challenge = generators.codeChallenge(code_verifier); const url = client.authorizationUrl({ - scope: 'openid email profile',// https://www.googleapis.com/auth/contacts.readonly', // https://www.google.com/m8/feeds - resource: 'http://localhost:3000/ssoauth/', + scope: 'openid email profile', + resource: opts.callbackURL, access_type: 'offline', code_challenge, code_challenge_method: 'S256', @@ -43,29 +51,35 @@ module.exports = { if (err) { return void cb ('E_OIDC_CONNECT'); } const params = client.callbackParams(url); + delete params.state; client.callback('http://localhost:3000/ssoauth', params, { code_verifier: token }) .then((tokenSet) => { let j = tokenSet; let c = tokenSet.claims(); - cb(void 0, { - id: c.sub, - name: c.name, - idpData: { - expires_at: j.expires_at, - access_token: j.access_token, - refresh_token: j.refresh_token, - //id_token: j.id_token // XXX no need to store id_token? - } + let name = c.name; + const end = () => { + cb(void 0, { + id: c.sub, + name: name, + idpData: { + expires_at: j.expires_at, + access_token: j.access_token, + refresh_token: j.refresh_token, + //id_token: j.id_token // XXX no need to store id_token? + } + }); + }; + if (name) { return void end(); } + let t = client.userinfo(j.access_token).then((data) => { + name = data.name; + end(); + console.log(t); + }, (err) => { + console.error(err); + name = 'Unknown'; // XXX + end(); }); }); }); }, - /* - getData: (Env, cfg, data, cb) => { - // data = { refresh_token, access_token, expires_at } - let t = new OID.TokenSet(data); - // TODO get userinfo using access_token - // use refresh_token if access expired - } - */ }; diff --git a/lib/sso-utils.js b/lib/sso-utils.js index 9a6a069fb..61db7eb73 100644 --- a/lib/sso-utils.js +++ b/lib/sso-utils.js @@ -3,14 +3,24 @@ const Sessions = require("./storage/sessions"); const Nacl = require("tweetnacl/nacl-fast"); const JWT = require("jsonwebtoken"); const Util = require("./common-util"); +const config = require("./lib/load-config"); const SSOUtils = module.exports; const SESSION_EXPIRATIION = 12 * 3600 * 1000; // XXX Hours? Days? Weeks? Configurable? + + +SSOUtils.getOptions = () => { + return { + callbackURL: config.httpUnsafeOrigin + '/ssoauth' + }; +}; + // XXX const SAML = require('node-saml'); // https://www.npmjs.com/package/node-saml const TYPES = SSOUtils.TYPES = { - oidc: require('./plugins/sso/oidc') + oidc: require('./plugins/sso/oidc'), + saml: require('./plugins/sso/saml') }; const checkConfig = SSOUtils.checkConfig = (Env) => { diff --git a/package-lock.json b/package-lock.json index a13c658a4..0f4c0c109 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "AGPL-3.0+", "dependencies": { "@mcrowe/minibloom": "^0.2.0", + "@node-saml/node-saml": "^4.0.5", "alertify.js": "1.0.11", "bootstrap": "^4.0.0", "bootstrap-tokenfield": "^0.12.0", @@ -88,6 +89,48 @@ "node": ">=4" } }, + "node_modules/@node-saml/node-saml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@node-saml/node-saml/-/node-saml-4.0.5.tgz", + "integrity": "sha512-J5DglElbY1tjOuaR1NPtjOXkXY5bpUhDoKVoeucYN98A3w4fwgjIOPqIGcb6cQsqFq2zZ6vTCeKn5C/hvefSaw==", + "dependencies": { + "@types/debug": "^4.1.7", + "@types/passport": "^1.0.11", + "@types/xml-crypto": "^1.4.2", + "@types/xml-encryption": "^1.2.1", + "@types/xml2js": "^0.4.11", + "@xmldom/xmldom": "^0.8.6", + "debug": "^4.3.4", + "xml-crypto": "^3.0.1", + "xml-encryption": "^3.0.2", + "xml2js": "^0.5.0", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@node-saml/node-saml/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@node-saml/node-saml/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", @@ -97,6 +140,53 @@ "node": ">= 6" } }, + "node_modules/@types/body-parser": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", + "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.37", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", + "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.10.tgz", + "integrity": "sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", + "integrity": "sha512-rOaqlkgEvOW495xErXMsmyX3WKBInbhG5eqojXYi3cGUaLoRDlXa5d52fkfWZT963AZ3v2eZ4MbKE6WpDAGVsw==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.38", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.38.tgz", + "integrity": "sha512-hXOtc0tuDHZPFwwhuBJXPbjemWtXnJjbvuuyNH2Y5Z6in+iXc63c4eXYDc7GGGqHy+iwYqAJMdaItqdnbcBKmg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", @@ -107,6 +197,11 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==" + }, "node_modules/@types/http-proxy": { "version": "1.17.12", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.12.tgz", @@ -115,17 +210,89 @@ "@types/node": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" + }, "node_modules/@types/minimatch": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true }, + "node_modules/@types/ms": { + "version": "0.7.33", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz", + "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==" + }, "node_modules/@types/node": { "version": "20.6.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.3.tgz", "integrity": "sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA==" }, + "node_modules/@types/passport": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.14.tgz", + "integrity": "sha512-D6p2ygR2S7Cq5PO7iUaEIQu/5WrM0tONu6Lxgk0C9r3lafQIlVpWCo3V/KI9To3OqHBxcfQaOeK+8AvwW5RYmw==", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" + }, + "node_modules/@types/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", + "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", + "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/xml-crypto": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/xml-crypto/-/xml-crypto-1.4.3.tgz", + "integrity": "sha512-pnvKYb7vUsUIMc+C6JM/j779YWQgOMcwjnqHJ9cdaWXwWEBE1hAqthzeszRx62V5RWMvS+XS9w9tXMOYyUc8zg==", + "dependencies": { + "@types/node": "*", + "xpath": "0.0.27" + } + }, + "node_modules/@types/xml-encryption": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/xml-encryption/-/xml-encryption-1.2.2.tgz", + "integrity": "sha512-UeuYOqW3ZzUQfwb/mb3GNZ2/DlVdh5mjJNmB/yFXgQr8/pwlVJ9I2w+AHPfRDzLshe7YpgUB4T1//qgbk6U87Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/xml2js": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.12.tgz", + "integrity": "sha512-CZPpQKBZ8db66EP5hCjwvYrLThgZvnyZrPXK2W+UI1oOaWezGt34iOaUCX4Jah2X8+rQqjvl9VKEIT8TR1I0rA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -3939,6 +4106,11 @@ "resolved": "https://registry.npmjs.org/saferphore/-/saferphore-0.0.1.tgz", "integrity": "sha512-/KaXQyumYbALQwhl4/Qov6voayHwtBD12AWRI3zxYBW7cY3Mjtkk7PLttZPq+25nyfu/k3APHUptPLZI9A7yHA==" }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" + }, "node_modules/scrypt-async": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/scrypt-async/-/scrypt-async-1.2.0.tgz", @@ -4869,6 +5041,83 @@ "node": "*" } }, + "node_modules/xml-crypto": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-3.2.0.tgz", + "integrity": "sha512-qVurBUOQrmvlgmZqIVBqmb06TD2a/PpEUfFPgD7BuBfjmoH4zgkqaWSIJrnymlCvM2GGt9x+XtJFA+ttoAufqg==", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "xpath": "0.0.32" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml-crypto/node_modules/xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/xml-encryption": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/xml-encryption/-/xml-encryption-3.0.2.tgz", + "integrity": "sha512-VxYXPvsWB01/aqVLd6ZMPWZ+qaj0aIdF+cStrVJMcFj3iymwZeI0ABzB3VqMYv48DkSpRhnrXqTUkR34j+UDyg==", + "dependencies": { + "@xmldom/xmldom": "^0.8.5", + "escape-html": "^1.0.3", + "xpath": "0.0.32" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/xml-encryption/node_modules/xpath": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.32.tgz", + "integrity": "sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw==", + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xml2js/node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xpath": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", + "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==", + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 58278be02..9cba9046e 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "notp": "^2.0.3", "nthen": "0.1.8", "openid-client": "^5.4.2", + "@node-saml/node-saml": "^4.0.5", "prompt-confirm": "^2.0.4", "pull-stream": "^3.6.1", "saferphore": "0.0.1", diff --git a/www/common/common-login.js b/www/common/common-login.js index b9ec62da2..d2776dcb7 100644 --- a/www/common/common-login.js +++ b/www/common/common-login.js @@ -301,7 +301,6 @@ define([ register: isRegister, uname: uname }; - if (ssoAuth && ssoAuth.name) { uname = res.uname = ssoAuth.name; } var RT, blockKeys, blockUrl; @@ -312,6 +311,7 @@ define([ res.opt = allocateBytes(bytes); res.blockHash = res.opt.blockHash; blockKeys = res.opt.blockKeys; + if (ssoAuth && ssoAuth.name) { uname = res.uname = ssoAuth.name; } })); }).nThen(function (waitFor) { // the allocated bytes can be used either in a legacy fashion, diff --git a/www/ssoauth/main.js b/www/ssoauth/main.js index 3c5056ef5..758e823ce 100644 --- a/www/ssoauth/main.js +++ b/www/ssoauth/main.js @@ -51,8 +51,12 @@ define([ } }, function (err) { if (err) { - console.error(err); - return void UI.warn(Messages.error); + UI.removeLoadingScreen(); + var msg = Messages.error; + if (err === 'NO_SUCH_USER') { msg = Messages.drive_sfPasswordError; } + let $button = $('button#cp-ssoauth-button'); + $button.prop('disabled', ''); + return void UI.warn(msg); } window.location.href = '/drive/'; });