mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
Merge pull request #784 from pferreir/add-oauth2-support
Add "generic" OAuth2 support
This commit is contained in:
commit
551840ad57
10 changed files with 163 additions and 6 deletions
|
@ -79,6 +79,12 @@ module.exports = {
|
||||||
container: undefined
|
container: undefined
|
||||||
},
|
},
|
||||||
// authentication
|
// authentication
|
||||||
|
oauth2: {
|
||||||
|
authorizationURL: undefined,
|
||||||
|
tokenURL: undefined,
|
||||||
|
clientID: undefined,
|
||||||
|
clientSecret: undefined
|
||||||
|
},
|
||||||
facebook: {
|
facebook: {
|
||||||
clientID: undefined,
|
clientID: undefined,
|
||||||
clientSecret: undefined
|
clientSecret: undefined
|
||||||
|
|
|
@ -72,6 +72,17 @@ module.exports = {
|
||||||
clientID: process.env.HMD_MATTERMOST_CLIENTID,
|
clientID: process.env.HMD_MATTERMOST_CLIENTID,
|
||||||
clientSecret: process.env.HMD_MATTERMOST_CLIENTSECRET
|
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: {
|
dropbox: {
|
||||||
clientID: process.env.HMD_DROPBOX_CLIENTID,
|
clientID: process.env.HMD_DROPBOX_CLIENTID,
|
||||||
clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET,
|
clientSecret: process.env.HMD_DROPBOX_CLIENTSECRET,
|
||||||
|
|
|
@ -99,6 +99,7 @@ config.isGitLabEnable = config.gitlab.clientID && config.gitlab.clientSecret
|
||||||
config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
|
config.isMattermostEnable = config.mattermost.clientID && config.mattermost.clientSecret
|
||||||
config.isLDAPEnable = config.ldap.url
|
config.isLDAPEnable = config.ldap.url
|
||||||
config.isSAMLEnable = config.saml.idpSsoUrl
|
config.isSAMLEnable = config.saml.idpSsoUrl
|
||||||
|
config.isOAuth2Enable = config.oauth2.clientID && config.oauth2.clientSecret
|
||||||
config.isPDFExportEnable = config.allowPDFExport
|
config.isPDFExportEnable = config.allowPDFExport
|
||||||
|
|
||||||
// merge legacy values
|
// merge legacy values
|
||||||
|
|
23
lib/migrations/20180326103000-use-text-in-tokens.js
Normal file
23
lib/migrations/20180326103000-use-text-in-tokens.js
Normal file
|
@ -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
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,10 +26,10 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
type: DataTypes.TEXT
|
type: DataTypes.TEXT
|
||||||
},
|
},
|
||||||
accessToken: {
|
accessToken: {
|
||||||
type: DataTypes.STRING
|
type: DataTypes.TEXT
|
||||||
},
|
},
|
||||||
refreshToken: {
|
refreshToken: {
|
||||||
type: DataTypes.STRING
|
type: DataTypes.TEXT
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: Sequelize.TEXT,
|
type: Sequelize.TEXT,
|
||||||
|
|
|
@ -76,6 +76,8 @@ function showIndex (req, res, next) {
|
||||||
ldap: config.isLDAPEnable,
|
ldap: config.isLDAPEnable,
|
||||||
ldapProviderName: config.ldap.providerName,
|
ldapProviderName: config.ldap.providerName,
|
||||||
saml: config.isSAMLEnable,
|
saml: config.isSAMLEnable,
|
||||||
|
oauth2: config.isOAuth2Enable,
|
||||||
|
oauth2ProviderName: config.oauth2.providerName,
|
||||||
email: config.isEmailEnable,
|
email: config.isEmailEnable,
|
||||||
allowEmailRegister: config.allowEmailRegister,
|
allowEmailRegister: config.allowEmailRegister,
|
||||||
allowPDFExport: config.allowPDFExport,
|
allowPDFExport: config.allowPDFExport,
|
||||||
|
@ -110,7 +112,9 @@ function responseHackMD (res, note) {
|
||||||
google: config.isGoogleEnable,
|
google: config.isGoogleEnable,
|
||||||
ldap: config.isLDAPEnable,
|
ldap: config.isLDAPEnable,
|
||||||
ldapProviderName: config.ldap.providerName,
|
ldapProviderName: config.ldap.providerName,
|
||||||
|
oauth2ProviderName: config.oauth2.providerName,
|
||||||
saml: config.isSAMLEnable,
|
saml: config.isSAMLEnable,
|
||||||
|
oauth2: config.isOAuth2Enable,
|
||||||
email: config.isEmailEnable,
|
email: config.isEmailEnable,
|
||||||
allowEmailRegister: config.allowEmailRegister,
|
allowEmailRegister: config.allowEmailRegister,
|
||||||
allowPDFExport: config.allowPDFExport
|
allowPDFExport: config.allowPDFExport
|
||||||
|
|
|
@ -43,6 +43,7 @@ if (config.isDropboxEnable) authRouter.use(require('./dropbox'))
|
||||||
if (config.isGoogleEnable) authRouter.use(require('./google'))
|
if (config.isGoogleEnable) authRouter.use(require('./google'))
|
||||||
if (config.isLDAPEnable) authRouter.use(require('./ldap'))
|
if (config.isLDAPEnable) authRouter.use(require('./ldap'))
|
||||||
if (config.isSAMLEnable) authRouter.use(require('./saml'))
|
if (config.isSAMLEnable) authRouter.use(require('./saml'))
|
||||||
|
if (config.isOAuth2Enable) authRouter.use(require('./oauth2'))
|
||||||
if (config.isEmailEnable) authRouter.use(require('./email'))
|
if (config.isEmailEnable) authRouter.use(require('./email'))
|
||||||
|
|
||||||
// logout
|
// logout
|
||||||
|
|
106
lib/web/auth/oauth2/index.js
Normal file
106
lib/web/auth/oauth2/index.js
Normal file
|
@ -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 + '/'
|
||||||
|
})
|
||||||
|
)
|
|
@ -15,7 +15,7 @@
|
||||||
<% if(allowAnonymous) { %>
|
<% if(allowAnonymous) { %>
|
||||||
<a type="button" href="<%- url %>/new" class="btn btn-sm btn-primary"><i class="fa fa-plus"></i> <%= __('New guest note') %></a>
|
<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>
|
<button class="btn btn-sm btn-success ui-signin" data-toggle="modal" data-target=".signin-modal"><%= __('Sign In') %></button>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
<% if (errorMessage && errorMessage.length > 0) { %>
|
<% if (errorMessage && errorMessage.length > 0) { %>
|
||||||
<div class="alert alert-danger" style="max-width: 400px; margin: 0 auto;"><%= errorMessage %></div>
|
<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">
|
<span class="ui-signin">
|
||||||
<br>
|
<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>
|
<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>
|
||||||
|
|
|
@ -48,7 +48,12 @@
|
||||||
<i class="fa fa-users"></i> <%= __('Sign in via %s', 'SAML') %>
|
<i class="fa fa-users"></i> <%= __('Sign in via %s', 'SAML') %>
|
||||||
</a>
|
</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>
|
<hr>
|
||||||
<% }%>
|
<% }%>
|
||||||
<% if(ldap) { %>
|
<% if(ldap) { %>
|
||||||
|
@ -73,7 +78,7 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap) && email) { %>
|
<% if((facebook || twitter || github || gitlab || mattermost || dropbox || google || ldap || oauth2) && email) { %>
|
||||||
<hr>
|
<hr>
|
||||||
<% }%>
|
<% }%>
|
||||||
<% if(email) { %>
|
<% if(email) { %>
|
||||||
|
|
Loading…
Reference in a new issue