diff --git a/package-lock.json b/package-lock.json index 82657aba..c32ea13b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", + "passport-openidconnect": "^0.1.1", "socket.io": "^4.5.4", "xml2js": "^0.4.23" }, @@ -1138,6 +1139,22 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-openidconnect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.1.tgz", + "integrity": "sha512-r0QJiWEzwCg2MeCIXVP5G6YxVRqnEsZ2HpgKRthZ9AiQHJrgGUytXpsdcGF9BRwd3yMrEesb/uG/Yxb86rrY0g==", + "dependencies": { + "oauth": "0.9.x", + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -2417,6 +2434,15 @@ "utils-merge": "1.x.x" } }, + "passport-openidconnect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.1.tgz", + "integrity": "sha512-r0QJiWEzwCg2MeCIXVP5G6YxVRqnEsZ2HpgKRthZ9AiQHJrgGUytXpsdcGF9BRwd3yMrEesb/uG/Yxb86rrY0g==", + "requires": { + "oauth": "0.9.x", + "passport-strategy": "1.x.x" + } + }, "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", diff --git a/package.json b/package.json index 3190539a..95429a08 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", + "passport-openidconnect": "^0.1.1", "socket.io": "^4.5.4", "xml2js": "^0.4.23" }, diff --git a/server/Auth.js b/server/Auth.js index 16889163..40bb64ac 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -5,6 +5,7 @@ const LocalStrategy = require('passport-local') const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; const GoogleStrategy = require('passport-google-oauth20').Strategy; +var OpenIDConnectStrategy = require('passport-openidconnect'); const User = require('./objects/user/User.js') /** @@ -24,17 +25,52 @@ class Auth { if (global.ServerSettings.authActiveAuthMethods.includes("local")) { passport.use(new LocalStrategy(this.localAuthCheckUserPw.bind(this))) } + // Check if we should load the google-oauth20 strategy if (global.ServerSettings.authActiveAuthMethods.includes("google-oauth20")) { passport.use(new GoogleStrategy({ clientID: global.ServerSettings.authGoogleOauth20ClientID, clientSecret: global.ServerSettings.authGoogleOauth20ClientSecret, callbackURL: global.ServerSettings.authGoogleOauth20CallbackURL - }, function (accessToken, refreshToken, profile, done) { + }, (function (accessToken, refreshToken, profile, done) { // TODO: what to use as username // TODO: do we want to create the users which does not exist? - return done(null, { username: profile.emails[0].value }) - })) + var user = this.db.users.find(u => u.username.toLowerCase() === profile.emails[0].value.toLowerCase()) + + if (!user || !user.isActive) { + done(null, null) + return + } + + return done(null, user) + }).bind(this))) + } + + // Check if we should load the openid strategy + if (global.ServerSettings.authActiveAuthMethods.includes("openid")) { + passport.use(new OpenIDConnectStrategy({ + issuer: global.ServerSettings.authOpenIDIssuerURL, + authorizationURL: global.ServerSettings.authOpenIDAuthorizationURL, + tokenURL: global.ServerSettings.authOpenIDTokenURL, + userInfoURL: global.ServerSettings.authOpenIDUserInfoURL, + clientID: global.ServerSettings.authOpenIDClientID, + clientSecret: global.ServerSettings.authOpenIDClientSecret, + callbackURL: global.ServerSettings.authOpenIDCallbackURL, + scope: ["openid", "email", "profile"], + skipUserProfile: false + }, + (function (issuer, profile, done) { + // TODO: what to use as username + // TODO: do we want to create the users which does not exist? + var user = this.db.users.find(u => u.username.toLowerCase() === profile.emails[0].value.toLowerCase()) + + if (!user || !user.isActive) { + done(null, null) + return + } + + return done(null, user) + }).bind(this))) } // Load the JwtStrategy (always) -> for bearer token auth @@ -99,6 +135,18 @@ class Auth { }).bind(this) ) + // openid strategy login route (this redirects to the configured openid login provider) + router.get('/auth/openid', passport.authenticate('openidconnect')); + + // openid strategy callback route (this receives the token from the configured openid login provider) + router.get('/auth/openid/callback', + passport.authenticate('openidconnect', { failureRedirect: '/login' }), + (function (req, res) { + // return the user login response json if the login was successfull + res.json(this.getUserLoginResponsePayload(req.user.username)) + }).bind(this) + ) + // Logout route router.get('/logout', function (req, res) { // TODO: invalidate possible JWTs diff --git a/server/objects/settings/ServerSettings.js b/server/objects/settings/ServerSettings.js index 53e00e9b..602d8ac1 100644 --- a/server/objects/settings/ServerSettings.js +++ b/server/objects/settings/ServerSettings.js @@ -67,6 +67,14 @@ class ServerSettings { this.authGoogleOauth20ClientSecret = '' this.authGoogleOauth20CallbackURL = '' + // generic-oauth20 settings + this.authOpenIDIssuerURL = '' + this.authOpenIDAuthorizationURL = '' + this.authOpenIDTokenURL = '' + this.authOpenIDUserInfoURL = '' + this.authOpenIDClientID = '' + this.authOpenIDClientSecret = '' + this.authOpenIDCallbackURL = '' if (settings) { this.construct(settings) @@ -117,6 +125,14 @@ class ServerSettings { this.authGoogleOauth20ClientSecret = settings.authGoogleOauth20ClientSecret || '' this.authGoogleOauth20CallbackURL = settings.authGoogleOauth20CallbackURL || '' + this.authOpenIDIssuerURL = settings.authOpenIDIssuerURL || '' + this.authOpenIDAuthorizationURL = settings.authOpenIDAuthorizationURL || '' + this.authOpenIDTokenURL = settings.authOpenIDTokenURL || '' + this.authOpenIDUserInfoURL = settings.authOpenIDUserInfoURL || '' + this.authOpenIDClientID = settings.authOpenIDClientID || '' + this.authOpenIDClientSecret = settings.authOpenIDClientSecret || '' + this.authOpenIDCallbackURL = settings.authOpenIDCallbackURL || '' + if (!Array.isArray(this.authActiveAuthMethods)) { this.authActiveAuthMethods = ['local'] } @@ -131,6 +147,20 @@ class ServerSettings { this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('google-oauth20', 0), 1); } + // remove uninitialized methods + // OpenID + if (this.authActiveAuthMethods.includes('generic-oauth20') && ( + this.authOpenIDIssuerURL === '' || + this.authOpenIDAuthorizationURL === '' || + this.authOpenIDTokenURL === '' || + this.authOpenIDUserInfoURL === '' || + this.authOpenIDClientID === '' || + this.authOpenIDClientSecret === '' || + this.authOpenIDCallbackURL === '' + )) { + this.authActiveAuthMethods.splice(this.authActiveAuthMethods.indexOf('generic-oauth20', 0), 1); + } + // fallback to local if (!Array.isArray(this.authActiveAuthMethods) || this.authActiveAuthMethods.length == 0) { this.authActiveAuthMethods = ['local'] @@ -189,7 +219,14 @@ class ServerSettings { authActiveAuthMethods: this.authActiveAuthMethods, authGoogleOauth20ClientID: this.authGoogleOauth20ClientID, // Do not return to client authGoogleOauth20ClientSecret: this.authGoogleOauth20ClientSecret, // Do not return to client - authGoogleOauth20CallbackURL: this.authGoogleOauth20CallbackURL + authGoogleOauth20CallbackURL: this.authGoogleOauth20CallbackURL, + authOpenIDIssuerURL: this.authOpenIDIssuerURL, + authOpenIDAuthorizationURL: this.authOpenIDAuthorizationURL, + authOpenIDTokenURL: this.authOpenIDTokenURL, + authOpenIDUserInfoURL: this.authOpenIDUserInfoURL, + authOpenIDClientID: this.authOpenIDClientID, // Do not return to client + authOpenIDClientSecret: this.authOpenIDClientSecret, // Do not return to client + authOpenIDCallbackURL: this.authOpenIDCallbackURL } } @@ -198,6 +235,8 @@ class ServerSettings { delete json.tokenSecret delete json.authGoogleOauth20ClientID delete json.authGoogleOauth20ClientSecret + delete json.authOpenIDClientID + delete json.authOpenIDClientSecret return json }