feat: user menu + jwt certs + UI fixes
This commit is contained in:
@@ -1,35 +1,7 @@
|
||||
/* global WIKI */
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const express = require('express')
|
||||
const router = express.Router()
|
||||
const ExpressBrute = require('express-brute')
|
||||
const ExpressBruteRedisStore = require('express-brute-redis')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment')
|
||||
const _ = require('lodash')
|
||||
|
||||
/**
|
||||
* Setup Express-Brute
|
||||
*/
|
||||
const EBstore = new ExpressBruteRedisStore({
|
||||
client: WIKI.redis
|
||||
})
|
||||
const bruteforce = new ExpressBrute(EBstore, {
|
||||
freeRetries: 5,
|
||||
minWait: 60 * 1000,
|
||||
maxWait: 5 * 60 * 1000,
|
||||
refreshTimeoutOnRequest: false,
|
||||
failCallback (req, res, next, nextValidRequestDate) {
|
||||
req.flash('alert', {
|
||||
class: 'error',
|
||||
title: WIKI.lang.t('auth:errors.toomanyattempts'),
|
||||
message: WIKI.lang.t('auth:errors.toomanyattemptsmsg', { time: moment(nextValidRequestDate).fromNow() }),
|
||||
iconClass: 'fa-times'
|
||||
})
|
||||
res.redirect('/login')
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Login form
|
||||
@@ -38,72 +10,6 @@ router.get('/login', function (req, res, next) {
|
||||
res.render('login')
|
||||
})
|
||||
|
||||
router.post('/login', bruteforce.prevent, function (req, res, next) {
|
||||
new Promise((resolve, reject) => {
|
||||
// [1] LOCAL AUTHENTICATION
|
||||
WIKI.auth.passport.authenticate('local', { session: false }, function (err, user, info) {
|
||||
if (err) { return reject(err) }
|
||||
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
||||
resolve(user)
|
||||
})(req, res, next)
|
||||
}).catch({ message: 'INVALID_LOGIN' }, err => {
|
||||
if (_.has(WIKI.config.auth.strategy, 'ldap')) {
|
||||
// [2] LDAP AUTHENTICATION
|
||||
return new Promise((resolve, reject) => {
|
||||
WIKI.auth.passport.authenticate('ldapauth', { session: false }, function (err, user, info) {
|
||||
if (err) { return reject(err) }
|
||||
if (info && info.message) { return reject(new Error(info.message)) }
|
||||
if (!user) { return reject(new Error('INVALID_LOGIN')) }
|
||||
resolve(user)
|
||||
})(req, res, next)
|
||||
})
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}).then((user) => {
|
||||
// LOGIN SUCCESS
|
||||
return req.logIn(user, { session: false }, function (err) {
|
||||
if (err) { return next(err) }
|
||||
req.brute.reset(function () {
|
||||
return res.redirect('/')
|
||||
})
|
||||
})
|
||||
}).catch(err => {
|
||||
// LOGIN FAIL
|
||||
if (err.message === 'INVALID_LOGIN') {
|
||||
req.flash('alert', {
|
||||
title: WIKI.lang.t('auth:errors.invalidlogin'),
|
||||
message: WIKI.lang.t('auth:errors.invalidloginmsg')
|
||||
})
|
||||
return res.redirect('/login')
|
||||
} else {
|
||||
req.flash('alert', {
|
||||
title: WIKI.lang.t('auth:errors.loginerror'),
|
||||
message: err.message
|
||||
})
|
||||
return res.redirect('/login')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Social Login
|
||||
*/
|
||||
|
||||
router.get('/login/ms', WIKI.auth.passport.authenticate('windowslive', { scope: ['wl.signin', 'wl.basic', 'wl.emails'] }))
|
||||
router.get('/login/google', WIKI.auth.passport.authenticate('google', { scope: ['profile', 'email'] }))
|
||||
router.get('/login/facebook', WIKI.auth.passport.authenticate('facebook', { scope: ['public_profile', 'email'] }))
|
||||
router.get('/login/github', WIKI.auth.passport.authenticate('github', { scope: ['user:email'] }))
|
||||
router.get('/login/slack', WIKI.auth.passport.authenticate('slack', { scope: ['identity.basic', 'identity.email'] }))
|
||||
router.get('/login/azure', WIKI.auth.passport.authenticate('azure_ad_oauth2'))
|
||||
|
||||
router.get('/login/ms/callback', WIKI.auth.passport.authenticate('windowslive', { failureRedirect: '/login', successRedirect: '/' }))
|
||||
router.get('/login/google/callback', WIKI.auth.passport.authenticate('google', { failureRedirect: '/login', successRedirect: '/' }))
|
||||
router.get('/login/facebook/callback', WIKI.auth.passport.authenticate('facebook', { failureRedirect: '/login', successRedirect: '/' }))
|
||||
router.get('/login/github/callback', WIKI.auth.passport.authenticate('github', { failureRedirect: '/login', successRedirect: '/' }))
|
||||
router.get('/login/slack/callback', WIKI.auth.passport.authenticate('slack', { failureRedirect: '/login', successRedirect: '/' }))
|
||||
router.get('/login/azure/callback', WIKI.auth.passport.authenticate('azure_ad_oauth2', { failureRedirect: '/login', successRedirect: '/' }))
|
||||
|
||||
/**
|
||||
* Logout
|
||||
*/
|
||||
@@ -112,4 +18,14 @@ router.get('/logout', function (req, res) {
|
||||
res.redirect('/')
|
||||
})
|
||||
|
||||
/**
|
||||
* JWT Public Endpoints
|
||||
*/
|
||||
router.get('/.well-known/jwk.json', function (req, res, next) {
|
||||
res.json(WIKI.config.certs.jwk)
|
||||
})
|
||||
router.get('/.well-known/jwk.pem', function (req, res, next) {
|
||||
res.send(WIKI.config.certs.public)
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
|
@@ -45,7 +45,7 @@ module.exports = {
|
||||
// Load JWT
|
||||
passport.use('jwt', new passportJWT.Strategy({
|
||||
jwtFromRequest: securityHelper.extractJWT,
|
||||
secretOrKey: WIKI.config.sessionSecret,
|
||||
secretOrKey: WIKI.config.certs.public,
|
||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
||||
issuer: 'urn:wiki.js'
|
||||
}, (jwtPayload, cb) => {
|
||||
|
@@ -253,7 +253,11 @@ module.exports = class User extends Model {
|
||||
localeCode: user.localeCode,
|
||||
defaultEditor: user.defaultEditor,
|
||||
permissions: ['manage:system']
|
||||
}, WIKI.config.sessionSecret, {
|
||||
}, {
|
||||
key: WIKI.config.certs.private,
|
||||
passphrase: WIKI.config.sessionSecret
|
||||
}, {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: '30m',
|
||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
||||
issuer: 'urn:wiki.js'
|
||||
|
@@ -25,6 +25,7 @@ module.exports = () => {
|
||||
const _ = require('lodash')
|
||||
const cfgHelper = require('./helpers/config')
|
||||
const crypto = Promise.promisifyAll(require('crypto'))
|
||||
const pem2jwk = require('pem-jwk').pem2jwk
|
||||
|
||||
// ----------------------------------------
|
||||
// Define Express App
|
||||
@@ -90,6 +91,7 @@ module.exports = () => {
|
||||
}
|
||||
|
||||
// Create directory structure
|
||||
WIKI.logger.info('Creating data directories...')
|
||||
const dataPath = path.join(process.cwd(), 'data')
|
||||
await fs.ensureDir(dataPath)
|
||||
await fs.ensureDir(path.join(dataPath, 'cache'))
|
||||
@@ -110,6 +112,26 @@ module.exports = () => {
|
||||
_.set(WIKI.config, 'theming.darkMode', false)
|
||||
_.set(WIKI.config, 'title', 'Wiki.js')
|
||||
|
||||
// Generate certificates
|
||||
WIKI.logger.info('Generating certificates...')
|
||||
const certs = crypto.generateKeyPairSync('rsa', {
|
||||
modulusLength: 2048,
|
||||
publicKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem'
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs1',
|
||||
format: 'pem',
|
||||
cipher: 'aes-256-cbc',
|
||||
passphrase: WIKI.config.sessionSecret
|
||||
}
|
||||
})
|
||||
|
||||
_.set(WIKI.config, 'certs.jwk', pem2jwk(certs.publicKey))
|
||||
_.set(WIKI.config, 'certs.public', certs.publicKey)
|
||||
_.set(WIKI.config, 'certs.private', certs.privateKey)
|
||||
|
||||
// Save config to DB
|
||||
WIKI.logger.info('Persisting config to DB...')
|
||||
await WIKI.configSvc.saveToDb([
|
||||
@@ -120,7 +142,8 @@ module.exports = () => {
|
||||
'sessionSecret',
|
||||
'telemetry',
|
||||
'theming',
|
||||
'title'
|
||||
'title',
|
||||
'certs'
|
||||
])
|
||||
|
||||
// Create default locale
|
||||
|
Reference in New Issue
Block a user