You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

250 lines
7.3 KiB

  1. const passport = require('passport')
  2. const passportJWT = require('passport-jwt')
  3. const fs = require('fs-extra')
  4. const _ = require('lodash')
  5. const path = require('path')
  6. const jwt = require('jsonwebtoken')
  7. const moment = require('moment')
  8. const securityHelper = require('../helpers/security')
  9. /* global WIKI */
  10. module.exports = {
  11. strategies: {},
  12. guest: {
  13. cacheExpiration: moment.utc().subtract(1, 'd')
  14. },
  15. groups: {},
  16. /**
  17. * Initialize the authentication module
  18. */
  19. init() {
  20. this.passport = passport
  21. passport.serializeUser((user, done) => {
  22. done(null, user.id)
  23. })
  24. passport.deserializeUser(async (id, done) => {
  25. try {
  26. const user = await WIKI.models.users.query().findById(id).modifyEager('groups', builder => {
  27. builder.select('groups.id', 'permissions')
  28. })
  29. if (user) {
  30. done(null, user)
  31. } else {
  32. done(new Error(WIKI.lang.t('auth:errors:usernotfound')), null)
  33. }
  34. } catch (err) {
  35. done(err, null)
  36. }
  37. })
  38. this.reloadGroups()
  39. return this
  40. },
  41. /**
  42. * Load authentication strategies
  43. */
  44. async activateStrategies() {
  45. try {
  46. // Unload any active strategies
  47. WIKI.auth.strategies = {}
  48. const currentStrategies = _.keys(passport._strategies)
  49. _.pull(currentStrategies, 'session')
  50. _.forEach(currentStrategies, stg => { passport.unuse(stg) })
  51. // Load JWT
  52. passport.use('jwt', new passportJWT.Strategy({
  53. jwtFromRequest: securityHelper.extractJWT,
  54. secretOrKey: WIKI.config.certs.public,
  55. audience: WIKI.config.auth.audience,
  56. issuer: 'urn:wiki.js'
  57. }, (jwtPayload, cb) => {
  58. cb(null, jwtPayload)
  59. }))
  60. // Load enabled strategies
  61. const enabledStrategies = await WIKI.models.authentication.getStrategies()
  62. for (let idx in enabledStrategies) {
  63. const stg = enabledStrategies[idx]
  64. if (!stg.isEnabled) { continue }
  65. const strategy = require(`../modules/authentication/${stg.key}/authentication.js`)
  66. stg.config.callbackURL = `${WIKI.config.host}/login/${stg.key}/callback`
  67. strategy.init(passport, stg.config)
  68. fs.readFile(path.join(WIKI.ROOTPATH, `assets/svg/auth-icon-${strategy.key}.svg`), 'utf8').then(iconData => {
  69. strategy.icon = iconData
  70. }).catch(err => {
  71. if (err.code === 'ENOENT') {
  72. strategy.icon = '[missing icon]'
  73. } else {
  74. WIKI.logger.warn(err)
  75. }
  76. })
  77. WIKI.auth.strategies[stg.key] = strategy
  78. WIKI.logger.info(`Authentication Strategy ${stg.key}: [ OK ]`)
  79. }
  80. } catch (err) {
  81. WIKI.logger.error(`Authentication Strategy: [ FAILED ]`)
  82. WIKI.logger.error(err)
  83. }
  84. },
  85. /**
  86. * Authenticate current request
  87. *
  88. * @param {Express Request} req
  89. * @param {Express Response} res
  90. * @param {Express Next Callback} next
  91. */
  92. authenticate(req, res, next) {
  93. WIKI.auth.passport.authenticate('jwt', {session: false}, async (err, user, info) => {
  94. if (err) { return next() }
  95. // Expired but still valid within N days, just renew
  96. if (info instanceof Error && info.name === 'TokenExpiredError' && moment().subtract(14, 'days').isBefore(info.expiredAt)) {
  97. const jwtPayload = jwt.decode(securityHelper.extractJWT(req))
  98. try {
  99. const newToken = await WIKI.models.users.refreshToken(jwtPayload.id)
  100. user = newToken.user
  101. // Try headers, otherwise cookies for response
  102. if (req.get('content-type') === 'application/json') {
  103. res.set('new-jwt', newToken.token)
  104. } else {
  105. res.cookie('jwt', newToken.token, { expires: moment().add(365, 'days').toDate() })
  106. }
  107. } catch (err) {
  108. WIKI.logger.warn(err)
  109. return next()
  110. }
  111. }
  112. // JWT is NOT valid, set as guest
  113. if (!user) {
  114. if (true || WIKI.auth.guest.cacheExpiration.isSameOrBefore(moment.utc())) {
  115. WIKI.auth.guest = await WIKI.models.users.getGuestUser()
  116. WIKI.auth.guest.cacheExpiration = moment.utc().add(1, 'm')
  117. }
  118. req.user = WIKI.auth.guest
  119. return next()
  120. }
  121. // JWT is valid
  122. req.logIn(user, { session: false }, (err) => {
  123. if (err) { return next(err) }
  124. next()
  125. })
  126. })(req, res, next)
  127. },
  128. /**
  129. * Check if user has access to resource
  130. *
  131. * @param {User} user
  132. * @param {Array<String>} permissions
  133. * @param {String|Boolean} path
  134. */
  135. checkAccess(user, permissions = [], page = false) {
  136. // System Admin
  137. if (_.includes(user.permissions, 'manage:system')) {
  138. return true
  139. }
  140. const userPermissions = user.permissions ? user.permissions : user.getGlobalPermissions()
  141. // Check Global Permissions
  142. if (_.intersection(userPermissions, permissions).length < 1) {
  143. return false
  144. }
  145. console.info('---------------------')
  146. // Check Page Rules
  147. if (path && user.groups) {
  148. let checkState = {
  149. deny: false,
  150. match: false,
  151. specificity: ''
  152. }
  153. user.groups.forEach(grp => {
  154. const grpId = _.isObject(grp) ? _.get(grp, 'id', 0) : grp
  155. _.get(WIKI.auth.groups, `${grpId}.pageRules`, []).forEach(rule => {
  156. console.info(page.path)
  157. console.info(rule)
  158. switch(rule.match) {
  159. case 'START':
  160. if (_.startsWith(`/${page.path}`, `/${rule.path}`)) {
  161. checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['END', 'REGEX', 'EXACT'] })
  162. }
  163. break
  164. case 'END':
  165. if (_.endsWith(page.path, rule.path)) {
  166. checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['REGEX', 'EXACT'] })
  167. }
  168. break
  169. case 'REGEX':
  170. const reg = new RegExp(rule.path)
  171. if (reg.test(page.path)) {
  172. checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: ['EXACT'] })
  173. }
  174. case 'EXACT':
  175. if (`/${page.path}` === `/${rule.path}`) {
  176. checkState = this._applyPageRuleSpecificity({ rule, checkState, higherPriority: [] })
  177. }
  178. break
  179. }
  180. })
  181. })
  182. console.info('DAKSJDHKASJD')
  183. console.info(checkState)
  184. return (checkState.match && !checkState.deny)
  185. }
  186. return false
  187. },
  188. /**
  189. * Check and apply Page Rule specificity
  190. *
  191. * @access private
  192. */
  193. _applyPageRuleSpecificity ({ rule, checkState, higherPriority = [] }) {
  194. if (rule.path.length === checkState.specificity.length) {
  195. // Do not override higher priority rules
  196. if (_.includes(higherPriority, checkState.match)) {
  197. return checkState
  198. }
  199. // Do not override a previous DENY rule with same match
  200. if (rule.match === checkState.match && checkState.deny && !rule.deny) {
  201. return checkState
  202. }
  203. } else if (rule.path.length < checkState.specificity.length) {
  204. // Do not override higher specificity rules
  205. return checkState
  206. }
  207. return {
  208. deny: rule.deny,
  209. match: rule.match,
  210. specificity: rule.path
  211. }
  212. },
  213. /**
  214. * Reload Groups from DB
  215. */
  216. async reloadGroups() {
  217. const groupsArray = await WIKI.models.groups.query()
  218. this.groups = _.keyBy(groupsArray, 'id')
  219. }
  220. }