| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 | 
							- const passport = require('passport')
 
- const passportJWT = require('passport-jwt')
 
- const _ = require('lodash')
 
- const jwt = require('jsonwebtoken')
 
- const ms = require('ms')
 
- const moment = require('moment')
 
- const Promise = require('bluebird')
 
- const crypto = Promise.promisifyAll(require('crypto'))
 
- const pem2jwk = require('pem-jwk').pem2jwk
 
- const securityHelper = require('../helpers/security')
 
- /* global WIKI */
 
- module.exports = {
 
-   strategies: {},
 
-   guest: {
 
-     cacheExpiration: moment.utc().subtract(1, 'd')
 
-   },
 
-   groups: {},
 
-   validApiKeys: [],
 
-   /**
 
-    * 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).withGraphFetched('groups').modifyGraph('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()
 
-     this.reloadApiKeys()
 
-     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',
 
-         algorithms: ['RS256']
 
-       }, (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 }
 
-         try {
 
-           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 ${stg.key}: [ FAILED ]`)
 
-           WIKI.logger.error(err)
 
-         }
 
-       }
 
-     } catch (err) {
 
-       WIKI.logger.error(`Failed to initialize Authentication Strategies: [ ERROR ]`)
 
-       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(ms(WIKI.config.auth.tokenRenewal), 'ms').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()
 
-           user.groups = user.getGroups()
 
-           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 (errc) {
 
-           WIKI.logger.warn(errc)
 
-           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()
 
-       }
 
-       // Process API tokens
 
-       if (_.has(user, 'api')) {
 
-         if (!WIKI.config.api.isEnabled) {
 
-           return next(new Error('API is disabled. You must enable it from the Administration Area first.'))
 
-         } else if (_.includes(WIKI.auth.validApiKeys, user.api)) {
 
-           req.user = {
 
-             id: 1,
 
-             email: 'api@localhost',
 
-             name: 'API',
 
-             pictureUrl: null,
 
-             timezone: 'America/New_York',
 
-             localeCode: 'en',
 
-             permissions: _.get(WIKI.auth.groups, `${user.grp}.permissions`, []),
 
-             groups: [user.grp],
 
-             getGlobalPermissions () {
 
-               return req.user.permissions
 
-             },
 
-             getGroups () {
 
-               return req.user.groups
 
-             }
 
-           }
 
-           return next()
 
-         } else {
 
-           return next(new Error('API Key is invalid or was revoked.'))
 
-         }
 
-       }
 
-       // JWT is valid
 
-       req.logIn(user, { session: false }, (errc) => {
 
-         if (errc) { return next(errc) }
 
-         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 (page && 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 => {
 
-           if (_.intersection(rule.roles, permissions).length > 0) {
 
-             switch (rule.match) {
 
-               case 'START':
 
-                 if (_.startsWith(`/${page.path}`, `/${rule.path}`)) {
 
-                   checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['END', 'REGEX', 'EXACT', 'TAG'] })
 
-                 }
 
-                 break
 
-               case 'END':
 
-                 if (_.endsWith(page.path, rule.path)) {
 
-                   checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['REGEX', 'EXACT', 'TAG'] })
 
-                 }
 
-                 break
 
-               case 'REGEX':
 
-                 const reg = new RegExp(rule.path)
 
-                 if (reg.test(page.path)) {
 
-                   checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['EXACT', 'TAG'] })
 
-                 }
 
-                 break
 
-               case 'TAG':
 
-                 _.get(page, 'tags', []).forEach(tag => {
 
-                   if (tag.tag === rule.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')
 
-     WIKI.auth.guest.cacheExpiration = moment.utc().subtract(1, 'd')
 
-   },
 
-   /**
 
-    * Reload valid API Keys from DB
 
-    */
 
-   async reloadApiKeys () {
 
-     const keys = await WIKI.models.apiKeys.query().select('id').where('isRevoked', false).andWhere('expiration', '>', moment.utc().toISOString())
 
-     this.validApiKeys = _.map(keys, 'id')
 
-   },
 
-   /**
 
-    * Generate New Authentication Public / Private Key Certificates
 
-    */
 
-   async regenerateCertificates () {
 
-     WIKI.logger.info('Regenerating certificates...')
 
-     _.set(WIKI.config, 'sessionSecret', (await crypto.randomBytesAsync(32)).toString('hex'))
 
-     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),
 
-       public: certs.publicKey,
 
-       private: certs.privateKey
 
-     })
 
-     await WIKI.configSvc.saveToDb([
 
-       'certs',
 
-       'sessionSecret'
 
-     ])
 
-     await WIKI.auth.activateStrategies()
 
-     WIKI.events.outbound.emit('reloadAuthStrategies')
 
-     WIKI.logger.info('Regenerated certificates: [ COMPLETED ]')
 
-   },
 
-   /**
 
-    * Reset Guest User
 
-    */
 
-   async resetGuestUser() {
 
-     WIKI.logger.info('Resetting guest account...')
 
-     const guestGroup = await WIKI.models.groups.query().where('id', 2).first()
 
-     await WIKI.models.users.query().delete().where({
 
-       providerKey: 'local',
 
-       email: 'guest@example.com'
 
-     }).orWhere('id', 2)
 
-     const guestUser = await WIKI.models.users.query().insert({
 
-       id: 2,
 
-       provider: 'local',
 
-       email: 'guest@example.com',
 
-       name: 'Guest',
 
-       password: '',
 
-       locale: 'en',
 
-       defaultEditor: 'markdown',
 
-       tfaIsActive: false,
 
-       isSystem: true,
 
-       isActive: true,
 
-       isVerified: true
 
-     })
 
-     await guestUser.$relatedQuery('groups').relate(guestGroup.id)
 
-     WIKI.logger.info('Guest user has been reset: [ COMPLETED ]')
 
-   },
 
-   /**
 
-    * Subscribe to HA propagation events
 
-    */
 
-   subscribeToEvents() {
 
-     WIKI.events.inbound.on('reloadGroups', () => {
 
-       WIKI.auth.reloadGroups()
 
-     })
 
-     WIKI.events.inbound.on('reloadApiKeys', () => {
 
-       WIKI.auth.reloadApiKeys()
 
-     })
 
-     WIKI.events.inbound.on('reloadAuthStrategies', () => {
 
-       WIKI.auth.activateStrategies()
 
-     })
 
-   },
 
-   /**
 
-    * Get all user permissions for a specific page
 
-    */
 
-   getEffectivePermissions (req, page) {
 
-     return {
 
-       comments: {
 
-         read: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['read:comments'], page) : false,
 
-         write: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['write:comments'], page) : false,
 
-         manage: WIKI.config.features.featurePageComments ? WIKI.auth.checkAccess(req.user, ['manage:comments'], page) : false
 
-       },
 
-       history: {
 
-         read: WIKI.auth.checkAccess(req.user, ['read:history'], page)
 
-       },
 
-       source: {
 
-         read: WIKI.auth.checkAccess(req.user, ['read:source'], page)
 
-       },
 
-       pages: {
 
-         read: WIKI.auth.checkAccess(req.user, ['read:pages'], page),
 
-         write: WIKI.auth.checkAccess(req.user, ['write:pages'], page),
 
-         manage: WIKI.auth.checkAccess(req.user, ['manage:pages'], page),
 
-         delete: WIKI.auth.checkAccess(req.user, ['delete:pages'], page),
 
-         script: WIKI.auth.checkAccess(req.user, ['write:scripts'], page),
 
-         style: WIKI.auth.checkAccess(req.user, ['write:styles'], page)
 
-       },
 
-       system: {
 
-         manage: WIKI.auth.checkAccess(req.user, ['manage:system'], page)
 
-       }
 
-     }
 
-   }
 
- }
 
 
  |