| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 | const passport = require('passport')const passportJWT = require('passport-jwt')const _ = require('lodash')const path = require('path')const jwt = require('jsonwebtoken')const moment = require('moment')const securityHelper = require('../helpers/security')/* global WIKI */module.exports = {  strategies: {},  guest: {    cacheExpiration: moment.utc().subtract(1, 'd')  },  groups: {},  /**   * Initialize the authentication module   */  init() {    this.passport = passport    passport.serializeUser((user, done) => {      done(null, user.id)    })    passport.deserializeUser(async (id, done) => {      try {        const user = await WIKI.models.users.query().findById(id).modifyEager('groups', builder => {          builder.select('groups.id', 'permissions')        })        if (user) {          done(null, user)        } else {          done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)        }      } catch (err) {        done(err, null)      }    })    this.reloadGroups()    return this  },  /**   * Load authentication strategies   */  async activateStrategies() {    try {      // Unload any active strategies      WIKI.auth.strategies = {}      const currentStrategies = _.keys(passport._strategies)      _.pull(currentStrategies, 'session')      _.forEach(currentStrategies, stg => { passport.unuse(stg) })      // Load JWT      passport.use('jwt', new passportJWT.Strategy({        jwtFromRequest: securityHelper.extractJWT,        secretOrKey: WIKI.config.certs.public,        audience: WIKI.config.auth.audience,        issuer: 'urn:wiki.js'      }, (jwtPayload, cb) => {        cb(null, jwtPayload)      }))      // Load enabled strategies      const enabledStrategies = await WIKI.models.authentication.getStrategies()      for (let idx in enabledStrategies) {        const stg = enabledStrategies[idx]        if (!stg.isEnabled) { continue }        const strategy = require(`../modules/authentication/${stg.key}/authentication.js`)        stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback`        strategy.init(passport, stg.config)        strategy.config = stg.config        WIKI.auth.strategies[stg.key] = {          ...strategy,          ...stg        }        WIKI.logger.info(`Authentication Strategy ${stg.key}: [ OK ]`)      }    } catch (err) {      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          user.permissions = user.getGlobalPermissions()          req.user = 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) {          WIKI.logger.warn(err)          return next()        }      }      // JWT is NOT valid, set as guest      if (!user) {        if (WIKI.auth.guest.cacheExpiration.isSameOrBefore(moment.utc())) {          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 = [], page = false) {    const userPermissions = user.permissions ? user.permissions : user.getGlobalPermissions()    // System Admin    if (_.includes(userPermissions, 'manage:system')) {      return true    }    // Check Global Permissions    if (_.intersection(userPermissions, permissions).length < 1) {      return false    }    // Check Page Rules    if (path && user.groups) {      let checkState = {        deny: false,        match: false,        specificity: ''      }      user.groups.forEach(grp => {        const grpId = _.isObject(grp) ? _.get(grp, 'id', 0) : grp        _.get(WIKI.auth.groups, `${grpId}.pageRules`, []).forEach(rule => {          switch (rule.match) {            case 'START':              if (_.startsWith(`/${page.path}`, `/${rule.path}`)) {                checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['END', 'REGEX', 'EXACT'] })              }              break            case 'END':              if (_.endsWith(page.path, rule.path)) {                checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['REGEX', 'EXACT'] })              }              break            case 'REGEX':              const reg = new RegExp(rule.path)              if (reg.test(page.path)) {                checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['EXACT'] })              }              break            case 'EXACT':              if (`/${page.path}` === `/${rule.path}`) {                checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: [] })              }              break          }        })      })      return (checkState.match && !checkState.deny)    }    return false  },  /**   * Check and apply Page Rule specificity   *   * @access private   */  _applyPageRuleSpecificity ({ rule, checkState, higherPriority = [] }) {    if (rule.path.length === checkState.specificity.length) {      // Do not override higher priority rules      if (_.includes(higherPriority, checkState.match)) {        return checkState      }      // Do not override a previous DENY rule with same match      if (rule.match === checkState.match && checkState.deny && !rule.deny) {        return checkState      }    } else if (rule.path.length < checkState.specificity.length) {      // Do not override higher specificity rules      return checkState    }    return {      deny: rule.deny,      match: rule.match,      specificity: rule.path    }  },  /**   * Reload Groups from DB   */  async reloadGroups() {    const groupsArray = await WIKI.models.groups.query()    this.groups = _.keyBy(groupsArray, 'id')  }}
 |