common.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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. }
  139. // -> From Template
  140. if (req.query.from && tmplCreateRegex.test(req.query.from)) {
  141. let tmplPageId = 0
  142. let tmplVersionId = 0
  143. if (req.query.from.indexOf(',')) {
  144. const q = req.query.from.split(',')
  145. tmplPageId = _.toSafeInteger(q[0])
  146. tmplVersionId = _.toSafeInteger(q[1])
  147. } else {
  148. tmplPageId = _.toSafeInteger(req.query.from)
  149. }
  150. if (tmplVersionId > 0) {
  151. // -> From Page Version
  152. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: tmplPageId, versionId: tmplVersionId })
  153. if (!pageVersion) {
  154. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  155. return res.status(404).render('notfound', { action: 'template' })
  156. }
  157. if (!WIKI.auth.checkAccess(req.user, ['read:history'], { path: pageVersion.path, locale: pageVersion.locale })) {
  158. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  159. return res.render('unauthorized', { action: 'sourceVersion' })
  160. }
  161. page.content = Buffer.from(pageVersion.content).toString('base64')
  162. page.editorKey = pageVersion.editor
  163. page.title = pageVersion.title
  164. page.description = pageVersion.description
  165. } else {
  166. // -> From Page Live
  167. const pageOriginal = await WIKI.models.pages.query().findById(tmplPageId)
  168. if (!pageOriginal) {
  169. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  170. return res.status(404).render('notfound', { action: 'template' })
  171. }
  172. if (!WIKI.auth.checkAccess(req.user, ['read:source'], { path: pageOriginal.path, locale: pageOriginal.locale })) {
  173. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  174. return res.render('unauthorized', { action: 'source' })
  175. }
  176. page.content = Buffer.from(pageOriginal.content).toString('base64')
  177. page.editorKey = pageOriginal.editorKey
  178. page.title = pageOriginal.title
  179. page.description = pageOriginal.description
  180. }
  181. }
  182. }
  183. res.render('editor', { page, injectCode, effectivePermissions })
  184. })
  185. /**
  186. * History
  187. */
  188. router.get(['/h', '/h/*'], async (req, res, next) => {
  189. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  190. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  191. return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
  192. }
  193. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  194. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  195. const page = await WIKI.models.pages.getPageFromDb({
  196. path: pageArgs.path,
  197. locale: pageArgs.locale,
  198. userId: req.user.id,
  199. isPrivate: false
  200. })
  201. if (!page) {
  202. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  203. return res.status(404).render('notfound', { action: 'history' })
  204. }
  205. pageArgs.tags = _.get(page, 'tags', [])
  206. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  207. if (!effectivePermissions.history.read) {
  208. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  209. return res.render('unauthorized', { action: 'history' })
  210. }
  211. if (page) {
  212. _.set(res.locals, 'pageMeta.title', page.title)
  213. _.set(res.locals, 'pageMeta.description', page.description)
  214. res.render('history', { page, effectivePermissions })
  215. } else {
  216. res.redirect(`/${pageArgs.path}`)
  217. }
  218. })
  219. /**
  220. * Page ID redirection
  221. */
  222. router.get(['/i', '/i/:id'], async (req, res, next) => {
  223. const pageId = _.toSafeInteger(req.params.id)
  224. if (pageId <= 0) {
  225. return res.redirect('/')
  226. }
  227. const page = await WIKI.models.pages.query().column(['path', 'localeCode', 'isPrivate', 'privateNS']).findById(pageId)
  228. if (!page) {
  229. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  230. return res.status(404).render('notfound', { action: 'view' })
  231. }
  232. if (!WIKI.auth.checkAccess(req.user, ['read:pages'], {
  233. locale: page.localeCode,
  234. path: page.path,
  235. private: page.isPrivate,
  236. privateNS: page.privateNS,
  237. explicitLocale: false,
  238. tags: page.tags
  239. })) {
  240. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  241. return res.render('unauthorized', { action: 'view' })
  242. }
  243. if (WIKI.config.lang.namespacing) {
  244. return res.redirect(`/${page.localeCode}/${page.path}`)
  245. } else {
  246. return res.redirect(`/${page.path}`)
  247. }
  248. })
  249. /**
  250. * Profile
  251. */
  252. router.get(['/p', '/p/*'], (req, res, next) => {
  253. if (!req.user || req.user.id < 1 || req.user.id === 2) {
  254. return res.render('unauthorized', { action: 'view' })
  255. }
  256. _.set(res.locals, 'pageMeta.title', 'User Profile')
  257. res.render('profile')
  258. })
  259. /**
  260. * Source
  261. */
  262. router.get(['/s', '/s/*'], async (req, res, next) => {
  263. const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
  264. const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
  265. const page = await WIKI.models.pages.getPageFromDb({
  266. path: pageArgs.path,
  267. locale: pageArgs.locale,
  268. userId: req.user.id,
  269. isPrivate: false
  270. })
  271. pageArgs.tags = _.get(page, 'tags', [])
  272. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  273. return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
  274. }
  275. // -> Effective Permissions
  276. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  277. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  278. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  279. if (versionId > 0) {
  280. if (!effectivePermissions.history.read) {
  281. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  282. return res.render('unauthorized', { action: 'sourceVersion' })
  283. }
  284. } else {
  285. if (!effectivePermissions.source.read) {
  286. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  287. return res.render('unauthorized', { action: 'source' })
  288. }
  289. }
  290. if (page) {
  291. if (versionId > 0) {
  292. const pageVersion = await WIKI.models.pageHistory.getVersion({ pageId: page.id, versionId })
  293. _.set(res.locals, 'pageMeta.title', pageVersion.title)
  294. _.set(res.locals, 'pageMeta.description', pageVersion.description)
  295. res.render('source', {
  296. page: {
  297. ...page,
  298. ...pageVersion
  299. }
  300. })
  301. } else {
  302. _.set(res.locals, 'pageMeta.title', page.title)
  303. _.set(res.locals, 'pageMeta.description', page.description)
  304. res.render('source', { page, effectivePermissions })
  305. }
  306. } else {
  307. res.redirect(`/${pageArgs.path}`)
  308. }
  309. })
  310. /**
  311. * Tags
  312. */
  313. router.get(['/t', '/t/*'], (req, res, next) => {
  314. _.set(res.locals, 'pageMeta.title', 'Tags')
  315. res.render('tags')
  316. })
  317. /**
  318. * View document / asset
  319. */
  320. router.get('/*', async (req, res, next) => {
  321. const stripExt = _.some(WIKI.data.pageExtensions, ext => _.endsWith(req.path, `.${ext}`))
  322. const pageArgs = pageHelper.parsePath(req.path, { stripExt })
  323. const isPage = (stripExt || pageArgs.path.indexOf('.') === -1)
  324. if (isPage) {
  325. if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
  326. return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
  327. }
  328. req.i18n.changeLanguage(pageArgs.locale)
  329. try {
  330. // -> Get Page from cache
  331. const page = await WIKI.models.pages.getPage({
  332. path: pageArgs.path,
  333. locale: pageArgs.locale,
  334. userId: req.user.id,
  335. isPrivate: false
  336. })
  337. pageArgs.tags = _.get(page, 'tags', [])
  338. // -> Effective Permissions
  339. const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
  340. // -> Check User Access
  341. if (!effectivePermissions.pages.read) {
  342. if (req.user.id === 2) {
  343. res.cookie('loginRedirect', req.path, {
  344. maxAge: 15 * 60 * 1000
  345. })
  346. }
  347. if (pageArgs.path === 'home' && req.user.id === 2) {
  348. return res.redirect('/login')
  349. }
  350. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  351. return res.status(403).render('unauthorized', {
  352. action: 'view'
  353. })
  354. }
  355. _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
  356. _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
  357. if (page) {
  358. _.set(res.locals, 'pageMeta.title', page.title)
  359. _.set(res.locals, 'pageMeta.description', page.description)
  360. // -> Check Publishing State
  361. let pageIsPublished = page.isPublished
  362. if (pageIsPublished && !_.isEmpty(page.publishStartDate)) {
  363. pageIsPublished = moment(page.publishStartDate).isSameOrBefore()
  364. }
  365. if (pageIsPublished && !_.isEmpty(page.publishEndDate)) {
  366. pageIsPublished = moment(page.publishEndDate).isSameOrAfter()
  367. }
  368. if (!pageIsPublished && !effectivePermissions.pages.write) {
  369. _.set(res.locals, 'pageMeta.title', 'Unauthorized')
  370. return res.status(403).render('unauthorized', {
  371. action: 'view'
  372. })
  373. }
  374. // -> Build sidebar navigation
  375. let sdi = 1
  376. const sidebar = (await WIKI.models.navigation.getTree({ cache: true, locale: pageArgs.locale, groups: req.user.groups })).map(n => ({
  377. i: `sdi-${sdi++}`,
  378. k: n.kind,
  379. l: n.label,
  380. c: n.icon,
  381. y: n.targetType,
  382. t: n.target
  383. }))
  384. // -> Build theme code injection
  385. const injectCode = {
  386. css: WIKI.config.theming.injectCSS,
  387. head: WIKI.config.theming.injectHead,
  388. body: WIKI.config.theming.injectBody
  389. }
  390. if (!_.isEmpty(page.extra.css)) {
  391. injectCode.css = `${injectCode.css}\n${page.extra.css}`
  392. }
  393. if (!_.isEmpty(page.extra.js)) {
  394. injectCode.body = `${injectCode.body}\n${page.extra.js}`
  395. }
  396. if (req.query.legacy || req.get('user-agent').indexOf('Trident') >= 0) {
  397. // -> Convert page TOC
  398. if (_.isString(page.toc)) {
  399. page.toc = JSON.parse(page.toc)
  400. }
  401. // -> Render legacy view
  402. res.render('legacy/page', {
  403. page,
  404. sidebar,
  405. injectCode,
  406. isAuthenticated: req.user && req.user.id !== 2
  407. })
  408. } else {
  409. // -> Convert page TOC
  410. if (!_.isString(page.toc)) {
  411. page.toc = JSON.stringify(page.toc)
  412. }
  413. // -> Inject comments variables
  414. if (WIKI.config.features.featurePageComments && WIKI.data.commentProvider.codeTemplate) {
  415. [
  416. { key: 'pageUrl', value: `${WIKI.config.host}/i/${page.id}` },
  417. { key: 'pageId', value: page.id }
  418. ].forEach((cfg) => {
  419. WIKI.data.commentProvider.head = _.replace(WIKI.data.commentProvider.head, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  420. WIKI.data.commentProvider.body = _.replace(WIKI.data.commentProvider.body, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  421. WIKI.data.commentProvider.main = _.replace(WIKI.data.commentProvider.main, new RegExp(`{{${cfg.key}}}`, 'g'), cfg.value)
  422. })
  423. }
  424. // -> Render view
  425. res.render('page', {
  426. page,
  427. sidebar,
  428. injectCode,
  429. comments: WIKI.data.commentProvider,
  430. effectivePermissions
  431. })
  432. }
  433. } else if (pageArgs.path === 'home') {
  434. _.set(res.locals, 'pageMeta.title', 'Welcome')
  435. res.render('welcome', { locale: pageArgs.locale })
  436. } else {
  437. _.set(res.locals, 'pageMeta.title', 'Page Not Found')
  438. if (effectivePermissions.pages.write) {
  439. res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
  440. } else {
  441. res.status(404).render('notfound', { action: 'view' })
  442. }
  443. }
  444. } catch (err) {
  445. next(err)
  446. }
  447. } else {
  448. if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
  449. return res.sendStatus(403)
  450. }
  451. await WIKI.models.assets.getAsset(pageArgs.path, res)
  452. }
  453. })
  454. module.exports = router