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.

548 lines
17 KiB

  1. const express = require('express')
  2. const router = express.Router()
  3. const pageHelper = require('../helpers/page')
  4. const _ = require('lodash')
  5. const CleanCSS = require('clean-css')
  6. const moment = require('moment')
  7. /* global WIKI */
  8. const tmplCreateRegex = /^[0-9]+(,[0-9]+)?$/
  9. /**
  10. * Robots.txt
  11. */
  12. router.get('/robots.txt', (req, res, next) => {
  13. res.type('text/plain')
  14. if (_.includes(WIKI.config.seo.robots, 'noindex')) {
  15. res.send('User-agent: *\nDisallow: /')
  16. } else {
  17. res.status(200).end()
  18. }
  19. })
  20. /**
  21. * Health Endpoint
  22. */
  23. router.get('/healthz', (req, res, next) => {
  24. if (WIKI.models.knex.client.pool.numFree() < 1 && WIKI.models.knex.client.pool.numUsed() < 1) {
  25. res.status(503).json({ ok: false }).end()
  26. } else {
  27. res.status(200).json({ ok: true }).end()
  28. }
  29. })
  30. /**
  31. * Administration
  32. */
  33. router.get(['/a', '/a/*'], (req, res, next) => {
  34. if (!WIKI.auth.checkAccess(req.user, [
  35. 'manage:system',
  36. 'write:users',
  37. 'manage:users',
  38. 'write:groups',
  39. 'manage:groups',
  40. 'manage:navigation',
  41. 'manage:theme',
  42. 'manage:api'
  43. ])) {
  44. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  45. return res.status(403).render('unauthorized', { action: 'view' })
  46. }
  47. _.set(res.locals, 'pageMeta.title', 'Admin')
  48. res.render('admin')
  49. })
  50. /**
  51. * Download Page / Version
  52. */
  53. router.get(['/d', '/d/*'], async (req, res, next) => {
  54. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  55. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  56. const page = await WIKI.models.pages.getPageFromDb({
  57. path: pageArgs.path,
  58. locale: pageArgs.locale,
  59. userId: req.user.id,
  60. isPrivate: false
  61. })
  62. pageArgs.tags = _.get(page, 'tags', [])
  63. if (versionId > 0) {
  64. if (!WIKI.auth.checkAccess(req.user, ['read:history'], pageArgs)) {
  65. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  66. return res.render('unauthorized', { action: 'downloadVersion' })
  67. }
  68. } else {
  69. if (!WIKI.auth.checkAccess(req.user, ['read:source'], pageArgs)) {
  70. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  71. return res.render('unauthorized', { action: 'download' })
  72. }
  73. }
  74. if (page) {
  75. const fileName = _.last(page.path.split('/')) + '.' + pageHelper.getFileExtension(page.contentType)
  76. res.attachment(fileName)
  77. if (versionId > 0) {
  78. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: page.id, versionId })
  79. res.send(pageHelper.injectPageMetadata(pageVersion))
  80. } else {
  81. res.send(pageHelper.injectPageMetadata(page))
  82. }
  83. } else {
  84. res.status(404).end()
  85. }
  86. })
  87. /**
  88. * Create/Edit document
  89. */
  90. router.get(['/e', '/e/*'], async (req, res, next) => {
  91. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  92. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  93. return res.redirect(`/e/${pageArgs.locale}/${pageArgs.path}`)
  94. }
  95. req.i18n.changeLanguage(pageArgs.locale)
  96. // -> Set Editor Lang
  97. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  98. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  99. // -> Check for reserved path
  100. if (pageHelper.isReservedPath(pageArgs.path)) {
  101. return next(new Error('Cannot create this page because it starts with a system reserved path.'))
  102. }
  103. // -> Get page data from DB
  104. let page = await WIKI.models.pages.getPageFromDb({
  105. path: pageArgs.path,
  106. locale: pageArgs.locale,
  107. userId: req.user.id,
  108. isPrivate: false
  109. })
  110. pageArgs.tags = _.get(page, 'tags', [])
  111. // -> Effective Permissions
  112. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  113. const injectCode = {
  114. css: WIKI.config.theming.injectCSS,
  115. head: WIKI.config.theming.injectHead,
  116. body: WIKI.config.theming.injectBody
  117. }
  118. if (page) {
  119. // -> EDIT MODE
  120. if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
  121. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  122. return res.render('unauthorized', { action: 'edit' })
  123. }
  124. // -> Get page tags
  125. await page.$relatedQuery('tags')
  126. page.tags = _.map(page.tags, 'tag')
  127. // -> Beautify Script CSS
  128. if (!_.isEmpty(page.extra.css)) {
  129. page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
  130. }
  131. _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
  132. _.set(res.locals, 'pageMeta.description', page.description)
  133. page.mode = 'update'
  134. page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
  135. page.content = Buffer.from(page.content).toString('base64')
  136. } else {
  137. // -> CREATE MODE
  138. if (!effectivePermissions.pages.write) {
  139. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  140. return res.render('unauthorized', { action: 'create' })
  141. }
  142. _.set(res.locals, 'pageMeta.title', `New Page`)
  143. page = {
  144. path: pageArgs.path,
  145. localeCode: pageArgs.locale,
  146. editorKey: null,
  147. mode: 'create',
  148. content: null,
  149. title: null,
  150. description: null,
  151. updatedAt: new Date().toISOString(),
  152. extra: {
  153. css: '',
  154. js: ''
  155. }
  156. }
  157. // -> From Template
  158. if (req.query.from && tmplCreateRegex.test(req.query.from)) {
  159. let tmplPageId = 0
  160. let tmplVersionId = 0
  161. if (req.query.from.indexOf(',')) {
  162. const q = req.query.from.split(',')
  163. tmplPageId = _.toSafeInteger(q[0])
  164. tmplVersionId = _.toSafeInteger(q[1])
  165. } else {
  166. tmplPageId = _.toSafeInteger(req.query.from)
  167. }
  168. if (tmplVersionId > 0) {
  169. // -> From Page Version
  170. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: tmplPageId, versionId: tmplVersionId })
  171. if (!pageVersion) {
  172. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  173. return res.status(404).render('notfound', { action: 'template' })
  174. }
  175. if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
  176. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  177. return res.render('unauthorized', { action: 'sourceVersion' })
  178. }
  179. page.content = Buffer.from(pageVersion.content).toString('base64')
  180. page.editorKey = pageVersion.editor
  181. page.title = pageVersion.title
  182. page.description = pageVersion.description
  183. } else {
  184. // -> From Page Live
  185. const pageOriginal = await WIKI.models.pages.query().findById(tmplPageId)
  186. if (!pageOriginal) {
  187. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  188. return res.status(404).render('notfound', { action: 'template' })
  189. }
  190. if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
  191. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  192. return res.render('unauthorized', { action: 'source' })
  193. }
  194. page.content = Buffer.from(pageOriginal.content).toString('base64')
  195. page.editorKey = pageOriginal.editorKey
  196. page.title = pageOriginal.title
  197. page.description = pageOriginal.description
  198. }
  199. }
  200. }
  201. res.render('editor', { page, injectCode, effectivePermissions })
  202. })
  203. /**
  204. * History
  205. */
  206. router.get(['/h', '/h/*'], async (req, res, next) => {
  207. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  208. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  209. return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
  210. }
  211. req.i18n.changeLanguage(pageArgs.locale)
  212. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  213. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  214. const page = await WIKI.models.pages.getPageFromDb({
  215. path: pageArgs.path,
  216. locale: pageArgs.locale,
  217. userId: req.user.id,
  218. isPrivate: false
  219. })
  220. if (!page) {
  221. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  222. return res.status(404).render('notfound', { action: 'history' })
  223. }
  224. pageArgs.tags = _.get(page, 'tags', [])
  225. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  226. if (!effectivePermissions.history.read) {
  227. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  228. return res.render('unauthorized', { action: 'history' })
  229. }
  230. if (page) {
  231. _.set(res.locals, 'pageMeta.title', page.title)
  232. _.set(res.locals, 'pageMeta.description', page.description)
  233. res.render('history', { page, effectivePermissions })
  234. } else {
  235. res.redirect(`/${pageArgs.path}`)
  236. }
  237. })
  238. /**
  239. * Page ID redirection
  240. */
  241. router.get(['/i', '/i/:id'], async (req, res, next) => {
  242. const pageId = _.toSafeInteger(req.params.id)
  243. if (pageId <= 0) {
  244. return res.redirect('/')
  245. }
  246. const page = await WIKI.models.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
  247. if (!page) {
  248. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  249. return res.status(404).render('notfound', { action: 'view' })
  250. }
  251. if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
  252. locale: page.localeCode,
  253. path: page.path,
  254. private: page.isPrivate,
  255. privateNS: page.privateNS,
  256. explicitLocale: false,
  257. tags: page.tags
  258. })) {
  259. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  260. return res.render('unauthorized', { action: 'view' })
  261. }
  262. if (WIKI.config.lang.namespacing) {
  263. return res.redirect(`/${page.localeCode}/${page.path}`)
  264. } else {
  265. return res.redirect(`/${page.path}`)
  266. }
  267. })
  268. /**
  269. * Profile
  270. */
  271. router.get(['/p', '/p/*'], (req, res, next) => {
  272. if (!req.user || req.user.id < 1 || req.user.id === 2) {
  273. return res.render('unauthorized', { action: 'view' })
  274. }
  275. _.set(res.locals, 'pageMeta.title', 'User Profile')
  276. res.render('profile')
  277. })
  278. /**
  279. * Source
  280. */
  281. router.get(['/s', '/s/*'], async (req, res, next) => {
  282. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  283. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  284. const page = await WIKI.models.pages.getPageFromDb({
  285. path: pageArgs.path,
  286. locale: pageArgs.locale,
  287. userId: req.user.id,
  288. isPrivate: false
  289. })
  290. pageArgs.tags = _.get(page, 'tags', [])
  291. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  292. return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
  293. }
  294. // -> Effective Permissions
  295. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  296. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  297. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  298. if (versionId > 0) {
  299. if (!effectivePermissions.history.read) {
  300. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  301. return res.render('unauthorized', { action: 'sourceVersion' })
  302. }
  303. } else {
  304. if (!effectivePermissions.source.read) {
  305. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  306. return res.render('unauthorized', { action: 'source' })
  307. }
  308. }
  309. if (page) {
  310. if (versionId > 0) {
  311. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: page.id, versionId })
  312. _.set(res.locals, 'pageMeta.title', pageVersion.title)
  313. _.set(res.locals, 'pageMeta.description', pageVersion.description)
  314. res.render('source', {
  315. page: {
  316. ...page,
  317. ...pageVersion
  318. }
  319. })
  320. } else {
  321. _.set(res.locals, 'pageMeta.title', page.title)
  322. _.set(res.locals, 'pageMeta.description', page.description)
  323. res.render('source', { page, effectivePermissions })
  324. }
  325. } else {
  326. res.redirect(`/${pageArgs.path}`)
  327. }
  328. })
  329. /**
  330. * Tags
  331. */
  332. router.get(['/t', '/t/*'], (req, res, next) => {
  333. _.set(res.locals, 'pageMeta.title', 'Tags')
  334. res.render('tags')
  335. })
  336. /**
  337. * View document / asset
  338. */
  339. router.get('/*', async (req, res, next) => {
  340. const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
  341. const pageArgs = pageHelper.parsePath(req.path, { stripExt })
  342. const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
  343. if (isPage) {
  344. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  345. return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
  346. }
  347. req.i18n.changeLanguage(pageArgs.locale)
  348. try {
  349. // -> Get Page from cache
  350. const page = await WIKI.models.pages.getPage({
  351. path: pageArgs.path,
  352. locale: pageArgs.locale,
  353. userId: req.user.id,
  354. isPrivate: false
  355. })
  356. pageArgs.tags = _.get(page, 'tags', [])
  357. // -> Effective Permissions
  358. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  359. // -> Check User Access
  360. if (!effectivePermissions.pages.read) {
  361. if (req.user.id === 2) {
  362. res.cookie('loginRedirect', req.path, {
  363. maxAge: 15 * 60 * 1000
  364. })
  365. }
  366. if (pageArgs.path === 'home' && req.user.id === 2) {
  367. return res.redirect('/login')
  368. }
  369. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  370. return res.status(403).render('unauthorized', {
  371. action: 'view'
  372. })
  373. }
  374. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  375. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  376. if (page) {
  377. _.set(res.locals, 'pageMeta.title', page.title)
  378. _.set(res.locals, 'pageMeta.description', page.description)
  379. // -> Check Publishing State
  380. let pageIsPublished = page.isPublished
  381. if (pageIsPublished && !_.isEmpty(page.publishStartDate)) {
  382. pageIsPublished = moment(page.publishStartDate).isSameOrBefore()
  383. }
  384. if (pageIsPublished && !_.isEmpty(page.publishEndDate)) {
  385. pageIsPublished = moment(page.publishEndDate).isSameOrAfter()
  386. }
  387. if (!pageIsPublished && !effectivePermissions.pages.write) {
  388. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  389. return res.status(403).render('unauthorized', {
  390. action: 'view'
  391. })
  392. }
  393. // -> Build sidebar navigation
  394. let sdi = 1
  395. const sidebar = (await WIKI.models.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({
  396. i: `sdi-${sdi++}`,
  397. k: n.kind,
  398. l: n.label,
  399. c: n.icon,
  400. y: n.targetType,
  401. t: n.target
  402. }))
  403. // -> Build theme code injection
  404. const injectCode = {
  405. css: WIKI.config.theming.injectCSS,
  406. head: WIKI.config.theming.injectHead,
  407. body: WIKI.config.theming.injectBody
  408. }
  409. if (!_.isEmpty(page.extra.css)) {
  410. injectCode.css = `${injectCode.css}\n${page.extra.css}`
  411. }
  412. if (!_.isEmpty(page.extra.js)) {
  413. injectCode.body = `${injectCode.body}\n${page.extra.js}`
  414. }
  415. if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
  416. // -> Convert page TOC
  417. if (_.isString(page.toc)) {
  418. page.toc = JSON.parse(page.toc)
  419. }
  420. // -> Render legacy view
  421. res.render('legacy/page', {
  422. page,
  423. sidebar,
  424. injectCode,
  425. isAuthenticated: req.user && req.user.id !== 2
  426. })
  427. } else {
  428. // -> Convert page TOC
  429. if (!_.isString(page.toc)) {
  430. page.toc = JSON.stringify(page.toc)
  431. }
  432. // -> Inject comments variables
  433. if (WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
  434. [
  435. { key: 'pageUrl', value: `${WIKI.config.host}/i/${page.id}` },
  436. { key: 'pageId', value: page.id }
  437. ].forEach((cfg) => {
  438. WIKI.data.commentProvider.head = _.replace(WIKI.data.commentProvider.head, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  439. WIKI.data.commentProvider.body = _.replace(WIKI.data.commentProvider.body, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  440. WIKI.data.commentProvider.main = _.replace(WIKI.data.commentProvider.main, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  441. })
  442. }
  443. // -> Render view
  444. res.render('page', {
  445. page,
  446. sidebar,
  447. injectCode,
  448. comments: WIKI.data.commentProvider,
  449. effectivePermissions
  450. })
  451. }
  452. } else if (pageArgs.path === 'home') {
  453. _.set(res.locals, 'pageMeta.title', 'Welcome')
  454. res.render('welcome', { locale: pageArgs.locale })
  455. } else {
  456. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  457. if (effectivePermissions.pages.write) {
  458. res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
  459. } else {
  460. res.status(404).render('notfound', { action: 'view' })
  461. }
  462. }
  463. } catch (err) {
  464. next(err)
  465. }
  466. } else {
  467. if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
  468. return res.sendStatus(403)
  469. }
  470. await WIKI.models.assets.getAsset(pageArgs.path, res)
  471. }
  472. })
  473. module.exports = router