feat: self-contained auth modules + login UI + icons
This commit is contained in:
@@ -8,24 +8,29 @@
|
||||
|
||||
const AzureAdOAuth2Strategy = require('passport-azure-ad-oauth2').Strategy
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
const jwt = require('jsonwebtoken')
|
||||
passport.use('azure_ad_oauth2',
|
||||
new AzureAdOAuth2Strategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL,
|
||||
resource: conf.resource,
|
||||
tenant: conf.tenant
|
||||
}, (accessToken, refreshToken, params, profile, cb) => {
|
||||
let waadProfile = jwt.decode(params.id_token)
|
||||
waadProfile.id = waadProfile.oid
|
||||
waadProfile.provider = 'azure'
|
||||
wiki.db.User.processProfile(waadProfile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'azure',
|
||||
title: 'Azure Active Directory',
|
||||
props: ['clientId', 'clientSecret', 'callbackURL', 'resource', 'tenant'],
|
||||
init (passport, conf) {
|
||||
const jwt = require('jsonwebtoken')
|
||||
passport.use('azure_ad_oauth2',
|
||||
new AzureAdOAuth2Strategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL,
|
||||
resource: conf.resource,
|
||||
tenant: conf.tenant
|
||||
}, (accessToken, refreshToken, params, profile, cb) => {
|
||||
let waadProfile = jwt.decode(params.id_token)
|
||||
waadProfile.id = waadProfile.oid
|
||||
waadProfile.provider = 'azure'
|
||||
wiki.db.User.processProfile(waadProfile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -8,19 +8,24 @@
|
||||
|
||||
const FacebookStrategy = require('passport-facebook').Strategy
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
passport.use('facebook',
|
||||
new FacebookStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL,
|
||||
profileFields: ['id', 'displayName', 'email']
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'facebook',
|
||||
title: 'Facebook',
|
||||
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||
init (passport, conf) {
|
||||
passport.use('facebook',
|
||||
new FacebookStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL,
|
||||
profileFields: ['id', 'displayName', 'email']
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -8,19 +8,24 @@
|
||||
|
||||
const GitHubStrategy = require('passport-github2').Strategy
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
passport.use('github',
|
||||
new GitHubStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL,
|
||||
scope: ['user:email']
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'github',
|
||||
title: 'GitHub',
|
||||
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||
init (passport, conf) {
|
||||
passport.use('github',
|
||||
new GitHubStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL,
|
||||
scope: ['user:email']
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -8,18 +8,23 @@
|
||||
|
||||
const GoogleStrategy = require('passport-google-oauth20').Strategy
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
passport.use('google',
|
||||
new GoogleStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'google',
|
||||
title: 'Google ID',
|
||||
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||
init (passport, conf) {
|
||||
passport.use('google',
|
||||
new GoogleStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -9,32 +9,37 @@
|
||||
const LdapStrategy = require('passport-ldapauth').Strategy
|
||||
const fs = require('fs')
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
passport.use('ldapauth',
|
||||
new LdapStrategy({
|
||||
server: {
|
||||
url: conf.url,
|
||||
bindDn: conf.bindDn,
|
||||
bindCredentials: conf.bindCredentials,
|
||||
searchBase: conf.searchBase,
|
||||
searchFilter: conf.searchFilter,
|
||||
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
|
||||
tlsOptions: (conf.tlsEnabled) ? {
|
||||
ca: [
|
||||
fs.readFileSync(conf.tlsCertPath)
|
||||
]
|
||||
} : {}
|
||||
},
|
||||
usernameField: 'email',
|
||||
passReqToCallback: false
|
||||
}, (profile, cb) => {
|
||||
profile.provider = 'ldap'
|
||||
profile.id = profile.dn
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'ldap',
|
||||
title: 'LDAP / Active Directory',
|
||||
props: ['url', 'bindDn', 'bindCredentials', 'searchBase', 'searchFilter', 'tlsEnabled', 'tlsCertPath'],
|
||||
init (passport, conf) {
|
||||
passport.use('ldapauth',
|
||||
new LdapStrategy({
|
||||
server: {
|
||||
url: conf.url,
|
||||
bindDn: conf.bindDn,
|
||||
bindCredentials: conf.bindCredentials,
|
||||
searchBase: conf.searchBase,
|
||||
searchFilter: conf.searchFilter,
|
||||
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
|
||||
tlsOptions: (conf.tlsEnabled) ? {
|
||||
ca: [
|
||||
fs.readFileSync(conf.tlsCertPath)
|
||||
]
|
||||
} : {}
|
||||
},
|
||||
usernameField: 'email',
|
||||
passReqToCallback: false
|
||||
}, (profile, cb) => {
|
||||
profile.provider = 'ldap'
|
||||
profile.id = profile.dn
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -8,25 +8,30 @@
|
||||
|
||||
const LocalStrategy = require('passport-local').Strategy
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
passport.use('local',
|
||||
new LocalStrategy({
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
}, (uEmail, uPassword, done) => {
|
||||
wiki.db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
|
||||
if (user) {
|
||||
return user.validatePassword(uPassword).then(() => {
|
||||
return done(null, user) || true
|
||||
}).catch((err) => {
|
||||
return done(err, null)
|
||||
})
|
||||
} else {
|
||||
return done(new Error('INVALID_LOGIN'), null)
|
||||
}
|
||||
}).catch((err) => {
|
||||
done(err, null)
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'local',
|
||||
title: 'Local',
|
||||
props: [],
|
||||
init (passport, conf) {
|
||||
passport.use('local',
|
||||
new LocalStrategy({
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
}, (uEmail, uPassword, done) => {
|
||||
wiki.db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
|
||||
if (user) {
|
||||
return user.validatePassword(uPassword).then(() => {
|
||||
return done(null, user) || true
|
||||
}).catch((err) => {
|
||||
return done(err, null)
|
||||
})
|
||||
} else {
|
||||
return done(new Error('INVALID_LOGIN'), null)
|
||||
}
|
||||
}).catch((err) => {
|
||||
done(err, null)
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -8,18 +8,23 @@
|
||||
|
||||
const WindowsLiveStrategy = require('passport-windowslive').Strategy
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
passport.use('windowslive',
|
||||
new WindowsLiveStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'microsoft',
|
||||
title: 'Microsoft Account',
|
||||
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||
init (passport, conf) {
|
||||
passport.use('windowslive',
|
||||
new WindowsLiveStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, function (accessToken, refreshToken, profile, cb) {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -8,18 +8,23 @@
|
||||
|
||||
const SlackStrategy = require('passport-slack').Strategy
|
||||
|
||||
module.exports = (passport, conf) => {
|
||||
passport.use('slack',
|
||||
new SlackStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
module.exports = {
|
||||
key: 'slack',
|
||||
title: 'Slack',
|
||||
props: ['clientId', 'clientSecret', 'callbackURL'],
|
||||
init (passport, conf) {
|
||||
passport.use('slack',
|
||||
new SlackStrategy({
|
||||
clientID: conf.clientId,
|
||||
clientSecret: conf.clientSecret,
|
||||
callbackURL: conf.callbackURL
|
||||
}, (accessToken, refreshToken, profile, cb) => {
|
||||
wiki.db.User.processProfile(profile).then((user) => {
|
||||
return cb(null, user) || true
|
||||
}).catch((err) => {
|
||||
return cb(err, null) || true
|
||||
})
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@@ -37,7 +37,8 @@ const bruteforce = new ExpressBrute(EBstore, {
|
||||
*/
|
||||
router.get('/login', function (req, res, next) {
|
||||
res.render('auth/login', {
|
||||
usr: res.locals.usr
|
||||
authStrategies: wiki.auth.strategies,
|
||||
hasMultipleStrategies: Object.keys(wiki.config.auth.strategies).length > 0
|
||||
})
|
||||
})
|
||||
|
||||
|
@@ -16,6 +16,7 @@ module.exports = Promise.join(
|
||||
// Load global modules
|
||||
// ----------------------------------------
|
||||
|
||||
wiki.auth = require('./modules/auth').init()
|
||||
wiki.disk = require('./modules/disk').init()
|
||||
wiki.docs = require('./modules/documents').init()
|
||||
wiki.git = require('./modules/git').init(false)
|
||||
@@ -38,7 +39,6 @@ module.exports = Promise.join(
|
||||
const http = require('http')
|
||||
const i18nBackend = require('i18next-node-fs-backend')
|
||||
const path = require('path')
|
||||
const passport = require('passport')
|
||||
const passportSocketIo = require('passport.socketio')
|
||||
const session = require('express-session')
|
||||
const SessionRedisStore = require('connect-redis')(session)
|
||||
@@ -78,10 +78,6 @@ module.exports = Promise.join(
|
||||
// Passport Authentication
|
||||
// ----------------------------------------
|
||||
|
||||
require('./modules/auth').init(passport)
|
||||
wiki.rights = require('./modules/rights')
|
||||
// wiki.rights.init()
|
||||
|
||||
let sessionStore = new SessionRedisStore({
|
||||
client: wiki.redis
|
||||
})
|
||||
@@ -95,8 +91,8 @@ module.exports = Promise.join(
|
||||
saveUninitialized: false
|
||||
}))
|
||||
app.use(flash())
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
app.use(wiki.auth.passport.initialize())
|
||||
app.use(wiki.auth.passport.session())
|
||||
|
||||
// ----------------------------------------
|
||||
// SEO
|
||||
@@ -135,6 +131,7 @@ module.exports = Promise.join(
|
||||
// View accessible data
|
||||
// ----------------------------------------
|
||||
|
||||
app.locals.basedir = wiki.ROOTPATH
|
||||
app.locals._ = require('lodash')
|
||||
app.locals.t = wiki.lang.t.bind(wiki.lang)
|
||||
app.locals.moment = require('moment')
|
||||
|
@@ -9,7 +9,7 @@
|
||||
* @return {any} void
|
||||
*/
|
||||
module.exports = (req, res, next) => {
|
||||
res.locals.appflash = req.flash('alert')
|
||||
res.locals.flash = req.flash('alert')
|
||||
|
||||
next()
|
||||
}
|
||||
|
@@ -3,10 +3,16 @@
|
||||
/* global wiki */
|
||||
|
||||
const _ = require('lodash')
|
||||
const passport = require('passport')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
init(passport) {
|
||||
// Serialization user methods
|
||||
strategies: {},
|
||||
init() {
|
||||
this.passport = passport
|
||||
|
||||
// Serialization user methods
|
||||
|
||||
passport.serializeUser(function (user, done) {
|
||||
done(null, user._id)
|
||||
@@ -27,20 +33,26 @@ module.exports = {
|
||||
|
||||
// Load authentication strategies
|
||||
|
||||
wiki.config.authStrategies = {
|
||||
list: _.pickBy(wiki.config.auth, strategy => strategy.enabled),
|
||||
socialEnabled: (_.chain(wiki.config.auth).omit('local').filter(['enabled', true]).value().length > 0)
|
||||
}
|
||||
|
||||
_.forOwn(wiki.config.authStrategies.list, (strategyConfig, strategyName) => {
|
||||
strategyConfig.callbackURL = `${wiki.config.site.host}/login/${strategyName}/callback`
|
||||
require(`../authentication/${strategyName}`)(passport, strategyConfig)
|
||||
wiki.logger.info(`Authentication Provider ${_.upperFirst(strategyName)}: OK`)
|
||||
_.forOwn(wiki.config.auth.strategies, (strategyConfig, strategyKey) => {
|
||||
strategyConfig.callbackURL = `${wiki.config.site.host}${wiki.config.site.path}/login/${strategyKey}/callback`
|
||||
let strategy = require(`../authentication/${strategyKey}`)
|
||||
strategy.init(passport, strategyConfig)
|
||||
fs.readFile(path.join(wiki.ROOTPATH, `assets/svg/auth-icon-${strategyKey}.svg`), 'utf8').then(iconData => {
|
||||
strategy.icon = iconData
|
||||
}).catch(err => {
|
||||
if (err.code === 'ENOENT') {
|
||||
strategy.icon = '[missing icon]'
|
||||
} else {
|
||||
wiki.logger.error(err)
|
||||
}
|
||||
})
|
||||
this.strategies[strategy.key] = strategy
|
||||
wiki.logger.info(`Authentication Provider ${strategyKey}: OK`)
|
||||
})
|
||||
|
||||
// Create Guest account for first-time
|
||||
|
||||
return wiki.db.User.findOne({
|
||||
wiki.db.User.findOne({
|
||||
where: {
|
||||
provider: 'local',
|
||||
email: 'guest@example.com'
|
||||
@@ -88,5 +100,7 @@ module.exports = {
|
||||
// })
|
||||
// } else { return true }
|
||||
// })
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
@@ -3,52 +3,30 @@ extends ../master.pug
|
||||
block body
|
||||
body
|
||||
.login#root
|
||||
.login-container
|
||||
if config.authStrategies.socialEnabled
|
||||
.login-container(:class={ "is-expanded": hasMultipleStrategies })
|
||||
if flash.length > 0
|
||||
.login-error
|
||||
strong
|
||||
i.icon-warning-outline
|
||||
= flash[0].title
|
||||
span= flash[0].message
|
||||
if hasMultipleStrategies
|
||||
.login-providers
|
||||
button.is-active(onclick='window.location.assign("/login/ms")')
|
||||
button.is-active(title=t('auth:providers.local'))
|
||||
i.nc-icon-outline.ui-1_database
|
||||
span= t('auth:providers.local')
|
||||
if config.auth.microsoft && config.auth.microsoft.enabled
|
||||
button(onclick='window.location.assign("/login/ms")')
|
||||
i.icon-windows2
|
||||
span= t('auth:providers.windowslive')
|
||||
if config.auth.azure && config.auth.azure.enabled
|
||||
button(onclick='window.location.assign("/login/azure")')
|
||||
i.icon-windows2
|
||||
span= t('auth:providers.azure')
|
||||
if config.auth.google && config.auth.google.enabled
|
||||
button(onclick='window.location.assign("/login/google")')
|
||||
i.icon-google
|
||||
span= t('auth:providers.google')
|
||||
if config.auth.facebook && config.auth.facebook.enabled
|
||||
button(onclick='window.location.assign("/login/facebook")')
|
||||
i.icon-facebook
|
||||
span= t('auth:providers.facebook')
|
||||
if config.auth.github && config.auth.github.enabled
|
||||
button(onclick='window.location.assign("/login/github")')
|
||||
i.icon-github
|
||||
span= t('auth:providers.github')
|
||||
if config.auth.slack && config.auth.slack.enabled
|
||||
button(onclick='window.location.assign("/login/slack")')
|
||||
i.icon-slack
|
||||
span= t('auth:providers.slack')
|
||||
each strategy in authStrategies
|
||||
button(onclick='window.location.assign("/login/' + strategy.key + '")', title=strategy.title)
|
||||
!= strategy.icon
|
||||
span= strategy.title
|
||||
.login-frame
|
||||
h1= config.site.title
|
||||
h2= t('auth:loginrequired')
|
||||
if appflash.length > 0
|
||||
h3
|
||||
i.icon-warning-outline
|
||||
= appflash[0].title
|
||||
h4= appflash[0].message
|
||||
if config.auth.local.enabled
|
||||
form(method='post', action='/login')
|
||||
input#login-user(type='text', name='email', placeholder=t('auth:fields.emailuser'))
|
||||
input#login-pass(type='password', name='password', placeholder=t('auth:fields.password'))
|
||||
button.button.is-light-green.is-fullwidth(type='submit')
|
||||
span= t('auth:actions.login')
|
||||
form(method='post', action='/login')
|
||||
input#login-user(type='text', name='email', placeholder=t('auth:fields.emailuser'))
|
||||
input#login-pass(type='password', name='password', placeholder=t('auth:fields.password'))
|
||||
button.button.is-light-green.is-fullwidth(type='submit')
|
||||
span= t('auth:actions.login')
|
||||
.login-copyright
|
||||
= t('footer.poweredby') + ' '
|
||||
a.icon(href='https://github.com/Requarks/wiki')
|
||||
i.icon-github
|
||||
a(href='https://wiki.requarks.io/') Wiki.js
|
||||
= t('footer.poweredby')
|
||||
a(href='https://wiki.js.org', rel='external', title='Wiki.js') Wiki.js
|
||||
|
Reference in New Issue
Block a user