web.mjs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import bodyParser from 'body-parser'
  2. import compression from 'compression'
  3. import cookieParser from 'cookie-parser'
  4. import cors from 'cors'
  5. import express from 'express'
  6. import session from 'express-session'
  7. import KnexSessionStore from 'connect-session-knex'
  8. import favicon from 'serve-favicon'
  9. import path from 'node:path'
  10. import { set } from 'lodash-es'
  11. import auth from './core/auth.mjs'
  12. import mail from './core/mail.mjs'
  13. import system from './core/system.mjs'
  14. import ctrlAuth from './controllers/auth.mjs'
  15. import ctrlCommon from './controllers/common.mjs'
  16. import ctrlSsl from './controllers/ssl.mjs'
  17. import ctrlWs from './controllers/ws.mjs'
  18. export async function init () {
  19. // ----------------------------------------
  20. // Load core modules
  21. // ----------------------------------------
  22. WIKI.auth = auth.init()
  23. WIKI.mail = mail.init()
  24. WIKI.system = system.init()
  25. // ----------------------------------------
  26. // Define Express App
  27. // ----------------------------------------
  28. const app = express()
  29. WIKI.app = app
  30. app.use(compression())
  31. // ----------------------------------------
  32. // Initialize HTTP/HTTPS Server
  33. // ----------------------------------------
  34. const useHTTPS = WIKI.config.ssl.enabled === true || WIKI.config.ssl.enabled === 'true' || WIKI.config.ssl.enabled === 1 || WIKI.config.ssl.enabled === '1'
  35. await WIKI.servers.initHTTP()
  36. if (useHTTPS) {
  37. await WIKI.servers.initHTTPS()
  38. }
  39. await WIKI.servers.initWebSocket()
  40. // ----------------------------------------
  41. // Attach WebSocket Server
  42. // ----------------------------------------
  43. ctrlWs()
  44. // ----------------------------------------
  45. // Security
  46. // ----------------------------------------
  47. app.use((req, res, next) => {
  48. // -> Disable X-Powered-By
  49. req.app.disable('x-powered-by')
  50. // -> Disable Frame Embedding
  51. if (WIKI.config.security.securityIframe) {
  52. res.set('X-Frame-Options', 'deny')
  53. }
  54. // -> Re-enable XSS Fitler if disabled
  55. res.set('X-XSS-Protection', '1; mode=block')
  56. // -> Disable MIME-sniffing
  57. res.set('X-Content-Type-Options', 'nosniff')
  58. // -> Disable IE Compatibility Mode
  59. res.set('X-UA-Compatible', 'IE=edge')
  60. // -> Disables referrer header when navigating to a different origin
  61. if (WIKI.config.security.securityReferrerPolicy) {
  62. res.set('Referrer-Policy', 'same-origin')
  63. }
  64. // -> Enforce HSTS
  65. if (WIKI.config.security.securityHSTS) {
  66. res.set('Strict-Transport-Security', `max-age=${WIKI.config.security.securityHSTSDuration}; includeSubDomains`)
  67. }
  68. // -> Prevent Open Redirect from user provided URL
  69. if (WIKI.config.security.securityOpenRedirect) {
  70. // Strips out all repeating / character in the provided URL
  71. req.url = req.url.replace(/(\/)(?=\/*\1)/g, '')
  72. }
  73. next()
  74. })
  75. app.use(cors({ origin: false }))
  76. app.options('*', cors({ origin: false }))
  77. if (WIKI.config.security.securityTrustProxy) {
  78. app.enable('trust proxy')
  79. }
  80. // ----------------------------------------
  81. // Public Assets
  82. // ----------------------------------------
  83. app.use(favicon(path.join(WIKI.ROOTPATH, 'assets', 'favicon.ico')))
  84. app.use('/_assets', express.static(path.join(WIKI.ROOTPATH, 'assets/_assets'), {
  85. index: false,
  86. maxAge: '7d'
  87. }))
  88. app.use('/_assets/svg/twemoji', async (req, res, next) => {
  89. try {
  90. WIKI.asar.serve('twemoji', req, res, next)
  91. } catch (err) {
  92. res.sendStatus(404)
  93. }
  94. })
  95. // ----------------------------------------
  96. // SSL Handlers
  97. // ----------------------------------------
  98. app.use('/', ctrlSsl())
  99. // ----------------------------------------
  100. // Passport Authentication
  101. // ----------------------------------------
  102. app.use(cookieParser())
  103. app.use(session({
  104. secret: WIKI.config.auth.secret,
  105. resave: false,
  106. saveUninitialized: false,
  107. store: new KnexSessionStore(session)({
  108. knex: WIKI.db.knex
  109. })
  110. }))
  111. app.use(WIKI.auth.passport.initialize())
  112. app.use(WIKI.auth.authenticate)
  113. // ----------------------------------------
  114. // GraphQL Server
  115. // ----------------------------------------
  116. app.use(bodyParser.json({ limit: WIKI.config.bodyParserLimit || '1mb' }))
  117. await WIKI.servers.startGraphQL()
  118. // ----------------------------------------
  119. // SEO
  120. // ----------------------------------------
  121. app.use((req, res, next) => {
  122. if (req.path.length > 1 && req.path.endsWith('/')) {
  123. let query = req.url.slice(req.path.length) || ''
  124. res.redirect(301, req.path.slice(0, -1) + query)
  125. } else {
  126. set(res.locals, 'pageMeta.url', `${WIKI.config.host}${req.path}`)
  127. next()
  128. }
  129. })
  130. // ----------------------------------------
  131. // View Engine Setup
  132. // ----------------------------------------
  133. app.set('views', path.join(WIKI.SERVERPATH, 'views'))
  134. app.set('view engine', 'pug')
  135. app.use(bodyParser.urlencoded({ extended: false, limit: '1mb' }))
  136. // ----------------------------------------
  137. // View accessible data
  138. // ----------------------------------------
  139. app.locals.analyticsCode = {}
  140. app.locals.basedir = WIKI.ROOTPATH
  141. app.locals.config = WIKI.config
  142. app.locals.pageMeta = {
  143. title: '',
  144. description: WIKI.config.description,
  145. image: '',
  146. url: '/'
  147. }
  148. app.locals.devMode = WIKI.devMode
  149. // ----------------------------------------
  150. // HMR (Dev Mode Only)
  151. // ----------------------------------------
  152. if (global.DEV) {
  153. app.use(global.WP_DEV.devMiddleware)
  154. app.use(global.WP_DEV.hotMiddleware)
  155. }
  156. // ----------------------------------------
  157. // Routing
  158. // ----------------------------------------
  159. app.use(async (req, res, next) => {
  160. const currentSite = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
  161. if (!currentSite) {
  162. return res.status(404).send('Site Not Found')
  163. }
  164. res.locals.siteConfig = {
  165. id: currentSite.id,
  166. title: currentSite.config.title,
  167. darkMode: currentSite.config.theme.dark,
  168. lang: currentSite.config.locale,
  169. rtl: false, // TODO: handle RTL
  170. company: currentSite.config.company,
  171. contentLicense: currentSite.config.contentLicense
  172. }
  173. res.locals.theming = {
  174. }
  175. res.locals.langs = await WIKI.db.locales.getNavLocales({ cache: true })
  176. res.locals.analyticsCode = await WIKI.db.analytics.getCode({ cache: true })
  177. next()
  178. })
  179. app.use('/', ctrlAuth())
  180. app.use('/', ctrlCommon())
  181. // ----------------------------------------
  182. // Error handling
  183. // ----------------------------------------
  184. app.use((req, res, next) => {
  185. const err = new Error('Not Found')
  186. err.status = 404
  187. next(err)
  188. })
  189. app.use((err, req, res, next) => {
  190. if (req.path === '/_graphql') {
  191. res.status(err.status || 500).json({
  192. data: {},
  193. errors: [{
  194. message: err.message,
  195. path: []
  196. }]
  197. })
  198. } else {
  199. res.status(err.status || 500)
  200. set(res.locals, 'pageMeta.title', 'Error')
  201. res.render('error', {
  202. message: err.message,
  203. error: WIKI.IS_DEBUG ? err : {}
  204. })
  205. }
  206. })
  207. // ----------------------------------------
  208. // Start HTTP Server(s)
  209. // ----------------------------------------
  210. await WIKI.servers.startHTTP()
  211. if (useHTTPS) {
  212. await WIKI.servers.startHTTPS()
  213. }
  214. return true
  215. }