diff options
author | Christoph (Sheogorath) Kern | 2018-06-04 15:54:47 +0200 |
---|---|---|
committer | GitHub | 2018-06-04 15:54:47 +0200 |
commit | 551840ad57125fa168247c21613f9d6de6cd5d99 (patch) | |
tree | 51dda6767b42acc445fb169ea8ea6b4fe19050ac | |
parent | 3f7a33df646c4db3de449bd486a7dfddc9ba6e6b (diff) | |
parent | 34df7ccce83bebec1db3b6995eb25cb6fe4fdbd4 (diff) |
Merge pull request #784 from pferreir/add-oauth2-support
Add "generic" OAuth2 support
Diffstat (limited to '')
-rw-r--r-- | lib/config/default.js | 6 | ||||
-rw-r--r-- | lib/config/environment.js | 11 | ||||
-rw-r--r-- | lib/config/index.js | 1 | ||||
-rw-r--r-- | lib/migrations/20180326103000-use-text-in-tokens.js | 23 | ||||
-rw-r--r-- | lib/models/user.js | 4 | ||||
-rw-r--r-- | lib/response.js | 4 | ||||
-rw-r--r-- | lib/web/auth/index.js | 1 | ||||
-rw-r--r-- | lib/web/auth/oauth2/index.js | 106 | ||||
-rw-r--r-- | public/views/index/body.ejs | 4 | ||||
-rw-r--r-- | public/views/shared/signin-modal.ejs | 9 |
10 files changed, 163 insertions, 6 deletions
diff --git a/lib/config/default.js b/lib/config/default.js index 30ce2090..1b124b3e 100644 --- a/lib/config/default.js +++ b/lib/config/default.js @@ -79,6 +79,12 @@ module.exports = { container: undefined }, // authentication + oauth2: { + authorizationURL: undefined, + tokenURL: undefined, + clientID: undefined, + clientSecret: undefined + }, facebook: { clientID: undefined, clientSecret: undefined diff --git a/lib/config/environment.js b/lib/config/environment.js index 810cb225..e1c11569 100644 --- a/lib/config/environment.js +++ b/lib/config/environment.js @@ -72,6 +72,17 @@ module.exports = { clientID: process.env.HMD_MATTERMOST_CLIENTID, clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET }, + oauth2: { + baseURL: process.env.HMD_OAUTH2_BASEURL, + userProfileURL: process.env.HMD_OAUTH2_USER_PROFILE_URL, + userProfileUsernameAttr: process.env.HMD_OAUTH2_USER_PROFILE_USERNAME_ATTR, + userProfileDisplayNameAttr: process.env.HMD_OAUTH2_USER_PROFILE_DISPLAY_NAME_ATTR, + userProfileEmailAttr: process.env.HMD_OAUTH2_USER_PROFILE_EMAIL_ATTR, + tokenURL: process.env.HMD_OAUTH2_TOKEN_URL, + authorizationURL: process.env.HMD_OAUTH2_AUTHORIZATION_URL, + clientID: process.env.HMD_OAUTH2_CLIENT_ID, + clientSecret: process.env.HMD_OAUTH2_CLIENT_SECRET + }, dropbox: { clientID: process.env.HMD_DROPBOX_CLIENTID, clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET, diff --git a/lib/config/index.js b/lib/config/index.js index f10eadb8..484301c4 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -99,6 +99,7 @@ config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret config.isLDAPEnable = config.ldap.url config.isSAMLEnable = config.saml.idpSsoUrl +config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret config.isPDFExportEnable = config.allowPDFExport // merge legacy values diff --git a/lib/migrations/20180326103000-use-text-in-tokens.js b/lib/migrations/20180326103000-use-text-in-tokens.js new file mode 100644 index 00000000..f9507747 --- /dev/null +++ b/lib/migrations/20180326103000-use-text-in-tokens.js @@ -0,0 +1,23 @@ +'use strict' + +module.exports = { + up: function (queryInterface, Sequelize) { + return queryInterface.changeColumn('Users', 'accessToken', { + type: Sequelize.TEXT + }).then(function () { + return queryInterface.changeColumn('Users', 'refreshToken', { + type: Sequelize.TEXT + }) + }) + }, + + down: function (queryInterface, Sequelize) { + return queryInterface.changeColumn('Users', 'accessToken', { + type: Sequelize.STRING + }).then(function () { + return queryInterface.changeColumn('Users', 'refreshToken', { + type: Sequelize.STRING + }) + }) + } +} diff --git a/lib/models/user.js b/lib/models/user.js index 4c823355..0f6acd65 100644 --- a/lib/models/user.js +++ b/lib/models/user.js @@ -26,10 +26,10 @@ module.exports = function (sequelize, DataTypes) { type: DataTypes.TEXT }, accessToken: { - type: DataTypes.STRING + type: DataTypes.TEXT }, refreshToken: { - type: DataTypes.STRING + type: DataTypes.TEXT }, email: { type: Sequelize.TEXT, diff --git a/lib/response.js b/lib/response.js index d6fb3b42..89ade7a7 100644 --- a/lib/response.js +++ b/lib/response.js @@ -76,6 +76,8 @@ function showIndex (req, res, next) { ldap: config.isLDAPEnable, ldapProviderName: config.ldap.providerName, saml: config.isSAMLEnable, + oauth2: config.isOAuth2Enable, + oauth2ProviderName: config.oauth2.providerName, email: config.isEmailEnable, allowEmailRegister: config.allowEmailRegister, allowPDFExport: config.allowPDFExport, @@ -110,7 +112,9 @@ function responseHackMD (res, note) { google: config.isGoogleEnable, ldap: config.isLDAPEnable, ldapProviderName: config.ldap.providerName, + oauth2ProviderName: config.oauth2.providerName, saml: config.isSAMLEnable, + oauth2: config.isOAuth2Enable, email: config.isEmailEnable, allowEmailRegister: config.allowEmailRegister, allowPDFExport: config.allowPDFExport diff --git a/lib/web/auth/index.js b/lib/web/auth/index.js index eb42fb36..61e7c3f9 100644 --- a/lib/web/auth/index.js +++ b/lib/web/auth/index.js @@ -43,6 +43,7 @@ if (config.isDropboxEnable) authRouter.use(require('./dropbox')) if (config.isGoogleEnable) authRouter.use(require('./google')) if (config.isLDAPEnable) authRouter.use(require('./ldap')) if (config.isSAMLEnable) authRouter.use(require('./saml')) +if (config.isOAuth2Enable) authRouter.use(require('./oauth2')) if (config.isEmailEnable) authRouter.use(require('./email')) // logout diff --git a/lib/web/auth/oauth2/index.js b/lib/web/auth/oauth2/index.js new file mode 100644 index 00000000..f2a3132d --- /dev/null +++ b/lib/web/auth/oauth2/index.js @@ -0,0 +1,106 @@ +'use strict' + +const Router = require('express').Router +const passport = require('passport') +const OAuth2Strategy = require('passport-oauth2').Strategy +const config = require('../../../config') +const {setReturnToFromReferer, passportGeneralCallback} = require('../utils') + +let oauth2Auth = module.exports = Router() + +class OAuth2CustomStrategy extends OAuth2Strategy { + constructor (options, verify) { + options.customHeaders = options.customHeaders || {} + super(options, verify) + this.name = 'oauth2' + this._userProfileURL = options.userProfileURL + this._oauth2.useAuthorizationHeaderforGET(true) + } + + userProfile (accessToken, done) { + this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { + var json + + if (err) { + return done(new passport.InternalOAuthError('Failed to fetch user profile', err)) + } + + try { + json = JSON.parse(body) + } catch (ex) { + return done(new Error('Failed to parse user profile')) + } + + let profile = parseProfile(json) + profile.provider = 'oauth2' + + done(null, profile) + }) + } +} + +function extractProfileAttribute (data, path) { + // can handle stuff like `attrs[0].name` + path = path.split('.') + for (const segment of path) { + const m = segment.match(/([\d\w]+)\[(.*)\]/) + data = m ? data[m[1]][m[2]] : data[segment] + } + return data +} + +function parseProfile (data) { + const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr) + const displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr) + const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr) + + return { + id: username, + username: username, + displayName: displayName, + email: email + } +} + +OAuth2CustomStrategy.prototype.userProfile = function (accessToken, done) { + this._oauth2.get(this._userProfileURL, accessToken, function (err, body, res) { + var json + + if (err) { + return done(new passport.InternalOAuthError('Failed to fetch user profile', err)) + } + + try { + json = JSON.parse(body) + } catch (ex) { + return done(new Error('Failed to parse user profile')) + } + + let profile = parseProfile(json) + profile.provider = 'oauth2' + + done(null, profile) + }) +} + +passport.use(new OAuth2CustomStrategy({ + authorizationURL: config.oauth2.authorizationURL, + tokenURL: config.oauth2.tokenURL, + clientID: config.oauth2.clientID, + clientSecret: config.oauth2.clientSecret, + callbackURL: config.serverURL + '/auth/oauth2/callback', + userProfileURL: config.oauth2.userProfileURL +}, passportGeneralCallback)) + +oauth2Auth.get('/auth/oauth2', function (req, res, next) { + setReturnToFromReferer(req) + passport.authenticate('oauth2')(req, res, next) +}) + +// github auth callback +oauth2Auth.get('/auth/oauth2/callback', + passport.authenticate('oauth2', { + successReturnToOrRedirect: config.serverurl + '/', + failureRedirect: config.serverurl + '/' + }) +) diff --git a/public/views/index/body.ejs b/public/views/index/body.ejs index d8766fec..bf7988b7 100644 --- a/public/views/index/body.ejs +++ b/public/views/index/body.ejs @@ -15,7 +15,7 @@ <% if(allowAnonymous) { %> <a type="button" href="<%- url %>/new" class="btn btn-sm btn-primary"><i class="fa fa-plus"></i> <%= __('New guest note') %></a> <% } %> - <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || email) { %> + <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || oauth2 || email) { %> <button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button> <% } %> </div> @@ -49,7 +49,7 @@ <% if (errorMessage && errorMessage.length > 0) { %> <div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div> <% } %> - <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || email) { %> + <% if(facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || saml || oauth2 || email) { %> <span class="ui-signin"> <br> <a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="min-width: 200px;"><%= __('Sign In') %></a> diff --git a/public/views/shared/signin-modal.ejs b/public/views/shared/signin-modal.ejs index 82b5cf1f..f0cffad9 100644 --- a/public/views/shared/signin-modal.ejs +++ b/public/views/shared/signin-modal.ejs @@ -48,7 +48,12 @@ <i class="fa fa-users"></i> <%= __('Sign in via %s', 'SAML') %> </a> <% } %> - <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || saml) && ldap) { %> + <% if(oauth2) { %> + <a href="<%- url %>/auth/oauth2" class="btn btn-lg btn-block btn-social btn-soundcloud"> + <i class="fa fa-mail-forward"></i> <%= __('Sign in via %s', oauth2ProviderName || 'OAuth2') %> + </a> + <% } %> + <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || saml || oauth2) && ldap) { %> <hr> <% }%> <% if(ldap) { %> @@ -73,7 +78,7 @@ </div> </form> <% } %> - <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap) && email) { %> + <% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || oauth2) && email) { %> <hr> <% }%> <% if(email) { %> |