common.js 17.2 KB
Newer Older
1 2
const express = require('express')
const router = express.Router()
3
const pageHelper = require('../helpers/page')
4
const _ = require('lodash')
NGPixel's avatar
NGPixel committed
5
const CleanCSS = require('clean-css')
6
const moment = require('moment')
7 8

/* global WIKI */
NGPixel's avatar
NGPixel committed
9

10 11
const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/

12 13 14 15 16 17
/**
 * Robots.txt
 */
router.get('/robots.txt', (req, res, next) => {
  res.type('text/plain')
  if (_.includes(WIKI.config.seo.robots, 'noindex')) {
18
    res.send('User-agent: *\nDisallow: /')
19 20 21 22 23
  } else {
    res.status(200).end()
  }
})

Nick's avatar
Nick committed
24 25 26 27 28 29 30 31 32 33 34
/**
 * Health Endpoint
 */
router.get('/healthz', (req, res, next) => {
  if (WIKI.models.knex.client.pool.numFree() < 1 && WIKI.models.knex.client.pool.numUsed() < 1) {
    res.status(503).json({ ok: false }).end()
  } else {
    res.status(200).json({ ok: true }).end()
  }
})

35 36 37 38
/**
 * Administration
 */
router.get(['/a', '/a/*'], (req, res, next) => {
39 40 41 42 43 44 45 46 47 48 49
  if (!WIKI.auth.checkAccess(req.user, [
    'manage:system',
    'write:users',
    'manage:users',
    'write:groups',
    'manage:groups',
    'manage:navigation',
    'manage:theme',
    'manage:api'
  ])) {
    _.set(res.locals, 'pageMeta.title', 'Unauthorized')
50
    return res.status(403).render('unauthorized', { action: 'view' })
51 52
  }

53 54 55 56
  _.set(res.locals, 'pageMeta.title', 'Admin')
  res.render('admin')
})

57 58 59 60 61
/**
 * Download Page / Version
 */
router.get(['/d', '/d/*'], async (req, res, next) => {
  const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
NGPixel's avatar
NGPixel committed
62

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
  const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0

  const page = await WIKI.models.pages.getPageFromDb({
    path: pageArgs.path,
    locale: pageArgs.locale,
    userId: req.user.id,
    isPrivate: false
  })

  pageArgs.tags = _.get(page, 'tags', [])

  if (versionId > 0) {
    if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
      _.set(res.locals, 'pageMeta.title', 'Unauthorized')
      return res.render('unauthorized', { action: 'downloadVersion' })
    }
  } else {
    if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
      _.set(res.locals, 'pageMeta.title', 'Unauthorized')
      return res.render('unauthorized', { action: 'download' })
    }
  }

  if (page) {
    const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType)
    res.attachment(fileName)
    if (versionId > 0) {
      const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: page.id, versionId })
      res.send(pageHelper.injectPageMetadata(pageVersion))
    } else {
      res.send(pageHelper.injectPageMetadata(page))
    }
  } else {
    res.status(404).end()
  }
})

NGPixel's avatar
NGPixel committed
100 101 102
/**
 * Create/Edit document
 */
103
router.get(['/e', '/e/*'], async (req, res, next) => {
104
  const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
105

106 107 108 109
  if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
    return res.redirect(`/e/${pageArgs.locale}/${pageArgs.path}`)
  }

110
  req.i18n.changeLanguage(pageArgs.locale)
NGPixel's avatar
NGPixel committed
111

112
  // -> Set Editor Lang
113
  _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
NGPixel's avatar
NGPixel committed
114
  _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
115

116
  // -> Check for reserved path
117 118 119 120
  if (pageHelper.isReservedPath(pageArgs.path)) {
    return next(new Error('Cannot create this page because it starts with a system reserved path.'))
  }

121
  // -> Get page data from DB
122 123 124 125 126 127
  let page = await WIKI.models.pages.getPageFromDb({
    path: pageArgs.path,
    locale: pageArgs.locale,
    userId: req.user.id,
    isPrivate: false
  })
128

129 130
  pageArgs.tags = _.get(page, 'tags', [])

131 132 133
  // -> Effective Permissions
  const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)

134 135 136 137 138 139
  const injectCode = {
    css: WIKI.config.theming.injectCSS,
    head: WIKI.config.theming.injectHead,
    body: WIKI.config.theming.injectBody
  }

140
  if (page) {
141
    // -> EDIT MODE
142
    if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
143
      _.set(res.locals, 'pageMeta.title', 'Unauthorized')
144
      return res.render('unauthorized', { action: 'edit' })
145 146
    }

147
    // -> Get page tags
Nick's avatar
Nick committed
148 149 150
    await page.$relatedQuery('tags')
    page.tags = _.map(page.tags, 'tag')

151 152 153
    // Handle missing extra field
    page.extra = page.extra || { css: '', js: '' }

NGPixel's avatar
NGPixel committed
154 155 156 157 158
    // -> Beautify Script CSS
    if (!_.isEmpty(page.extra.css)) {
      page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
    }

159 160
    _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
    _.set(res.locals, 'pageMeta.description', page.description)
161 162 163 164
    page.mode = 'update'
    page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
    page.content = Buffer.from(page.content).toString('base64')
  } else {
165
    // -> CREATE MODE
166
    if (!effectivePermissions.pages.write) {
167
      _.set(res.locals, 'pageMeta.title', 'Unauthorized')
168
      return res.render('unauthorized', { action: 'create' })
169 170
    }

171
    _.set(res.locals, 'pageMeta.title', `New Page`)
172 173 174 175 176
    page = {
      path: pageArgs.path,
      localeCode: pageArgs.locale,
      editorKey: null,
      mode: 'create',
177 178
      content: null,
      title: null,
179
      description: null,
180 181 182 183 184
      updatedAt: new Date().toISOString(),
      extra: {
        css: '',
        js: ''
      }
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
    }

    // -> From Template
    if (req.query.from && tmplCreateRegex.test(req.query.from)) {
      let tmplPageId = 0
      let tmplVersionId = 0
      if (req.query.from.indexOf(',')) {
        const q = req.query.from.split(',')
        tmplPageId = _.toSafeInteger(q[0])
        tmplVersionId = _.toSafeInteger(q[1])
      } else {
        tmplPageId = _.toSafeInteger(req.query.from)
      }

      if (tmplVersionId > 0) {
        // -> From Page Version
        const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: tmplPageId, versionId: tmplVersionId })
        if (!pageVersion) {
          _.set(res.locals, 'pageMeta.title', 'Page Not Found')
          return res.status(404).render('notfound', { action: 'template' })
        }
        if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
          _.set(res.locals, 'pageMeta.title', 'Unauthorized')
          return res.render('unauthorized', { action: 'sourceVersion' })
        }
        page.content = Buffer.from(pageVersion.content).toString('base64')
        page.editorKey = pageVersion.editor
        page.title = pageVersion.title
        page.description = pageVersion.description
      } else {
        // -> From Page Live
        const pageOriginal = await WIKI.models.pages.query().findById(tmplPageId)
        if (!pageOriginal) {
          _.set(res.locals, 'pageMeta.title', 'Page Not Found')
          return res.status(404).render('notfound', { action: 'template' })
        }
        if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
          _.set(res.locals, 'pageMeta.title', 'Unauthorized')
          return res.render('unauthorized', { action: 'source' })
        }
        page.content = Buffer.from(pageOriginal.content).toString('base64')
        page.editorKey = pageOriginal.editorKey
        page.title = pageOriginal.title
        page.description = pageOriginal.description
      }
230 231
    }
  }
232 233

  res.render('editor', { page, injectCode, effectivePermissions })
NGPixel's avatar
NGPixel committed
234 235
})

236
/**
237
 * History
238 239
 */
router.get(['/h', '/h/*'], async (req, res, next) => {
240
  const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
241

242 243 244
  if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
    return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
  }
NGPixel's avatar
NGPixel committed
245

246
  req.i18n.changeLanguage(pageArgs.locale)
247

248
  _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
NGPixel's avatar
NGPixel committed
249
  _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
250

251
  const page = await WIKI.models.pages.getPageFromDb({
252 253 254 255 256
    path: pageArgs.path,
    locale: pageArgs.locale,
    userId: req.user.id,
    isPrivate: false
  })
257

258 259 260 261 262
  if (!page) {
    _.set(res.locals, 'pageMeta.title', 'Page Not Found')
    return res.status(404).render('notfound', { action: 'history' })
  }

263 264
  pageArgs.tags = _.get(page, 'tags', [])

265 266 267
  const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)

  if (!effectivePermissions.history.read) {
268 269 270 271
    _.set(res.locals, 'pageMeta.title', 'Unauthorized')
    return res.render('unauthorized', { action: 'history' })
  }

272
  if (page) {
273 274
    _.set(res.locals, 'pageMeta.title', page.title)
    _.set(res.locals, 'pageMeta.description', page.description)
275 276

    res.render('history', { page, effectivePermissions })
277 278 279 280 281
  } else {
    res.redirect(`/${pageArgs.path}`)
  }
})

NGPixel's avatar
NGPixel committed
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
/**
 * Page ID redirection
 */
router.get(['/i', '/i/:id'], async (req, res, next) => {
  const pageId = _.toSafeInteger(req.params.id)
  if (pageId <= 0) {
    return res.redirect('/')
  }

  const page = await WIKI.models.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
  if (!page) {
    _.set(res.locals, 'pageMeta.title', 'Page Not Found')
    return res.status(404).render('notfound', { action: 'view' })
  }

  if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
    locale: page.localeCode,
    path: page.path,
    private: page.isPrivate,
    privateNS: page.privateNS,
302 303
    explicitLocale: false,
    tags: page.tags
NGPixel's avatar
NGPixel committed
304 305 306 307 308 309 310 311 312 313 314 315
  })) {
    _.set(res.locals, 'pageMeta.title', 'Unauthorized')
    return res.render('unauthorized', { action: 'view' })
  }

  if (WIKI.config.lang.namespacing) {
    return res.redirect(`/${page.localeCode}/${page.path}`)
  } else {
    return res.redirect(`/${page.path}`)
  }
})

316 317 318 319
/**
 * Profile
 */
router.get(['/p', '/p/*'], (req, res, next) => {
320 321 322 323
  if (!req.user || req.user.id < 1 || req.user.id === 2) {
    return res.render('unauthorized', { action: 'view' })
  }

324 325 326 327
  _.set(res.locals, 'pageMeta.title', 'User Profile')
  res.render('profile')
})

328 329 330 331
/**
 * Source
 */
router.get(['/s', '/s/*'], async (req, res, next) => {
332
  const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
333 334
  const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0

335 336 337 338 339 340 341 342
  const page = await WIKI.models.pages.getPageFromDb({
    path: pageArgs.path,
    locale: pageArgs.locale,
    userId: req.user.id,
    isPrivate: false
  })

  pageArgs.tags = _.get(page, 'tags', [])
343

344 345 346 347
  if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
    return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
  }

348 349 350
  // -> Effective Permissions
  const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)

351
  _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
NGPixel's avatar
NGPixel committed
352
  _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
353

354
  if (versionId > 0) {
355
    if (!effectivePermissions.history.read) {
356 357 358 359
      _.set(res.locals, 'pageMeta.title', 'Unauthorized')
      return res.render('unauthorized', { action: 'sourceVersion' })
    }
  } else {
360
    if (!effectivePermissions.source.read) {
361 362 363
      _.set(res.locals, 'pageMeta.title', 'Unauthorized')
      return res.render('unauthorized', { action: 'source' })
    }
364 365
  }

366
  if (page) {
367 368 369 370 371 372 373 374
    if (versionId > 0) {
      const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: page.id, versionId })
      _.set(res.locals, 'pageMeta.title', pageVersion.title)
      _.set(res.locals, 'pageMeta.description', pageVersion.description)
      res.render('source', {
        page: {
          ...page,
          ...pageVersion
375 376
        },
        effectivePermissions
377 378 379 380
      })
    } else {
      _.set(res.locals, 'pageMeta.title', page.title)
      _.set(res.locals, 'pageMeta.description', page.description)
381 382

      res.render('source', { page, effectivePermissions })
383
    }
384 385 386 387 388
  } else {
    res.redirect(`/${pageArgs.path}`)
  }
})

389 390 391 392 393 394 395 396
/**
 * Tags
 */
router.get(['/t', '/t/*'], (req, res, next) => {
  _.set(res.locals, 'pageMeta.title', 'Tags')
  res.render('tags')
})

NGPixel's avatar
NGPixel committed
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
/**
 * User Avatar
 */
router.get('/_userav/:uid', async (req, res, next) => {
  if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
    return res.sendStatus(403)
  }
  const av = await WIKI.models.users.getUserAvatarData(req.params.uid)
  if (av) {
    res.set('Content-Type', 'image/jpeg')
    res.send(av)
  }

  return res.sendStatus(404)
})

NGPixel's avatar
NGPixel committed
413
/**
414
 * View document / asset
NGPixel's avatar
NGPixel committed
415
 */
416
router.get('/*', async (req, res, next) => {
417 418 419
  const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
  const pageArgs = pageHelper.parsePath(req.path, { stripExt })
  const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
420

421
  if (isPage) {
422 423 424 425
    if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
      return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
    }

426 427
    req.i18n.changeLanguage(pageArgs.locale)

428
    try {
429
      // -> Get Page from cache
430 431 432 433 434 435
      const page = await WIKI.models.pages.getPage({
        path: pageArgs.path,
        locale: pageArgs.locale,
        userId: req.user.id,
        isPrivate: false
      })
436 437
      pageArgs.tags = _.get(page, 'tags', [])

438 439 440
      // -> Effective Permissions
      const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)

441
      // -> Check User Access
442
      if (!effectivePermissions.pages.read) {
443 444 445 446 447
        if (req.user.id === 2) {
          res.cookie('loginRedirect', req.path, {
            maxAge: 15 * 60 * 1000
          })
        }
448
        if (pageArgs.path === 'home' && req.user.id === 2) {
449 450 451 452 453 454 455
          return res.redirect('/login')
        }
        _.set(res.locals, 'pageMeta.title', 'Unauthorized')
        return res.status(403).render('unauthorized', {
          action: 'view'
        })
      }
456 457

      _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
NGPixel's avatar
NGPixel committed
458
      _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
459 460 461 462

      if (page) {
        _.set(res.locals, 'pageMeta.title', page.title)
        _.set(res.locals, 'pageMeta.description', page.description)
463

464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
        // -> Check Publishing State
        let pageIsPublished = page.isPublished
        if (pageIsPublished && !_.isEmpty(page.publishStartDate)) {
          pageIsPublished = moment(page.publishStartDate).isSameOrBefore()
        }
        if (pageIsPublished && !_.isEmpty(page.publishEndDate)) {
          pageIsPublished = moment(page.publishEndDate).isSameOrAfter()
        }
        if (!pageIsPublished && !effectivePermissions.pages.write) {
          _.set(res.locals, 'pageMeta.title', 'Unauthorized')
          return res.status(403).render('unauthorized', {
            action: 'view'
          })
        }

479
        // -> Build sidebar navigation
480 481 482 483 484 485 486 487 488 489
        let sdi = 1
        const sidebar = (await WIKI.models.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({
          i: `sdi-${sdi++}`,
          k: n.kind,
          l: n.label,
          c: n.icon,
          y: n.targetType,
          t: n.target
        }))

490
        // -> Build theme code injection
491 492 493 494 495
        const injectCode = {
          css: WIKI.config.theming.injectCSS,
          head: WIKI.config.theming.injectHead,
          body: WIKI.config.theming.injectBody
        }
496

497 498 499
        // Handle missing extra field
        page.extra = page.extra || { css: '', js: '' }

NGPixel's avatar
NGPixel committed
500 501 502 503 504 505 506 507
        if (!_.isEmpty(page.extra.css)) {
          injectCode.css = `${injectCode.css}\n${page.extra.css}`
        }

        if (!_.isEmpty(page.extra.js)) {
          injectCode.body = `${injectCode.body}\n${page.extra.js}`
        }

508
        if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
NGPixel's avatar
NGPixel committed
509 510 511 512 513
          // -> Convert page TOC
          if (_.isString(page.toc)) {
            page.toc = JSON.parse(page.toc)
          }

514 515 516 517 518 519 520
          // -> Render legacy view
          res.render('legacy/page', {
            page,
            sidebar,
            injectCode,
            isAuthenticated: req.user && req.user.id !== 2
          })
521
        } else {
NGPixel's avatar
NGPixel committed
522 523 524 525 526
          // -> Convert page TOC
          if (!_.isString(page.toc)) {
            page.toc = JSON.stringify(page.toc)
          }

527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
          // -> Inject comments variables
          if (WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
            [
              { key: 'pageUrl', value: `${WIKI.config.host}/i/${page.id}` },
              { key: 'pageId', value: page.id }
            ].forEach((cfg) => {
              WIKI.data.commentProvider.head = _.replace(WIKI.data.commentProvider.head, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
              WIKI.data.commentProvider.body = _.replace(WIKI.data.commentProvider.body, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
              WIKI.data.commentProvider.main = _.replace(WIKI.data.commentProvider.main, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
            })
          }

          // -> Render view
          res.render('page', {
            page,
            sidebar,
            injectCode,
544
            comments: WIKI.data.commentProvider,
545
            effectivePermissions
546
          })
547
        }
548 549
      } else if (pageArgs.path === 'home') {
        _.set(res.locals, 'pageMeta.title', 'Welcome')
550
        res.render('welcome', { locale: pageArgs.locale })
551
      } else {
552
        _.set(res.locals, 'pageMeta.title', 'Page Not Found')
553
        if (effectivePermissions.pages.write) {
554
          res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
555 556 557
        } else {
          res.status(404).render('notfound', { action: 'view' })
        }
558
      }
559 560
    } catch (err) {
      next(err)
561
    }
562
  } else {
563 564
    if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
      return res.sendStatus(403)
565
    }
566 567

    await WIKI.models.assets.getAsset(pageArgs.path, res)
568
  }
569 570
})

571
module.exports = router