feat: guest + user permissions
This commit is contained in:
@@ -22,14 +22,12 @@ defaults:
|
||||
db: 0
|
||||
password: null
|
||||
# DB defaults
|
||||
defaultEditor: 'markdown'
|
||||
graphEndpoint: 'https://graph.requarks.io'
|
||||
lang:
|
||||
code: en
|
||||
autoUpdate: true
|
||||
namespaces: []
|
||||
namespacing: false
|
||||
public: false
|
||||
telemetry:
|
||||
clientId: ''
|
||||
isEnabled: false
|
||||
@@ -47,13 +45,6 @@ defaults:
|
||||
maxAge: 600
|
||||
methods: 'GET,POST'
|
||||
origin: true
|
||||
configNamespaces:
|
||||
- auth
|
||||
- features
|
||||
- logging
|
||||
- site
|
||||
- theme
|
||||
- uploads
|
||||
localeNamespaces:
|
||||
- admin
|
||||
- auth
|
||||
|
@@ -5,6 +5,18 @@ const _ = require('lodash')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
/**
|
||||
* Robots.txt
|
||||
*/
|
||||
router.get('/robots.txt', (req, res, next) => {
|
||||
res.type('text/plain')
|
||||
if (_.includes(WIKI.config.seo.robots, 'noindex')) {
|
||||
res.send("User-agent: *\nDisallow: /")
|
||||
} else {
|
||||
res.status(200).end()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Create/Edit document
|
||||
*/
|
||||
@@ -17,12 +29,20 @@ router.get(['/e', '/e/*'], async (req, res, next) => {
|
||||
isPrivate: false
|
||||
})
|
||||
if (page) {
|
||||
if (!WIKI.auth.checkAccess(req.user, ['manage:pages'], pageArgs)) {
|
||||
return res.render('unauthorized', { action: 'edit'})
|
||||
}
|
||||
|
||||
_.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
|
||||
_.set(res.locals, 'pageMeta.description', page.description)
|
||||
page.mode = 'update'
|
||||
page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
|
||||
page.content = Buffer.from(page.content).toString('base64')
|
||||
} else {
|
||||
if (!WIKI.auth.checkAccess(req.user, ['write:pages'], pageArgs)) {
|
||||
return res.render('unauthorized', { action: 'create'})
|
||||
}
|
||||
|
||||
_.set(res.locals, 'pageMeta.title', `New Page`)
|
||||
page = {
|
||||
path: pageArgs.path,
|
||||
@@ -56,6 +76,11 @@ router.get(['/p', '/p/*'], (req, res, next) => {
|
||||
*/
|
||||
router.get(['/h', '/h/*'], async (req, res, next) => {
|
||||
const pageArgs = pageHelper.parsePath(req.path)
|
||||
|
||||
if (!WIKI.auth.checkAccess(req.user, ['read:pages'], pageArgs)) {
|
||||
return res.render('unauthorized', { action: 'history'})
|
||||
}
|
||||
|
||||
const page = await WIKI.models.pages.getPageFromDb({
|
||||
path: pageArgs.path,
|
||||
locale: pageArgs.locale,
|
||||
@@ -76,6 +101,11 @@ router.get(['/h', '/h/*'], async (req, res, next) => {
|
||||
*/
|
||||
router.get(['/s', '/s/*'], async (req, res, next) => {
|
||||
const pageArgs = pageHelper.parsePath(req.path)
|
||||
|
||||
if (!WIKI.auth.checkAccess(req.user, ['read:pages'], pageArgs)) {
|
||||
return res.render('unauthorized', { action: 'source'})
|
||||
}
|
||||
|
||||
const page = await WIKI.models.pages.getPageFromDb({
|
||||
path: pageArgs.path,
|
||||
locale: pageArgs.locale,
|
||||
@@ -96,6 +126,15 @@ router.get(['/s', '/s/*'], async (req, res, next) => {
|
||||
*/
|
||||
router.get('/*', async (req, res, next) => {
|
||||
const pageArgs = pageHelper.parsePath(req.path)
|
||||
|
||||
if (!WIKI.auth.checkAccess(req.user, ['read:pages'], pageArgs)) {
|
||||
if (pageArgs.path === 'home') {
|
||||
return res.redirect('/login')
|
||||
} else {
|
||||
return res.render('unauthorized', { action: 'view'})
|
||||
}
|
||||
}
|
||||
|
||||
const page = await WIKI.models.pages.getPage({
|
||||
path: pageArgs.path,
|
||||
locale: pageArgs.locale,
|
||||
@@ -108,8 +147,10 @@ router.get('/*', async (req, res, next) => {
|
||||
const sidebar = await WIKI.models.navigation.getTree({ cache: true })
|
||||
res.render('page', { page, sidebar })
|
||||
} else if (pageArgs.path === 'home') {
|
||||
_.set(res.locals, 'pageMeta.title', 'Welcome')
|
||||
res.render('welcome')
|
||||
} else {
|
||||
_.set(res.locals, 'pageMeta.title', 'Page Not Found')
|
||||
res.status(404).render('new', { pagePath: req.path })
|
||||
}
|
||||
})
|
||||
|
@@ -3,6 +3,8 @@ const passportJWT = require('passport-jwt')
|
||||
const fs = require('fs-extra')
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment')
|
||||
|
||||
const securityHelper = require('../helpers/security')
|
||||
|
||||
@@ -10,11 +12,16 @@ const securityHelper = require('../helpers/security')
|
||||
|
||||
module.exports = {
|
||||
strategies: {},
|
||||
guest: {
|
||||
cacheExpiration: moment.utc().subtract(1, 'd')
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the authentication module
|
||||
*/
|
||||
init() {
|
||||
this.passport = passport
|
||||
|
||||
// Serialization user methods
|
||||
|
||||
passport.serializeUser(function (user, done) {
|
||||
done(null, user.id)
|
||||
})
|
||||
@@ -34,6 +41,10 @@ module.exports = {
|
||||
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Load authentication strategies
|
||||
*/
|
||||
async activateStrategies() {
|
||||
try {
|
||||
// Unload any active strategies
|
||||
@@ -46,7 +57,7 @@ module.exports = {
|
||||
passport.use('jwt', new passportJWT.Strategy({
|
||||
jwtFromRequest: securityHelper.extractJWT,
|
||||
secretOrKey: WIKI.config.certs.public,
|
||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
||||
audience: WIKI.config.auth.audience,
|
||||
issuer: 'urn:wiki.js'
|
||||
}, (jwtPayload, cb) => {
|
||||
cb(null, jwtPayload)
|
||||
@@ -60,7 +71,7 @@ module.exports = {
|
||||
|
||||
const strategy = require(`../modules/authentication/${stg.key}/authentication.js`)
|
||||
|
||||
stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback` // TODO: config.host
|
||||
stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback`
|
||||
strategy.init(passport, stg.config)
|
||||
|
||||
fs.readFile(path.join(WIKI.ROOTPATH, `assets/svg/auth-icon-${strategy.key}.svg`), 'utf8').then(iconData => {
|
||||
@@ -79,5 +90,74 @@ module.exports = {
|
||||
WIKI.logger.error(`Authentication Strategy: [ FAILED ]`)
|
||||
WIKI.logger.error(err)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Authenticate current request
|
||||
*
|
||||
* @param {Express Request} req
|
||||
* @param {Express Response} res
|
||||
* @param {Express Next Callback} next
|
||||
*/
|
||||
authenticate(req, res, next) {
|
||||
WIKI.auth.passport.authenticate('jwt', {session: false}, async (err, user, info) => {
|
||||
if (err) { return next() }
|
||||
|
||||
// Expired but still valid within N days, just renew
|
||||
if (info instanceof Error && info.name === 'TokenExpiredError' && moment().subtract(14, 'days').isBefore(info.expiredAt)) {
|
||||
const jwtPayload = jwt.decode(securityHelper.extractJWT(req))
|
||||
try {
|
||||
const newToken = await WIKI.models.users.refreshToken(jwtPayload.id)
|
||||
user = newToken.user
|
||||
|
||||
// Try headers, otherwise cookies for response
|
||||
if (req.get('content-type') === 'application/json') {
|
||||
res.set('new-jwt', newToken.token)
|
||||
} else {
|
||||
res.cookie('jwt', newToken.token, { expires: moment().add(365, 'days').toDate() })
|
||||
}
|
||||
} catch (err) {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
// JWT is NOT valid, set as guest
|
||||
if (!user) {
|
||||
if (WIKI.auth.guest.cacheExpiration ) {
|
||||
WIKI.auth.guest = await WIKI.models.users.getGuestUser()
|
||||
WIKI.auth.guest.cacheExpiration = moment.utc().add(1, 'm')
|
||||
}
|
||||
req.user = WIKI.auth.guest
|
||||
return next()
|
||||
}
|
||||
|
||||
// JWT is valid
|
||||
req.logIn(user, { session: false }, (err) => {
|
||||
if (err) { return next(err) }
|
||||
next()
|
||||
})
|
||||
})(req, res, next)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if user has access to resource
|
||||
*
|
||||
* @param {User} user
|
||||
* @param {Array<String>} permissions
|
||||
* @param {String|Boolean} path
|
||||
*/
|
||||
checkAccess(user, permissions = [], path = false) {
|
||||
// System Admin
|
||||
if (_.includes(user.permissions, 'manage:system')) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check Global Permissions
|
||||
if (_.intersection(user.permissions, permissions).length < 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check Page Rules
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@@ -52,8 +52,6 @@ module.exports = {
|
||||
appconfig.port = process.env.PORT || 80
|
||||
}
|
||||
|
||||
appconfig.public = (appconfig.public === true || _.toLower(appconfig.public) === 'true')
|
||||
|
||||
WIKI.config = appconfig
|
||||
WIKI.data = appdata
|
||||
WIKI.version = require(path.join(WIKI.ROOTPATH, 'package.json')).version
|
||||
|
@@ -1,11 +1,14 @@
|
||||
exports.up = knex => {
|
||||
const dbCompat = {
|
||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
||||
}
|
||||
return knex.schema
|
||||
// =====================================
|
||||
// MODEL TABLES
|
||||
// =====================================
|
||||
// ASSETS ------------------------------
|
||||
.createTable('assets', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('filename').notNullable()
|
||||
table.string('basename').notNullable()
|
||||
@@ -19,7 +22,7 @@ exports.up = knex => {
|
||||
})
|
||||
// ASSET FOLDERS -----------------------
|
||||
.createTable('assetFolders', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('name').notNullable()
|
||||
table.string('slug').notNullable()
|
||||
@@ -27,7 +30,7 @@ exports.up = knex => {
|
||||
})
|
||||
// AUTHENTICATION ----------------------
|
||||
.createTable('authentication', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
@@ -37,7 +40,7 @@ exports.up = knex => {
|
||||
})
|
||||
// COMMENTS ----------------------------
|
||||
.createTable('comments', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.text('content').notNullable()
|
||||
table.string('createdAt').notNullable()
|
||||
@@ -45,14 +48,14 @@ exports.up = knex => {
|
||||
})
|
||||
// EDITORS -----------------------------
|
||||
.createTable('editors', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config').notNullable()
|
||||
})
|
||||
// GROUPS ------------------------------
|
||||
.createTable('groups', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('name').notNullable()
|
||||
table.json('permissions').notNullable()
|
||||
@@ -63,7 +66,7 @@ exports.up = knex => {
|
||||
})
|
||||
// LOCALES -----------------------------
|
||||
.createTable('locales', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('code', 2).notNullable().primary()
|
||||
table.json('strings')
|
||||
table.boolean('isRTL').notNullable().defaultTo(false)
|
||||
@@ -74,7 +77,7 @@ exports.up = knex => {
|
||||
})
|
||||
// LOGGING ----------------------------
|
||||
.createTable('loggers', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.string('level').notNullable().defaultTo('warn')
|
||||
@@ -82,13 +85,13 @@ exports.up = knex => {
|
||||
})
|
||||
// NAVIGATION ----------------------------
|
||||
.createTable('navigation', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.json('config')
|
||||
})
|
||||
// PAGE HISTORY ------------------------
|
||||
.createTable('pageHistory', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('path').notNullable()
|
||||
table.string('hash').notNullable()
|
||||
@@ -104,7 +107,7 @@ exports.up = knex => {
|
||||
})
|
||||
// PAGES -------------------------------
|
||||
.createTable('pages', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('path').notNullable()
|
||||
table.string('hash').notNullable()
|
||||
@@ -124,7 +127,7 @@ exports.up = knex => {
|
||||
})
|
||||
// PAGE TREE ---------------------------
|
||||
.createTable('pageTree', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('path').notNullable()
|
||||
table.integer('depth').unsigned().notNullable()
|
||||
@@ -135,28 +138,28 @@ exports.up = knex => {
|
||||
})
|
||||
// RENDERERS ---------------------------
|
||||
.createTable('renderers', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config')
|
||||
})
|
||||
// SEARCH ------------------------------
|
||||
.createTable('searchEngines', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.json('config')
|
||||
})
|
||||
// SETTINGS ----------------------------
|
||||
.createTable('settings', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.json('value')
|
||||
table.string('updatedAt').notNullable()
|
||||
})
|
||||
// STORAGE -----------------------------
|
||||
.createTable('storage', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.string('key').notNullable().primary()
|
||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
|
||||
@@ -164,7 +167,7 @@ exports.up = knex => {
|
||||
})
|
||||
// TAGS --------------------------------
|
||||
.createTable('tags', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('tag').notNullable().unique()
|
||||
table.string('title')
|
||||
@@ -173,7 +176,7 @@ exports.up = knex => {
|
||||
})
|
||||
// USER KEYS ---------------------------
|
||||
.createTable('userKeys', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('kind').notNullable()
|
||||
table.string('token').notNullable()
|
||||
@@ -182,7 +185,7 @@ exports.up = knex => {
|
||||
})
|
||||
// USERS -------------------------------
|
||||
.createTable('users', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.string('email').notNullable()
|
||||
table.string('name').notNullable()
|
||||
@@ -205,21 +208,21 @@ exports.up = knex => {
|
||||
// =====================================
|
||||
// PAGE HISTORY TAGS ---------------------------
|
||||
.createTable('pageHistoryTags', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
|
||||
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
||||
})
|
||||
// PAGE TAGS ---------------------------
|
||||
.createTable('pageTags', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
|
||||
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
||||
})
|
||||
// USER GROUPS -------------------------
|
||||
.createTable('userGroups', table => {
|
||||
table.charset('utf8mb4')
|
||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
||||
table.increments('id').primary()
|
||||
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
|
||||
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
|
@@ -1,11 +0,0 @@
|
||||
exports.seed = (knex, Promise) => {
|
||||
return knex('settings')
|
||||
.insert([
|
||||
{ key: 'auth', value: {} },
|
||||
{ key: 'features', value: {} },
|
||||
{ key: 'logging', value: {} },
|
||||
{ key: 'site', value: {} },
|
||||
{ key: 'theme', value: {} },
|
||||
{ key: 'uploads', value: {} }
|
||||
])
|
||||
}
|
@@ -70,6 +70,13 @@ module.exports = {
|
||||
},
|
||||
async updateStrategies(obj, args, context) {
|
||||
try {
|
||||
WIKI.config.auth = {
|
||||
audience: _.get(args, 'config.audience', WIKI.config.auth.audience),
|
||||
tokenExpiration: _.get(args, 'config.tokenExpiration', WIKI.config.auth.tokenExpiration),
|
||||
tokenRenewal: _.get(args, 'config.tokenRenewal', WIKI.config.auth.tokenRenewal)
|
||||
}
|
||||
await WIKI.configSvc.saveToDb(['auth'])
|
||||
|
||||
for (let str of args.strategies) {
|
||||
await WIKI.models.authentication.query().patch({
|
||||
isEnabled: str.isEnabled,
|
||||
|
@@ -43,7 +43,8 @@ type AuthenticationMutation {
|
||||
): AuthenticationRegisterResponse
|
||||
|
||||
updateStrategies(
|
||||
strategies: [AuthenticationStrategyInput]
|
||||
strategies: [AuthenticationStrategyInput]!
|
||||
config: AuthenticationConfigInput
|
||||
): DefaultResponse @auth(requires: ["manage:system"])
|
||||
}
|
||||
|
||||
@@ -88,3 +89,9 @@ input AuthenticationStrategyInput {
|
||||
domainWhitelist: [String]!
|
||||
autoEnrollGroups: [Int]!
|
||||
}
|
||||
|
||||
input AuthenticationConfigInput {
|
||||
audience: String!
|
||||
tokenExpiration: String!
|
||||
tokenRenewal: String!
|
||||
}
|
||||
|
@@ -67,7 +67,7 @@ module.exports = async () => {
|
||||
|
||||
app.use(cookieParser())
|
||||
app.use(WIKI.auth.passport.initialize())
|
||||
app.use(mw.auth.jwt)
|
||||
app.use(WIKI.auth.authenticate)
|
||||
|
||||
// ----------------------------------------
|
||||
// SEO
|
||||
@@ -138,8 +138,7 @@ module.exports = async () => {
|
||||
// ----------------------------------------
|
||||
|
||||
app.use('/', ctrl.auth)
|
||||
|
||||
app.use('/', mw.auth.checkPath, ctrl.common)
|
||||
app.use('/', ctrl.common)
|
||||
|
||||
// ----------------------------------------
|
||||
// Error handling
|
||||
|
@@ -1,72 +0,0 @@
|
||||
const jwt = require('jsonwebtoken')
|
||||
const moment = require('moment')
|
||||
|
||||
const securityHelper = require('../helpers/security')
|
||||
|
||||
/* global WIKI */
|
||||
|
||||
/**
|
||||
* Authentication middleware
|
||||
*/
|
||||
module.exports = {
|
||||
jwt(req, res, next) {
|
||||
WIKI.auth.passport.authenticate('jwt', {session: false}, async (err, user, info) => {
|
||||
if (err) { return next() }
|
||||
|
||||
// Expired but still valid within 7 days, just renew
|
||||
if (info instanceof Error && info.name === 'TokenExpiredError' && moment().subtract(14, 'days').isBefore(info.expiredAt)) {
|
||||
const jwtPayload = jwt.decode(securityHelper.extractJWT(req))
|
||||
try {
|
||||
const newToken = await WIKI.models.users.refreshToken(jwtPayload.id)
|
||||
user = newToken.user
|
||||
|
||||
// Try headers, otherwise cookies for response
|
||||
if (req.get('content-type') === 'application/json') {
|
||||
res.set('new-jwt', newToken.token)
|
||||
} else {
|
||||
res.cookie('jwt', newToken.token, { expires: moment().add(365, 'days').toDate() })
|
||||
}
|
||||
} catch (err) {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
// JWT is NOT valid
|
||||
if (!user) { return next() }
|
||||
|
||||
// JWT is valid
|
||||
req.logIn(user, { session: false }, (err) => {
|
||||
if (err) { return next(err) }
|
||||
next()
|
||||
})
|
||||
})(req, res, next)
|
||||
},
|
||||
checkPath(req, res, next) {
|
||||
// Is user authenticated ?
|
||||
|
||||
if (!req.isAuthenticated()) {
|
||||
if (WIKI.config.public !== true) {
|
||||
return res.redirect('/login')
|
||||
} else {
|
||||
// req.user = rights.guest
|
||||
res.locals.isGuest = true
|
||||
}
|
||||
} else {
|
||||
res.locals.isGuest = false
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
|
||||
// res.locals.rights = rights.check(req)
|
||||
|
||||
// if (!res.locals.rights.read) {
|
||||
// return res.render('error-forbidden')
|
||||
// }
|
||||
|
||||
// Expose user data
|
||||
|
||||
res.locals.user = req.user
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
@@ -138,6 +138,11 @@ module.exports = class User extends Model {
|
||||
return (result && _.has(result, 'delta') && result.delta === 0)
|
||||
}
|
||||
|
||||
async getPermissions() {
|
||||
const permissions = await this.$relatedQuery('groups').select('permissions').pluck('permissions')
|
||||
this.permissions = _.uniq(_.flatten(permissions))
|
||||
}
|
||||
|
||||
static async processProfile(profile) {
|
||||
let primaryEmail = ''
|
||||
if (_.isArray(profile.emails)) {
|
||||
@@ -262,8 +267,8 @@ module.exports = class User extends Model {
|
||||
passphrase: WIKI.config.sessionSecret
|
||||
}, {
|
||||
algorithm: 'RS256',
|
||||
expiresIn: '30m',
|
||||
audience: 'urn:wiki.js', // TODO: use value from admin
|
||||
expiresIn: WIKI.config.auth.tokenExpiration,
|
||||
audience: WIKI.config.auth.audience,
|
||||
issuer: 'urn:wiki.js'
|
||||
}),
|
||||
user
|
||||
@@ -391,4 +396,10 @@ module.exports = class User extends Model {
|
||||
throw new WIKI.Error.AuthRegistrationDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
static async getGuestUser () {
|
||||
let user = await WIKI.models.users.query().findById(2)
|
||||
user.getPermissions()
|
||||
return user
|
||||
}
|
||||
}
|
||||
|
@@ -104,8 +104,12 @@ module.exports = () => {
|
||||
await fs.ensureDir(path.join(dataPath, 'uploads'))
|
||||
|
||||
// Set config
|
||||
_.set(WIKI.config, 'auth', {
|
||||
audience: 'urn:wiki.js',
|
||||
tokenExpiration: '30m',
|
||||
tokenRenewal: '14d'
|
||||
})
|
||||
_.set(WIKI.config, 'company', '')
|
||||
_.set(WIKI.config, 'defaultEditor', 'markdown')
|
||||
_.set(WIKI.config, 'features', {
|
||||
featurePageRatings: true,
|
||||
featurePageComments: true,
|
||||
@@ -136,7 +140,6 @@ module.exports = () => {
|
||||
dkimKeySelector: '',
|
||||
dkimPrivateKey: ''
|
||||
})
|
||||
_.set(WIKI.config, 'public', false)
|
||||
_.set(WIKI.config, 'seo', {
|
||||
description: '',
|
||||
robots: ['index', 'follow'],
|
||||
@@ -145,7 +148,7 @@ module.exports = () => {
|
||||
})
|
||||
_.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
|
||||
_.set(WIKI.config, 'telemetry', {
|
||||
isEnabled: req.body.telemetry === 'true',
|
||||
isEnabled: req.body.telemetry === true,
|
||||
clientId: WIKI.telemetry.cid
|
||||
})
|
||||
_.set(WIKI.config, 'theming', {
|
||||
@@ -179,16 +182,15 @@ module.exports = () => {
|
||||
// Save config to DB
|
||||
WIKI.logger.info('Persisting config to DB...')
|
||||
await WIKI.configSvc.saveToDb([
|
||||
'auth',
|
||||
'certs',
|
||||
'company',
|
||||
'defaultEditor',
|
||||
'features',
|
||||
'graphEndpoint',
|
||||
'host',
|
||||
'lang',
|
||||
'logo',
|
||||
'mail',
|
||||
'public',
|
||||
'seo',
|
||||
'sessionSecret',
|
||||
'telemetry',
|
||||
@@ -389,8 +391,10 @@ module.exports = () => {
|
||||
|
||||
WIKI.server.on('listening', () => {
|
||||
WIKI.logger.info('HTTP Server: [ RUNNING ]')
|
||||
WIKI.logger.info('========================================')
|
||||
WIKI.logger.info(`Browse to http://localhost:${WIKI.config.port}/`)
|
||||
WIKI.logger.info('========================================')
|
||||
WIKI.logger.info('🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻🔻')
|
||||
WIKI.logger.info('')
|
||||
WIKI.logger.info(`Browse to http://localhost:${WIKI.config.port}/ to complete setup!`)
|
||||
WIKI.logger.info('')
|
||||
WIKI.logger.info('🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺🔺')
|
||||
})
|
||||
}
|
||||
|
13
server/views/unauthorized.pug
Normal file
13
server/views/unauthorized.pug
Normal file
@@ -0,0 +1,13 @@
|
||||
extends master.pug
|
||||
|
||||
block body
|
||||
#root.is-fullscreen
|
||||
v-app
|
||||
.unauthorized
|
||||
.unauthorized-content
|
||||
img.animated.fadeIn(src='/svg/icon-delete-shield.svg', alt='Unauthorized')
|
||||
.headline= t('unauthorized.title')
|
||||
.subheading.mt-3= t('unauthorized.action.' + action)
|
||||
v-btn.mt-5(color='red lighten-4', href='javascript:window.history.go(-1);', large, outline)
|
||||
v-icon(left) arrow_back
|
||||
span= t('unauthorized.goback')
|
Reference in New Issue
Block a user