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.

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