mirror of https://github.com/requarks/wiki
parent
fc820eb1b6
commit
e9e93eff42
@ -1,6 +1,4 @@
|
|||||||
'use strict'
|
export default {
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
arabic: '\u0600-\u06ff\u0750-\u077f\ufb50-\ufc3f\ufe70-\ufefc',
|
arabic: '\u0600-\u06ff\u0750-\u077f\ufb50-\ufc3f\ufe70-\ufefc',
|
||||||
cjk: '\u4E00-\u9FBF\u3040-\u309F\u30A0-\u30FFㄱ-ㅎ가-힣ㅏ-ㅣ',
|
cjk: '\u4E00-\u9FBF\u3040-\u309F\u30A0-\u30FFㄱ-ㅎ가-힣ㅏ-ㅣ',
|
||||||
youtube: /(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/,
|
youtube: /(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|&v(?:i)?=))([^#&?]*).*/,
|
@ -1,158 +0,0 @@
|
|||||||
/* global WIKI */
|
|
||||||
|
|
||||||
const express = require('express')
|
|
||||||
const ExpressBrute = require('express-brute')
|
|
||||||
const BruteKnex = require('../helpers/brute-knex')
|
|
||||||
const router = express.Router()
|
|
||||||
const moment = require('moment')
|
|
||||||
const _ = require('lodash')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const bruteforce = new ExpressBrute(new BruteKnex({
|
|
||||||
createTable: true,
|
|
||||||
knex: WIKI.db.knex
|
|
||||||
}), {
|
|
||||||
freeRetries: 5,
|
|
||||||
minWait: 5 * 60 * 1000, // 5 minutes
|
|
||||||
maxWait: 60 * 60 * 1000, // 1 hour
|
|
||||||
failCallback: (req, res, next) => {
|
|
||||||
res.status(401).send('Too many failed attempts. Try again later.')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Login form
|
|
||||||
*/
|
|
||||||
router.get('/login', async (req, res, next) => {
|
|
||||||
// -> Bypass Login
|
|
||||||
if (WIKI.config.auth.autoLogin && !req.query.all) {
|
|
||||||
const stg = await WIKI.db.authentication.query().orderBy('order').first()
|
|
||||||
const stgInfo = _.find(WIKI.data.authentication, ['key', stg.strategyKey])
|
|
||||||
if (!stgInfo.useForm) {
|
|
||||||
return res.redirect(`/login/${stg.key}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// -> Show Login
|
|
||||||
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Social Strategies Login
|
|
||||||
*/
|
|
||||||
router.get('/login/:strategy', async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
await WIKI.db.users.login({
|
|
||||||
strategy: req.params.strategy
|
|
||||||
}, { req, res })
|
|
||||||
} catch (err) {
|
|
||||||
next(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Social Strategies Callback
|
|
||||||
*/
|
|
||||||
router.all('/login/:strategy/callback', async (req, res, next) => {
|
|
||||||
if (req.method !== 'GET' && req.method !== 'POST') { return next() }
|
|
||||||
|
|
||||||
try {
|
|
||||||
const authResult = await WIKI.db.users.login({
|
|
||||||
strategy: req.params.strategy
|
|
||||||
}, { req, res })
|
|
||||||
res.cookie('jwt', authResult.jwt, { expires: moment().add(1, 'y').toDate() })
|
|
||||||
|
|
||||||
const loginRedirect = req.cookies['loginRedirect']
|
|
||||||
if (loginRedirect === '/' && authResult.redirect) {
|
|
||||||
res.clearCookie('loginRedirect')
|
|
||||||
res.redirect(authResult.redirect)
|
|
||||||
} else if (loginRedirect) {
|
|
||||||
res.clearCookie('loginRedirect')
|
|
||||||
res.redirect(loginRedirect)
|
|
||||||
} else if (authResult.redirect) {
|
|
||||||
res.redirect(authResult.redirect)
|
|
||||||
} else {
|
|
||||||
res.redirect('/')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
next(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logout
|
|
||||||
*/
|
|
||||||
router.get('/logout', async (req, res, next) => {
|
|
||||||
const redirURL = await WIKI.db.users.logout({ req, res })
|
|
||||||
req.logout((err) => {
|
|
||||||
if (err) { return next(err) }
|
|
||||||
res.clearCookie('jwt')
|
|
||||||
res.redirect(redirURL)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register form
|
|
||||||
*/
|
|
||||||
router.get('/register', async (req, res, next) => {
|
|
||||||
_.set(res.locals, 'pageMeta.title', 'Register')
|
|
||||||
const localStrg = await WIKI.db.authentication.getStrategy('local')
|
|
||||||
if (localStrg.selfRegistration) {
|
|
||||||
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
|
||||||
} else {
|
|
||||||
next(new WIKI.Error.AuthRegistrationDisabled())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verify
|
|
||||||
*/
|
|
||||||
router.get('/verify/:token', bruteforce.prevent, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const usr = await WIKI.db.userKeys.validateToken({ kind: 'verify', token: req.params.token })
|
|
||||||
await WIKI.db.users.query().patch({ isVerified: true }).where('id', usr.id)
|
|
||||||
req.brute.reset()
|
|
||||||
if (WIKI.config.auth.enforce2FA) {
|
|
||||||
res.redirect('/login')
|
|
||||||
} else {
|
|
||||||
const result = await WIKI.db.users.refreshToken(usr)
|
|
||||||
res.cookie('jwt', result.token, { expires: moment().add(1, 'years').toDate() })
|
|
||||||
res.redirect('/')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
next(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset Password
|
|
||||||
*/
|
|
||||||
router.get('/login-reset/:token', bruteforce.prevent, async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const usr = await WIKI.db.userKeys.validateToken({ kind: 'resetPwd', token: req.params.token })
|
|
||||||
if (!usr) {
|
|
||||||
throw new Error('Invalid Token')
|
|
||||||
}
|
|
||||||
req.brute.reset()
|
|
||||||
|
|
||||||
const changePwdContinuationToken = await WIKI.db.userKeys.generateToken({
|
|
||||||
userId: usr.id,
|
|
||||||
kind: 'changePwd'
|
|
||||||
})
|
|
||||||
const bgUrl = !_.isEmpty(WIKI.config.auth.loginBgUrl) ? WIKI.config.auth.loginBgUrl : '/_assets/img/splash/1.jpg'
|
|
||||||
res.render('login', { bgUrl, hideLocal: WIKI.config.auth.hideLocal, changePwdContinuationToken })
|
|
||||||
} catch (err) {
|
|
||||||
next(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* JWT Public Endpoints
|
|
||||||
*/
|
|
||||||
router.get('/.well-known/jwk.json', function (req, res, next) {
|
|
||||||
res.json(WIKI.config.certs.jwk)
|
|
||||||
})
|
|
||||||
router.get('/.well-known/jwk.pem', function (req, res, next) {
|
|
||||||
res.send(WIKI.config.certs.public)
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
|
@ -0,0 +1,161 @@
|
|||||||
|
/* global WIKI */
|
||||||
|
|
||||||
|
import express from 'express'
|
||||||
|
import ExpressBrute from 'express-brute'
|
||||||
|
import BruteKnex from '../helpers/brute-knex.mjs'
|
||||||
|
import { find, isEmpty, set } from 'lodash-es'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { DateTime } from 'luxon'
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
const bruteforce = new ExpressBrute(new BruteKnex({
|
||||||
|
createTable: true,
|
||||||
|
knex: WIKI.db.knex
|
||||||
|
}), {
|
||||||
|
freeRetries: 5,
|
||||||
|
minWait: 5 * 60 * 1000, // 5 minutes
|
||||||
|
maxWait: 60 * 60 * 1000, // 1 hour
|
||||||
|
failCallback: (req, res, next) => {
|
||||||
|
res.status(401).send('Too many failed attempts. Try again later.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login form
|
||||||
|
*/
|
||||||
|
router.get('/login', async (req, res, next) => {
|
||||||
|
// -> Bypass Login
|
||||||
|
if (WIKI.config.auth.autoLogin && !req.query.all) {
|
||||||
|
const stg = await WIKI.db.authentication.query().orderBy('order').first()
|
||||||
|
const stgInfo = find(WIKI.data.authentication, ['key', stg.strategyKey])
|
||||||
|
if (!stgInfo.useForm) {
|
||||||
|
return res.redirect(`/login/${stg.key}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -> Show Login
|
||||||
|
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Social Strategies Login
|
||||||
|
*/
|
||||||
|
router.get('/login/:strategy', async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
await WIKI.db.users.login({
|
||||||
|
strategy: req.params.strategy
|
||||||
|
}, { req, res })
|
||||||
|
} catch (err) {
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Social Strategies Callback
|
||||||
|
*/
|
||||||
|
router.all('/login/:strategy/callback', async (req, res, next) => {
|
||||||
|
if (req.method !== 'GET' && req.method !== 'POST') { return next() }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const authResult = await WIKI.db.users.login({
|
||||||
|
strategy: req.params.strategy
|
||||||
|
}, { req, res })
|
||||||
|
res.cookie('jwt', authResult.jwt, { expires: DateTime.now().plus({ years: 1 }).toJSDate() })
|
||||||
|
|
||||||
|
const loginRedirect = req.cookies['loginRedirect']
|
||||||
|
if (loginRedirect === '/' && authResult.redirect) {
|
||||||
|
res.clearCookie('loginRedirect')
|
||||||
|
res.redirect(authResult.redirect)
|
||||||
|
} else if (loginRedirect) {
|
||||||
|
res.clearCookie('loginRedirect')
|
||||||
|
res.redirect(loginRedirect)
|
||||||
|
} else if (authResult.redirect) {
|
||||||
|
res.redirect(authResult.redirect)
|
||||||
|
} else {
|
||||||
|
res.redirect('/')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout
|
||||||
|
*/
|
||||||
|
router.get('/logout', async (req, res, next) => {
|
||||||
|
const redirURL = await WIKI.db.users.logout({ req, res })
|
||||||
|
req.logout((err) => {
|
||||||
|
if (err) { return next(err) }
|
||||||
|
res.clearCookie('jwt')
|
||||||
|
res.redirect(redirURL)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register form
|
||||||
|
*/
|
||||||
|
router.get('/register', async (req, res, next) => {
|
||||||
|
set(res.locals, 'pageMeta.title', 'Register')
|
||||||
|
const localStrg = await WIKI.db.authentication.getStrategy('local')
|
||||||
|
if (localStrg.selfRegistration) {
|
||||||
|
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
||||||
|
} else {
|
||||||
|
next(new WIKI.Error.AuthRegistrationDisabled())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify
|
||||||
|
*/
|
||||||
|
router.get('/verify/:token', bruteforce.prevent, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const usr = await WIKI.db.userKeys.validateToken({ kind: 'verify', token: req.params.token })
|
||||||
|
await WIKI.db.users.query().patch({ isVerified: true }).where('id', usr.id)
|
||||||
|
req.brute.reset()
|
||||||
|
if (WIKI.config.auth.enforce2FA) {
|
||||||
|
res.redirect('/login')
|
||||||
|
} else {
|
||||||
|
const result = await WIKI.db.users.refreshToken(usr)
|
||||||
|
res.cookie('jwt', result.token, { expires: DateTime.now().plus({ years: 1 }).toJSDate() })
|
||||||
|
res.redirect('/')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset Password
|
||||||
|
*/
|
||||||
|
router.get('/login-reset/:token', bruteforce.prevent, async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const usr = await WIKI.db.userKeys.validateToken({ kind: 'resetPwd', token: req.params.token })
|
||||||
|
if (!usr) {
|
||||||
|
throw new Error('Invalid Token')
|
||||||
|
}
|
||||||
|
req.brute.reset()
|
||||||
|
|
||||||
|
const changePwdContinuationToken = await WIKI.db.userKeys.generateToken({
|
||||||
|
userId: usr.id,
|
||||||
|
kind: 'changePwd'
|
||||||
|
})
|
||||||
|
const bgUrl = !isEmpty(WIKI.config.auth.loginBgUrl) ? WIKI.config.auth.loginBgUrl : '/_assets/img/splash/1.jpg'
|
||||||
|
res.render('login', { bgUrl, hideLocal: WIKI.config.auth.hideLocal, changePwdContinuationToken })
|
||||||
|
} catch (err) {
|
||||||
|
next(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT Public Endpoints
|
||||||
|
*/
|
||||||
|
router.get('/.well-known/jwk.json', function (req, res, next) {
|
||||||
|
res.json(WIKI.config.certs.jwk)
|
||||||
|
})
|
||||||
|
router.get('/.well-known/jwk.pem', function (req, res, next) {
|
||||||
|
res.send(WIKI.config.certs.public)
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
@ -1,533 +0,0 @@
|
|||||||
const express = require('express')
|
|
||||||
const router = express.Router()
|
|
||||||
const pageHelper = require('../helpers/page')
|
|
||||||
const _ = require('lodash')
|
|
||||||
const CleanCSS = require('clean-css')
|
|
||||||
const moment = require('moment')
|
|
||||||
const path = require('path')
|
|
||||||
const siteAssetsPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Robots.txt
|
|
||||||
*/
|
|
||||||
router.get('/robots.txt', (req, res, next) => {
|
|
||||||
res.type('text/plain')
|
|
||||||
if (_.includes(WIKI.config.seo.robots, 'noindex')) {
|
|
||||||
res.send('User-agent: *\nDisallow: /')
|
|
||||||
} else {
|
|
||||||
res.status(200).end()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Health Endpoint
|
|
||||||
*/
|
|
||||||
router.get('/healthz', (req, res, next) => {
|
|
||||||
if (WIKI.db.knex.client.pool.numFree() < 1 && WIKI.db.knex.client.pool.numUsed() < 1) {
|
|
||||||
res.status(503).json({ ok: false }).end()
|
|
||||||
} else {
|
|
||||||
res.status(200).json({ ok: true }).end()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Site Asset
|
|
||||||
*/
|
|
||||||
router.get('/_site/:siteId?/:resource', async (req, res, next) => {
|
|
||||||
const site = req.params.siteId ? WIKI.sites[req.params.siteId] : await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
|
|
||||||
if (!site) {
|
|
||||||
return res.status(404).send('Site Not Found')
|
|
||||||
}
|
|
||||||
switch (req.params.resource) {
|
|
||||||
case 'logo': {
|
|
||||||
if (site.config.assets.logo) {
|
|
||||||
// TODO: Fetch from db if not in disk cache
|
|
||||||
res.sendFile(path.join(siteAssetsPath, `logo-${site.id}.${site.config.assets.logoExt}`))
|
|
||||||
} else {
|
|
||||||
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'favicon': {
|
|
||||||
if (site.config.assets.favicon) {
|
|
||||||
// TODO: Fetch from db if not in disk cache
|
|
||||||
res.sendFile(path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`))
|
|
||||||
} else {
|
|
||||||
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'loginbg': {
|
|
||||||
if (site.config.assets.loginBg) {
|
|
||||||
// TODO: Fetch from db if not in disk cache
|
|
||||||
res.sendFile(path.join(siteAssetsPath, `loginbg-${site.id}.jpg`))
|
|
||||||
} else {
|
|
||||||
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/bg/login.jpg'))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return res.status(404).send('Invalid Site Resource')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asset Thumbnails / Download
|
|
||||||
*/
|
|
||||||
router.get('/_thumb/:id.webp', async (req, res, next) => {
|
|
||||||
const thumb = await WIKI.db.assets.getThumbnail({
|
|
||||||
id: req.params.id
|
|
||||||
})
|
|
||||||
|
|
||||||
if (thumb) {
|
|
||||||
// TODO: Check permissions
|
|
||||||
|
|
||||||
switch (thumb.previewState) {
|
|
||||||
case 'pending': {
|
|
||||||
res.redirect('/_assets/illustrations/fileman-pending.svg')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'ready': {
|
|
||||||
res.set('Content-Type', 'image/webp')
|
|
||||||
res.send(thumb.preview)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'failed': {
|
|
||||||
res.redirect('/_assets/illustrations/fileman-failed.svg')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return res.status(500).send('Invalid Thumbnail Preview State')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return res.sendStatus(404)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// router.get(['/_admin', '/_admin/*'], (req, res, next) => {
|
|
||||||
// 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')
|
|
||||||
// return res.status(403).render('unauthorized', { action: 'view' })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Admin')
|
|
||||||
// res.render('admin')
|
|
||||||
|
|
||||||
// })
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Download Page / Version
|
|
||||||
// */
|
|
||||||
// router.get(['/d', '/d/*'], async (req, res, next) => {
|
|
||||||
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
|
||||||
|
|
||||||
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
|
|
||||||
|
|
||||||
// const page = await WIKI.db.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.db.pageHistory.getVersion({ pageId: page.id, versionId })
|
|
||||||
// res.send(pageHelper.injectPageMetadata(pageVersion))
|
|
||||||
// } else {
|
|
||||||
// res.send(pageHelper.injectPageMetadata(page))
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// res.status(404).end()
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Create/Edit document
|
|
||||||
// */
|
|
||||||
// router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
|
|
||||||
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
|
||||||
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
|
|
||||||
|
|
||||||
// if (!site) {
|
|
||||||
// throw new Error('INVALID_SITE')
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (pageArgs.path === '') {
|
|
||||||
// return res.redirect(`/_edit/home`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
|
||||||
// // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // req.i18n.changeLanguage(pageArgs.locale)
|
|
||||||
|
|
||||||
// // -> Set Editor Lang
|
|
||||||
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
|
||||||
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
|
||||||
|
|
||||||
// // -> Check for reserved path
|
|
||||||
// if (pageHelper.isReservedPath(pageArgs.path)) {
|
|
||||||
// return next(new Error('Cannot create this page because it starts with a system reserved path.'))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // -> Get page data from DB
|
|
||||||
// let page = await WIKI.db.pages.getPageFromDb({
|
|
||||||
// siteId: site.id,
|
|
||||||
// path: pageArgs.path,
|
|
||||||
// locale: pageArgs.locale,
|
|
||||||
// userId: req.user.id
|
|
||||||
// })
|
|
||||||
|
|
||||||
// pageArgs.tags = _.get(page, 'tags', [])
|
|
||||||
|
|
||||||
// // -> Effective Permissions
|
|
||||||
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
|
||||||
|
|
||||||
// const injectCode = {
|
|
||||||
// css: '', // WIKI.config.theming.injectCSS,
|
|
||||||
// head: '', // WIKI.config.theming.injectHead,
|
|
||||||
// body: '' // WIKI.config.theming.injectBody
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (page) {
|
|
||||||
// // -> EDIT MODE
|
|
||||||
// if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
|
||||||
// return res.render('unauthorized', { action: 'edit' })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // -> Get page tags
|
|
||||||
// await page.$relatedQuery('tags')
|
|
||||||
// page.tags = _.map(page.tags, 'tag')
|
|
||||||
|
|
||||||
// // Handle missing extra field
|
|
||||||
// page.extra = page.extra || { css: '', js: '' }
|
|
||||||
|
|
||||||
// // -> Beautify Script CSS
|
|
||||||
// if (!_.isEmpty(page.extra.css)) {
|
|
||||||
// page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
|
|
||||||
// _.set(res.locals, 'pageMeta.description', page.description)
|
|
||||||
// page.mode = 'update'
|
|
||||||
// page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
|
|
||||||
// page.content = Buffer.from(page.content).toString('base64')
|
|
||||||
// } else {
|
|
||||||
// // -> CREATE MODE
|
|
||||||
// if (!effectivePermissions.pages.write) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
|
||||||
// return res.render('unauthorized', { action: 'create' })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _.set(res.locals, 'pageMeta.title', `New Page`)
|
|
||||||
// page = {
|
|
||||||
// path: pageArgs.path,
|
|
||||||
// localeCode: pageArgs.locale,
|
|
||||||
// editorKey: null,
|
|
||||||
// mode: 'create',
|
|
||||||
// content: null,
|
|
||||||
// title: null,
|
|
||||||
// description: null,
|
|
||||||
// updatedAt: new Date().toISOString(),
|
|
||||||
// extra: {
|
|
||||||
// css: '',
|
|
||||||
// js: ''
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// res.render('editor', { page, injectCode, effectivePermissions })
|
|
||||||
// })
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * History
|
|
||||||
// */
|
|
||||||
// router.get(['/h', '/h/*'], async (req, res, next) => {
|
|
||||||
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
|
||||||
|
|
||||||
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
|
||||||
// return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// req.i18n.changeLanguage(pageArgs.locale)
|
|
||||||
|
|
||||||
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
|
||||||
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
|
||||||
|
|
||||||
// const page = await WIKI.db.pages.getPageFromDb({
|
|
||||||
// path: pageArgs.path,
|
|
||||||
// locale: pageArgs.locale,
|
|
||||||
// userId: req.user.id,
|
|
||||||
// isPrivate: false
|
|
||||||
// })
|
|
||||||
|
|
||||||
// if (!page) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
|
|
||||||
// return res.status(404).render('notfound', { action: 'history' })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pageArgs.tags = _.get(page, 'tags', [])
|
|
||||||
|
|
||||||
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
|
||||||
|
|
||||||
// if (!effectivePermissions.history.read) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
|
||||||
// return res.render('unauthorized', { action: 'history' })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (page) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', page.title)
|
|
||||||
// _.set(res.locals, 'pageMeta.description', page.description)
|
|
||||||
|
|
||||||
// res.render('history', { page, effectivePermissions })
|
|
||||||
// } else {
|
|
||||||
// res.redirect(`/${pageArgs.path}`)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * 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.db.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,
|
|
||||||
// explicitLocale: false,
|
|
||||||
// tags: page.tags
|
|
||||||
// })) {
|
|
||||||
// _.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}`)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Source
|
|
||||||
// */
|
|
||||||
// router.get(['/s', '/s/*'], async (req, res, next) => {
|
|
||||||
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
|
||||||
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
|
|
||||||
|
|
||||||
// const page = await WIKI.db.pages.getPageFromDb({
|
|
||||||
// path: pageArgs.path,
|
|
||||||
// locale: pageArgs.locale,
|
|
||||||
// userId: req.user.id,
|
|
||||||
// isPrivate: false
|
|
||||||
// })
|
|
||||||
|
|
||||||
// pageArgs.tags = _.get(page, 'tags', [])
|
|
||||||
|
|
||||||
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
|
||||||
// return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // -> Effective Permissions
|
|
||||||
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
|
||||||
|
|
||||||
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
|
||||||
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
|
||||||
|
|
||||||
// if (versionId > 0) {
|
|
||||||
// if (!effectivePermissions.history.read) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
|
||||||
// return res.render('unauthorized', { action: 'sourceVersion' })
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// if (!effectivePermissions.source.read) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
|
||||||
// return res.render('unauthorized', { action: 'source' })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (page) {
|
|
||||||
// if (versionId > 0) {
|
|
||||||
// const pageVersion = await WIKI.db.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
|
|
||||||
// },
|
|
||||||
// effectivePermissions
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', page.title)
|
|
||||||
// _.set(res.locals, 'pageMeta.description', page.description)
|
|
||||||
|
|
||||||
// res.render('source', { page, effectivePermissions })
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// res.redirect(`/${pageArgs.path}`)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * Tags
|
|
||||||
// */
|
|
||||||
// router.get(['/t', '/t/*'], (req, res, next) => {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Tags')
|
|
||||||
// res.render('tags')
|
|
||||||
// })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User Avatar
|
|
||||||
*/
|
|
||||||
router.get('/_user/:uid/avatar', async (req, res, next) => {
|
|
||||||
if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
|
|
||||||
return res.sendStatus(403)
|
|
||||||
}
|
|
||||||
const av = await WIKI.db.users.getUserAvatarData(req.params.uid)
|
|
||||||
if (av) {
|
|
||||||
res.set('Content-Type', 'image/jpeg')
|
|
||||||
return res.send(av)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.sendStatus(404)
|
|
||||||
})
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * View document / asset
|
|
||||||
// */
|
|
||||||
// router.get('/*', async (req, res, next) => {
|
|
||||||
// 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)
|
|
||||||
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
|
|
||||||
|
|
||||||
// if (!site) {
|
|
||||||
// throw new Error('INVALID_SITE')
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (isPage) {
|
|
||||||
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
|
||||||
// // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // req.i18n.changeLanguage(pageArgs.locale)
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// // -> Get Page from cache
|
|
||||||
// const page = await WIKI.db.pages.getPage({
|
|
||||||
// siteId: site.id,
|
|
||||||
// path: pageArgs.path,
|
|
||||||
// locale: pageArgs.locale,
|
|
||||||
// userId: req.user.id
|
|
||||||
// })
|
|
||||||
// pageArgs.tags = _.get(page, 'tags', [])
|
|
||||||
|
|
||||||
// // -> Effective Permissions
|
|
||||||
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
|
||||||
|
|
||||||
// // -> Check User Access
|
|
||||||
// if (!effectivePermissions.pages.read) {
|
|
||||||
// if (req.user.id === WIKI.auth.guest.id) {
|
|
||||||
// res.cookie('loginRedirect', req.path, {
|
|
||||||
// maxAge: 15 * 60 * 1000
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) {
|
|
||||||
// return res.redirect('/login')
|
|
||||||
// }
|
|
||||||
// return res.redirect(`/_error/unauthorized?from=${req.path}`)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
|
||||||
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
|
||||||
|
|
||||||
// if (page) {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', page.title)
|
|
||||||
// _.set(res.locals, 'pageMeta.description', page.description)
|
|
||||||
|
|
||||||
// // -> 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'
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // -> Render view
|
|
||||||
// res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
|
||||||
// } else if (pageArgs.path === 'home') {
|
|
||||||
// res.redirect('/_welcome')
|
|
||||||
// } else {
|
|
||||||
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
|
|
||||||
// if (effectivePermissions.pages.write) {
|
|
||||||
// res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
|
|
||||||
// } else {
|
|
||||||
// res.status(404).render('notfound', { action: 'view' })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (err) {
|
|
||||||
// next(err)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
|
|
||||||
// return res.sendStatus(403)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// await WIKI.db.assets.getAsset(pageArgs.path, res)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
router.get('/*', (req, res, next) => {
|
|
||||||
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
|
@ -0,0 +1,534 @@
|
|||||||
|
import express from 'express'
|
||||||
|
// import pageHelper from '../helpers/page.mjs'
|
||||||
|
// import CleanCSS from 'clean-css'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const router = express.Router()
|
||||||
|
const siteAssetsPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Robots.txt
|
||||||
|
*/
|
||||||
|
router.get('/robots.txt', (req, res, next) => {
|
||||||
|
res.type('text/plain')
|
||||||
|
if (WIKI.config.seo.robots.includes('noindex')) {
|
||||||
|
res.send('User-agent: *\nDisallow: /')
|
||||||
|
} else {
|
||||||
|
res.status(200).end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Health Endpoint
|
||||||
|
*/
|
||||||
|
router.get('/healthz', (req, res, next) => {
|
||||||
|
if (WIKI.db.knex.client.pool.numFree() < 1 && WIKI.db.knex.client.pool.numUsed() < 1) {
|
||||||
|
res.status(503).json({ ok: false }).end()
|
||||||
|
} else {
|
||||||
|
res.status(200).json({ ok: true }).end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Site Asset
|
||||||
|
*/
|
||||||
|
router.get('/_site/:siteId?/:resource', async (req, res, next) => {
|
||||||
|
const site = req.params.siteId ? WIKI.sites[req.params.siteId] : await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
|
||||||
|
if (!site) {
|
||||||
|
return res.status(404).send('Site Not Found')
|
||||||
|
}
|
||||||
|
switch (req.params.resource) {
|
||||||
|
case 'logo': {
|
||||||
|
if (site.config.assets.logo) {
|
||||||
|
// TODO: Fetch from db if not in disk cache
|
||||||
|
res.sendFile(path.join(siteAssetsPath, `logo-${site.id}.${site.config.assets.logoExt}`))
|
||||||
|
} else {
|
||||||
|
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'favicon': {
|
||||||
|
if (site.config.assets.favicon) {
|
||||||
|
// TODO: Fetch from db if not in disk cache
|
||||||
|
res.sendFile(path.join(siteAssetsPath, `favicon-${site.id}.${site.config.assets.faviconExt}`))
|
||||||
|
} else {
|
||||||
|
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/logo-wikijs.svg'))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'loginbg': {
|
||||||
|
if (site.config.assets.loginBg) {
|
||||||
|
// TODO: Fetch from db if not in disk cache
|
||||||
|
res.sendFile(path.join(siteAssetsPath, `loginbg-${site.id}.jpg`))
|
||||||
|
} else {
|
||||||
|
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/_assets/bg/login.jpg'))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return res.status(404).send('Invalid Site Resource')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asset Thumbnails / Download
|
||||||
|
*/
|
||||||
|
router.get('/_thumb/:id.webp', async (req, res, next) => {
|
||||||
|
const thumb = await WIKI.db.assets.getThumbnail({
|
||||||
|
id: req.params.id
|
||||||
|
})
|
||||||
|
|
||||||
|
if (thumb) {
|
||||||
|
// TODO: Check permissions
|
||||||
|
|
||||||
|
switch (thumb.previewState) {
|
||||||
|
case 'pending': {
|
||||||
|
res.redirect('/_assets/illustrations/fileman-pending.svg')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'ready': {
|
||||||
|
res.set('Content-Type', 'image/webp')
|
||||||
|
res.send(thumb.preview)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'failed': {
|
||||||
|
res.redirect('/_assets/illustrations/fileman-failed.svg')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return res.status(500).send('Invalid Thumbnail Preview State')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// router.get(['/_admin', '/_admin/*'], (req, res, next) => {
|
||||||
|
// 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')
|
||||||
|
// return res.status(403).render('unauthorized', { action: 'view' })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Admin')
|
||||||
|
// res.render('admin')
|
||||||
|
|
||||||
|
// })
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Download Page / Version
|
||||||
|
// */
|
||||||
|
// router.get(['/d', '/d/*'], async (req, res, next) => {
|
||||||
|
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
||||||
|
|
||||||
|
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
|
||||||
|
|
||||||
|
// const page = await WIKI.db.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.db.pageHistory.getVersion({ pageId: page.id, versionId })
|
||||||
|
// res.send(pageHelper.injectPageMetadata(pageVersion))
|
||||||
|
// } else {
|
||||||
|
// res.send(pageHelper.injectPageMetadata(page))
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// res.status(404).end()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Create/Edit document
|
||||||
|
// */
|
||||||
|
// router.get(['/_edit', '/_edit/*'], async (req, res, next) => {
|
||||||
|
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
||||||
|
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
|
||||||
|
|
||||||
|
// if (!site) {
|
||||||
|
// throw new Error('INVALID_SITE')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (pageArgs.path === '') {
|
||||||
|
// return res.redirect(`/_edit/home`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
||||||
|
// // return res.redirect(`/_edit/${pageArgs.locale}/${pageArgs.path}`)
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // req.i18n.changeLanguage(pageArgs.locale)
|
||||||
|
|
||||||
|
// // -> Set Editor Lang
|
||||||
|
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
||||||
|
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
||||||
|
|
||||||
|
// // -> Check for reserved path
|
||||||
|
// if (pageHelper.isReservedPath(pageArgs.path)) {
|
||||||
|
// return next(new Error('Cannot create this page because it starts with a system reserved path.'))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // -> Get page data from DB
|
||||||
|
// let page = await WIKI.db.pages.getPageFromDb({
|
||||||
|
// siteId: site.id,
|
||||||
|
// path: pageArgs.path,
|
||||||
|
// locale: pageArgs.locale,
|
||||||
|
// userId: req.user.id
|
||||||
|
// })
|
||||||
|
|
||||||
|
// pageArgs.tags = _.get(page, 'tags', [])
|
||||||
|
|
||||||
|
// // -> Effective Permissions
|
||||||
|
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
||||||
|
|
||||||
|
// const injectCode = {
|
||||||
|
// css: '', // WIKI.config.theming.injectCSS,
|
||||||
|
// head: '', // WIKI.config.theming.injectHead,
|
||||||
|
// body: '' // WIKI.config.theming.injectBody
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (page) {
|
||||||
|
// // -> EDIT MODE
|
||||||
|
// if (!(effectivePermissions.pages.write || effectivePermissions.pages.manage)) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
||||||
|
// return res.render('unauthorized', { action: 'edit' })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // -> Get page tags
|
||||||
|
// await page.$relatedQuery('tags')
|
||||||
|
// page.tags = _.map(page.tags, 'tag')
|
||||||
|
|
||||||
|
// // Handle missing extra field
|
||||||
|
// page.extra = page.extra || { css: '', js: '' }
|
||||||
|
|
||||||
|
// // -> Beautify Script CSS
|
||||||
|
// if (!_.isEmpty(page.extra.css)) {
|
||||||
|
// page.extra.css = new CleanCSS({ format: 'beautify' }).minify(page.extra.css).styles
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _.set(res.locals, 'pageMeta.title', `Edit ${page.title}`)
|
||||||
|
// _.set(res.locals, 'pageMeta.description', page.description)
|
||||||
|
// page.mode = 'update'
|
||||||
|
// page.isPublished = (page.isPublished === true || page.isPublished === 1) ? 'true' : 'false'
|
||||||
|
// page.content = Buffer.from(page.content).toString('base64')
|
||||||
|
// } else {
|
||||||
|
// // -> CREATE MODE
|
||||||
|
// if (!effectivePermissions.pages.write) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
||||||
|
// return res.render('unauthorized', { action: 'create' })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _.set(res.locals, 'pageMeta.title', `New Page`)
|
||||||
|
// page = {
|
||||||
|
// path: pageArgs.path,
|
||||||
|
// localeCode: pageArgs.locale,
|
||||||
|
// editorKey: null,
|
||||||
|
// mode: 'create',
|
||||||
|
// content: null,
|
||||||
|
// title: null,
|
||||||
|
// description: null,
|
||||||
|
// updatedAt: new Date().toISOString(),
|
||||||
|
// extra: {
|
||||||
|
// css: '',
|
||||||
|
// js: ''
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// res.render('editor', { page, injectCode, effectivePermissions })
|
||||||
|
// })
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * History
|
||||||
|
// */
|
||||||
|
// router.get(['/h', '/h/*'], async (req, res, next) => {
|
||||||
|
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
||||||
|
|
||||||
|
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
||||||
|
// return res.redirect(`/h/${pageArgs.locale}/${pageArgs.path}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// req.i18n.changeLanguage(pageArgs.locale)
|
||||||
|
|
||||||
|
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
||||||
|
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
||||||
|
|
||||||
|
// const page = await WIKI.db.pages.getPageFromDb({
|
||||||
|
// path: pageArgs.path,
|
||||||
|
// locale: pageArgs.locale,
|
||||||
|
// userId: req.user.id,
|
||||||
|
// isPrivate: false
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if (!page) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
|
||||||
|
// return res.status(404).render('notfound', { action: 'history' })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pageArgs.tags = _.get(page, 'tags', [])
|
||||||
|
|
||||||
|
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
||||||
|
|
||||||
|
// if (!effectivePermissions.history.read) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
||||||
|
// return res.render('unauthorized', { action: 'history' })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (page) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', page.title)
|
||||||
|
// _.set(res.locals, 'pageMeta.description', page.description)
|
||||||
|
|
||||||
|
// res.render('history', { page, effectivePermissions })
|
||||||
|
// } else {
|
||||||
|
// res.redirect(`/${pageArgs.path}`)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * 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.db.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,
|
||||||
|
// explicitLocale: false,
|
||||||
|
// tags: page.tags
|
||||||
|
// })) {
|
||||||
|
// _.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}`)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Source
|
||||||
|
// */
|
||||||
|
// router.get(['/s', '/s/*'], async (req, res, next) => {
|
||||||
|
// const pageArgs = pageHelper.parsePath(req.path, { stripExt: true })
|
||||||
|
// const versionId = (req.query.v) ? _.toSafeInteger(req.query.v) : 0
|
||||||
|
|
||||||
|
// const page = await WIKI.db.pages.getPageFromDb({
|
||||||
|
// path: pageArgs.path,
|
||||||
|
// locale: pageArgs.locale,
|
||||||
|
// userId: req.user.id,
|
||||||
|
// isPrivate: false
|
||||||
|
// })
|
||||||
|
|
||||||
|
// pageArgs.tags = _.get(page, 'tags', [])
|
||||||
|
|
||||||
|
// if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
||||||
|
// return res.redirect(`/s/${pageArgs.locale}/${pageArgs.path}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // -> Effective Permissions
|
||||||
|
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
||||||
|
|
||||||
|
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
||||||
|
// _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
||||||
|
|
||||||
|
// if (versionId > 0) {
|
||||||
|
// if (!effectivePermissions.history.read) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
||||||
|
// return res.render('unauthorized', { action: 'sourceVersion' })
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (!effectivePermissions.source.read) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Unauthorized')
|
||||||
|
// return res.render('unauthorized', { action: 'source' })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (page) {
|
||||||
|
// if (versionId > 0) {
|
||||||
|
// const pageVersion = await WIKI.db.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
|
||||||
|
// },
|
||||||
|
// effectivePermissions
|
||||||
|
// })
|
||||||
|
// } else {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', page.title)
|
||||||
|
// _.set(res.locals, 'pageMeta.description', page.description)
|
||||||
|
|
||||||
|
// res.render('source', { page, effectivePermissions })
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// res.redirect(`/${pageArgs.path}`)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Tags
|
||||||
|
// */
|
||||||
|
// router.get(['/t', '/t/*'], (req, res, next) => {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Tags')
|
||||||
|
// res.render('tags')
|
||||||
|
// })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User Avatar
|
||||||
|
*/
|
||||||
|
router.get('/_user/:uid/avatar', async (req, res, next) => {
|
||||||
|
if (!WIKI.auth.checkAccess(req.user, ['read:pages'])) {
|
||||||
|
return res.sendStatus(403)
|
||||||
|
}
|
||||||
|
const av = await WIKI.db.users.getUserAvatarData(req.params.uid)
|
||||||
|
if (av) {
|
||||||
|
res.set('Content-Type', 'image/jpeg')
|
||||||
|
return res.send(av)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.sendStatus(404)
|
||||||
|
})
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * View document / asset
|
||||||
|
// */
|
||||||
|
// router.get('/*', async (req, res, next) => {
|
||||||
|
// 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)
|
||||||
|
// const site = await WIKI.db.sites.getSiteByHostname({ hostname: req.hostname })
|
||||||
|
|
||||||
|
// if (!site) {
|
||||||
|
// throw new Error('INVALID_SITE')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (isPage) {
|
||||||
|
// // if (WIKI.config.lang.namespacing && !pageArgs.explicitLocale) {
|
||||||
|
// // return res.redirect(`/${pageArgs.locale}/${pageArgs.path}`)
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // req.i18n.changeLanguage(pageArgs.locale)
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// // -> Get Page from cache
|
||||||
|
// const page = await WIKI.db.pages.getPage({
|
||||||
|
// siteId: site.id,
|
||||||
|
// path: pageArgs.path,
|
||||||
|
// locale: pageArgs.locale,
|
||||||
|
// userId: req.user.id
|
||||||
|
// })
|
||||||
|
// pageArgs.tags = _.get(page, 'tags', [])
|
||||||
|
|
||||||
|
// // -> Effective Permissions
|
||||||
|
// const effectivePermissions = WIKI.auth.getEffectivePermissions(req, pageArgs)
|
||||||
|
|
||||||
|
// // -> Check User Access
|
||||||
|
// if (!effectivePermissions.pages.read) {
|
||||||
|
// if (req.user.id === WIKI.auth.guest.id) {
|
||||||
|
// res.cookie('loginRedirect', req.path, {
|
||||||
|
// maxAge: 15 * 60 * 1000
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// if (pageArgs.path === 'home' && req.user.id === WIKI.auth.guest.id) {
|
||||||
|
// return res.redirect('/login')
|
||||||
|
// }
|
||||||
|
// return res.redirect(`/_error/unauthorized?from=${req.path}`)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// _.set(res, 'locals.siteConfig.lang', pageArgs.locale)
|
||||||
|
// // _.set(res, 'locals.siteConfig.rtl', req.i18n.dir() === 'rtl')
|
||||||
|
|
||||||
|
// if (page) {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', page.title)
|
||||||
|
// _.set(res.locals, 'pageMeta.description', page.description)
|
||||||
|
|
||||||
|
// // -> 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'
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // -> Render view
|
||||||
|
// res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
||||||
|
// } else if (pageArgs.path === 'home') {
|
||||||
|
// res.redirect('/_welcome')
|
||||||
|
// } else {
|
||||||
|
// _.set(res.locals, 'pageMeta.title', 'Page Not Found')
|
||||||
|
// if (effectivePermissions.pages.write) {
|
||||||
|
// res.status(404).render('new', { path: pageArgs.path, locale: pageArgs.locale })
|
||||||
|
// } else {
|
||||||
|
// res.status(404).render('notfound', { action: 'view' })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } catch (err) {
|
||||||
|
// next(err)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// if (!WIKI.auth.checkAccess(req.user, ['read:assets'], pageArgs)) {
|
||||||
|
// return res.sendStatus(403)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await WIKI.db.assets.getAsset(pageArgs.path, res)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
router.get('/*', (req, res, next) => {
|
||||||
|
res.sendFile(path.join(WIKI.ROOTPATH, 'assets/index.html'))
|
||||||
|
})
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
const express = require('express')
|
|
||||||
const router = express.Router()
|
|
||||||
const _ = require('lodash')
|
|
||||||
const qs = require('querystring')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Let's Encrypt Challenge
|
|
||||||
*/
|
|
||||||
router.get('/.well-known/acme-challenge/:token', (req, res, next) => {
|
|
||||||
res.type('text/plain')
|
|
||||||
if (_.get(WIKI.config, 'letsencrypt.challenge', false)) {
|
|
||||||
if (WIKI.config.letsencrypt.challenge.token === req.params.token) {
|
|
||||||
res.send(WIKI.config.letsencrypt.challenge.keyAuthorization)
|
|
||||||
WIKI.logger.info(`(LETSENCRYPT) Received valid challenge request. [ ACCEPTED ]`)
|
|
||||||
} else {
|
|
||||||
res.status(406).send('Invalid Challenge Token!')
|
|
||||||
WIKI.logger.warn(`(LETSENCRYPT) Received invalid challenge request. [ REJECTED ]`)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(418).end()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to HTTPS if HTTP Redirection is enabled
|
|
||||||
*/
|
|
||||||
// router.all('/*', (req, res, next) => {
|
|
||||||
// if (WIKI.config.server.sslRedir && !req.secure && WIKI.servers.servers.https) {
|
|
||||||
// let query = (!_.isEmpty(req.query)) ? `?${qs.stringify(req.query)}` : ``
|
|
||||||
// return res.redirect(`https://${req.hostname}${req.originalUrl}${query}`)
|
|
||||||
// } else {
|
|
||||||
// next()
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
module.exports = router
|
|
@ -0,0 +1,39 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import { get } from 'lodash-es'
|
||||||
|
import qs from 'querystring'
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const router = express.Router()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Let's Encrypt Challenge
|
||||||
|
*/
|
||||||
|
router.get('/.well-known/acme-challenge/:token', (req, res, next) => {
|
||||||
|
res.type('text/plain')
|
||||||
|
if (get(WIKI.config, 'letsencrypt.challenge', false)) {
|
||||||
|
if (WIKI.config.letsencrypt.challenge.token === req.params.token) {
|
||||||
|
res.send(WIKI.config.letsencrypt.challenge.keyAuthorization)
|
||||||
|
WIKI.logger.info(`(LETSENCRYPT) Received valid challenge request. [ ACCEPTED ]`)
|
||||||
|
} else {
|
||||||
|
res.status(406).send('Invalid Challenge Token!')
|
||||||
|
WIKI.logger.warn(`(LETSENCRYPT) Received invalid challenge request. [ REJECTED ]`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(418).end()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to HTTPS if HTTP Redirection is enabled
|
||||||
|
*/
|
||||||
|
// router.all('/*', (req, res, next) => {
|
||||||
|
// if (WIKI.config.server.sslRedir && !req.secure && WIKI.servers.servers.https) {
|
||||||
|
// let query = (!_.isEmpty(req.query)) ? `?${qs.stringify(req.query)}` : ``
|
||||||
|
// return res.redirect(`https://${req.hostname}${req.originalUrl}${query}`)
|
||||||
|
// } else {
|
||||||
|
// next()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
@ -1,105 +0,0 @@
|
|||||||
const express = require('express')
|
|
||||||
const router = express.Router()
|
|
||||||
const _ = require('lodash')
|
|
||||||
const multer = require('multer')
|
|
||||||
const path = require('path')
|
|
||||||
const sanitize = require('sanitize-filename')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload files
|
|
||||||
*/
|
|
||||||
router.post('/u', (req, res, next) => {
|
|
||||||
multer({
|
|
||||||
dest: path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'),
|
|
||||||
limits: {
|
|
||||||
fileSize: WIKI.config.uploads.maxFileSize,
|
|
||||||
files: WIKI.config.uploads.maxFiles
|
|
||||||
}
|
|
||||||
}).array('mediaUpload')(req, res, next)
|
|
||||||
}, async (req, res, next) => {
|
|
||||||
if (!_.some(req.user.permissions, pm => _.includes(['write:assets', 'manage:system'], pm))) {
|
|
||||||
return res.status(403).json({
|
|
||||||
succeeded: false,
|
|
||||||
message: 'You are not authorized to upload files.'
|
|
||||||
})
|
|
||||||
} else if (req.files.length < 1) {
|
|
||||||
return res.status(400).json({
|
|
||||||
succeeded: false,
|
|
||||||
message: 'Missing upload payload.'
|
|
||||||
})
|
|
||||||
} else if (req.files.length > 1) {
|
|
||||||
return res.status(400).json({
|
|
||||||
succeeded: false,
|
|
||||||
message: 'You cannot upload multiple files within the same request.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const fileMeta = _.get(req, 'files[0]', false)
|
|
||||||
if (!fileMeta) {
|
|
||||||
return res.status(500).json({
|
|
||||||
succeeded: false,
|
|
||||||
message: 'Missing upload file metadata.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get folder Id
|
|
||||||
let folderId = null
|
|
||||||
try {
|
|
||||||
const folderRaw = _.get(req, 'body.mediaUpload', false)
|
|
||||||
if (folderRaw) {
|
|
||||||
folderId = _.get(JSON.parse(folderRaw), 'folderId', null)
|
|
||||||
if (folderId === 0) {
|
|
||||||
folderId = null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error('Missing File Metadata')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(400).json({
|
|
||||||
succeeded: false,
|
|
||||||
message: 'Missing upload folder metadata.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build folder hierarchy
|
|
||||||
let hierarchy = []
|
|
||||||
if (folderId) {
|
|
||||||
try {
|
|
||||||
hierarchy = await WIKI.db.assetFolders.getHierarchy(folderId)
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(400).json({
|
|
||||||
succeeded: false,
|
|
||||||
message: 'Failed to fetch folder hierarchy.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize filename
|
|
||||||
fileMeta.originalname = sanitize(fileMeta.originalname.toLowerCase().replace(/[\s,;#]+/g, '_'))
|
|
||||||
|
|
||||||
// Check if user can upload at path
|
|
||||||
const assetPath = (folderId) ? hierarchy.map(h => h.slug).join('/') + `/${fileMeta.originalname}` : fileMeta.originalname
|
|
||||||
if (!WIKI.auth.checkAccess(req.user, ['write:assets'], { path: assetPath })) {
|
|
||||||
return res.status(403).json({
|
|
||||||
succeeded: false,
|
|
||||||
message: 'You are not authorized to upload files to this folder.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process upload file
|
|
||||||
await WIKI.db.assets.upload({
|
|
||||||
...fileMeta,
|
|
||||||
mode: 'upload',
|
|
||||||
folderId: folderId,
|
|
||||||
assetPath,
|
|
||||||
user: req.user
|
|
||||||
})
|
|
||||||
res.send('ok')
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/u', async (req, res, next) => {
|
|
||||||
res.json({
|
|
||||||
ok: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
|
@ -1,16 +1,16 @@
|
|||||||
const chalk = require('chalk')
|
import chalk from 'chalk'
|
||||||
const os = require('node:os')
|
import os from 'node:os'
|
||||||
|
|
||||||
module.exports = () => {
|
export default function () {
|
||||||
WIKI.servers.ws.on('connection', (socket) => {
|
WIKI.servers.ws.on('connection', (socket) => {
|
||||||
// TODO: Validate token + permissions
|
// TODO: Validate token + permissions
|
||||||
const token = socket.handshake.auth.token
|
const token = socket.handshake.auth.token
|
||||||
console.info(token)
|
// console.info(token)
|
||||||
|
|
||||||
const listeners = {}
|
const listeners = {}
|
||||||
|
|
||||||
socket.on('server:logs', () => {
|
socket.on('server:logs', () => {
|
||||||
socket.emit('server:log', chalk`{greenBright Streaming logs from {bold Wiki.js} instance {yellowBright.bold ${WIKI.INSTANCE_ID}} on host {yellowBright.bold ${os.hostname()}}...}`)
|
socket.emit('server:log', chalk.greenBright(`Streaming logs from ${chalk.bold('Wiki.js')} instance ${chalk.yellowBright.bold(WIKI.INSTANCE_ID)} on host ${chalk.yellowBright.bold(os.hostname())}...`))
|
||||||
listeners.serverLogs = (msg) => {
|
listeners.serverLogs = (msg) => {
|
||||||
socket.emit('server:log', msg)
|
socket.emit('server:log', msg)
|
||||||
}
|
}
|
@ -1,20 +1,19 @@
|
|||||||
const pickle = require('chromium-pickle-js')
|
import pickle from 'chromium-pickle-js'
|
||||||
const path = require('path')
|
import path from 'node:path'
|
||||||
const UINT64 = require('cuint').UINT64
|
import { UINT64 } from 'cuint'
|
||||||
const fs = require('fs')
|
import fs from 'node:fs'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based of express-serve-asar (https://github.com/toyobayashi/express-serve-asar)
|
* Based of express-serve-asar (https://github.com/toyobayashi/express-serve-asar)
|
||||||
* by Fenglin Li (https://github.com/toyobayashi)
|
* by Fenglin Li (https://github.com/toyobayashi)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const packages = {
|
export default {
|
||||||
'twemoji': path.join(WIKI.ROOTPATH, `assets-legacy/svg/twemoji.asar`)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
fdCache: {},
|
fdCache: {},
|
||||||
async serve (pkgName, req, res, next) {
|
async serve (pkgName, req, res, next) {
|
||||||
|
const packages = {
|
||||||
|
'twemoji': path.join(WIKI.ROOTPATH, `assets/svg/twemoji.asar`)
|
||||||
|
}
|
||||||
const file = this.readFilesystemSync(packages[pkgName])
|
const file = this.readFilesystemSync(packages[pkgName])
|
||||||
const { filesystem, fd } = file
|
const { filesystem, fd } = file
|
||||||
const info = filesystem.getFile(req.path.substring(1))
|
const info = filesystem.getFile(req.path.substring(1))
|
@ -1,7 +0,0 @@
|
|||||||
const NodeCache = require('node-cache')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
init() {
|
|
||||||
return new NodeCache()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,13 @@
|
|||||||
const fs = require('fs-extra')
|
import fs from 'node:fs/promises'
|
||||||
const path = require('path')
|
import path from 'path'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
ext: {},
|
ext: {},
|
||||||
async init () {
|
async init () {
|
||||||
const extDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/extensions'))
|
const extDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/extensions'))
|
||||||
WIKI.logger.info(`Checking for installed optional extensions...`)
|
WIKI.logger.info(`Checking for installed optional extensions...`)
|
||||||
for (let dir of extDirs) {
|
for (const dir of extDirs) {
|
||||||
WIKI.extensions.ext[dir] = require(path.join(WIKI.SERVERPATH, 'modules/extensions', dir, 'ext.js'))
|
WIKI.extensions.ext[dir] = (await import(path.join(WIKI.SERVERPATH, 'modules/extensions', dir, 'ext.mjs'))).default
|
||||||
const isInstalled = await WIKI.extensions.ext[dir].check()
|
const isInstalled = await WIKI.extensions.ext[dir].check()
|
||||||
if (isInstalled) {
|
if (isInstalled) {
|
||||||
WIKI.logger.info(`Optional extension ${dir} is installed. [ OK ]`)
|
WIKI.logger.info(`Optional extension ${dir} is installed. [ OK ]`)
|
@ -1,60 +0,0 @@
|
|||||||
const chalk = require('chalk')
|
|
||||||
const EventEmitter = require('events')
|
|
||||||
|
|
||||||
const LEVELS = ['error', 'warn', 'info', 'debug']
|
|
||||||
const LEVELSIGNORED = ['verbose', 'silly']
|
|
||||||
const LEVELCOLORS = {
|
|
||||||
error: 'red',
|
|
||||||
warn: 'yellow',
|
|
||||||
info: 'green',
|
|
||||||
debug: 'cyan'
|
|
||||||
}
|
|
||||||
|
|
||||||
class Logger extends EventEmitter {}
|
|
||||||
const primaryLogger = new Logger()
|
|
||||||
|
|
||||||
let ignoreNextLevels = false
|
|
||||||
|
|
||||||
primaryLogger.ws = new EventEmitter()
|
|
||||||
|
|
||||||
LEVELS.forEach(lvl => {
|
|
||||||
primaryLogger[lvl] = (...args) => {
|
|
||||||
primaryLogger.emit(lvl, ...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ignoreNextLevels) {
|
|
||||||
primaryLogger.on(lvl, (msg) => {
|
|
||||||
let formatted = ''
|
|
||||||
if (WIKI.config.logFormat === 'json') {
|
|
||||||
formatted = JSON.stringify({
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
instance: WIKI.INSTANCE_ID,
|
|
||||||
level: lvl,
|
|
||||||
message: msg
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (msg instanceof Error) {
|
|
||||||
msg = msg.stack
|
|
||||||
}
|
|
||||||
formatted = chalk`${new Date().toISOString()} {dim [${WIKI.INSTANCE_ID}]} {${LEVELCOLORS[lvl]}.bold ${lvl}}: ${msg}`
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(formatted)
|
|
||||||
primaryLogger.ws.emit('log', formatted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (lvl === WIKI.config.logLevel) {
|
|
||||||
ignoreNextLevels = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
LEVELSIGNORED.forEach(lvl => {
|
|
||||||
primaryLogger[lvl] = () => {}
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
loggers: {},
|
|
||||||
init () {
|
|
||||||
return primaryLogger
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,61 @@
|
|||||||
|
import chalk from 'chalk'
|
||||||
|
import EventEmitter from 'node:events'
|
||||||
|
|
||||||
|
const LEVELS = ['error', 'warn', 'info', 'debug']
|
||||||
|
const LEVELSIGNORED = ['verbose', 'silly']
|
||||||
|
const LEVELCOLORS = {
|
||||||
|
error: 'red',
|
||||||
|
warn: 'yellow',
|
||||||
|
info: 'green',
|
||||||
|
debug: 'cyan'
|
||||||
|
}
|
||||||
|
|
||||||
|
class Logger extends EventEmitter {}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
loggers: {},
|
||||||
|
init () {
|
||||||
|
const primaryLogger = new Logger()
|
||||||
|
|
||||||
|
let ignoreNextLevels = false
|
||||||
|
|
||||||
|
primaryLogger.ws = new EventEmitter()
|
||||||
|
|
||||||
|
LEVELS.forEach(lvl => {
|
||||||
|
primaryLogger[lvl] = (...args) => {
|
||||||
|
primaryLogger.emit(lvl, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoreNextLevels) {
|
||||||
|
primaryLogger.on(lvl, (msg) => {
|
||||||
|
let formatted = ''
|
||||||
|
if (WIKI.config.logFormat === 'json') {
|
||||||
|
formatted = JSON.stringify({
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
instance: WIKI.INSTANCE_ID,
|
||||||
|
level: lvl,
|
||||||
|
message: msg
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (msg instanceof Error) {
|
||||||
|
msg = msg.stack
|
||||||
|
}
|
||||||
|
formatted = `${new Date().toISOString()} ${chalk.dim('[' + WIKI.INSTANCE_ID + ']')} ${chalk[LEVELCOLORS[lvl]].bold(lvl)}: ${msg}`
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(formatted)
|
||||||
|
primaryLogger.ws.emit('log', formatted)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (lvl === WIKI.config.logLevel) {
|
||||||
|
ignoreNextLevels = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
LEVELSIGNORED.forEach(lvl => {
|
||||||
|
primaryLogger[lvl] = () => {}
|
||||||
|
})
|
||||||
|
|
||||||
|
return primaryLogger
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
const fs = require('fs-extra')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
updates: {
|
|
||||||
channel: 'BETA',
|
|
||||||
version: WIKI.version,
|
|
||||||
releaseDate: WIKI.releaseDate,
|
|
||||||
minimumVersionRequired: '3.0.0-beta.0',
|
|
||||||
minimumNodeRequired: '18.0.0'
|
|
||||||
},
|
|
||||||
init () {
|
|
||||||
fs.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets'))
|
|
||||||
fs.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'))
|
|
||||||
|
|
||||||
// Clear content cache
|
|
||||||
fs.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
import fse from 'fs-extra'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
updates: {
|
||||||
|
channel: 'BETA',
|
||||||
|
version: WIKI.version,
|
||||||
|
releaseDate: WIKI.releaseDate,
|
||||||
|
minimumVersionRequired: '3.0.0-beta.0',
|
||||||
|
minimumNodeRequired: '18.0.0'
|
||||||
|
},
|
||||||
|
init () {
|
||||||
|
fse.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'assets'))
|
||||||
|
fse.ensureDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads'))
|
||||||
|
|
||||||
|
// Clear content cache
|
||||||
|
fse.emptyDir(path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'cache'))
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const fs = require('fs-extra')
|
|
||||||
const semver = require('semver')
|
|
||||||
|
|
||||||
const baseMigrationPath = path.join(WIKI.SERVERPATH, 'db/migrations')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/**
|
|
||||||
* Gets the migration names
|
|
||||||
* @returns Promise<string[]>
|
|
||||||
*/
|
|
||||||
async getMigrations() {
|
|
||||||
const migrationFiles = await fs.readdir(baseMigrationPath)
|
|
||||||
return migrationFiles.map(m => m.replace('.js', '')).sort(semver.compare).map(m => ({
|
|
||||||
file: m,
|
|
||||||
directory: baseMigrationPath
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
|
|
||||||
getMigrationName(migration) {
|
|
||||||
return migration.file.indexOf('.js') >= 0 ? migration.file : `${migration.file}.js`
|
|
||||||
},
|
|
||||||
|
|
||||||
getMigration(migration) {
|
|
||||||
return require(path.join(baseMigrationPath, migration.file))
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,26 @@
|
|||||||
|
import path from 'node:path'
|
||||||
|
import fse from 'fs-extra'
|
||||||
|
import semver from 'semver'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* Gets the migration names
|
||||||
|
* @returns Promise<string[]>
|
||||||
|
*/
|
||||||
|
async getMigrations() {
|
||||||
|
const baseMigrationPath = path.join(WIKI.SERVERPATH, 'db/migrations')
|
||||||
|
const migrationFiles = await fse.readdir(baseMigrationPath)
|
||||||
|
return migrationFiles.map(m => m.replace('.mjs', '')).sort(semver.compare).map(m => ({
|
||||||
|
file: m,
|
||||||
|
directory: baseMigrationPath
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
getMigrationName(migration) {
|
||||||
|
return migration.file.indexOf('.mjs') >= 0 ? migration.file : `${migration.file}.mjs`
|
||||||
|
},
|
||||||
|
|
||||||
|
async getMigration(migration) {
|
||||||
|
return import(path.join(WIKI.SERVERPATH, 'db/migrations', `${migration.file}.mjs`))
|
||||||
|
}
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
const _ = require('lodash')
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const autoload = require('auto-load')
|
|
||||||
const { makeExecutableSchema } = require('@graphql-tools/schema')
|
|
||||||
const { defaultKeyGenerator, rateLimitDirective } = require('graphql-rate-limit-directive')
|
|
||||||
const { GraphQLUpload } = require('graphql-upload')
|
|
||||||
|
|
||||||
// Rate Limiter
|
|
||||||
|
|
||||||
const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective({
|
|
||||||
keyGenerator: (directiveArgs, source, args, context, info) => `${context.req.ip}:${defaultKeyGenerator(directiveArgs, source, args, context, info)}`
|
|
||||||
})
|
|
||||||
|
|
||||||
// Schemas
|
|
||||||
|
|
||||||
WIKI.logger.info(`Loading GraphQL Schema...`)
|
|
||||||
const typeDefs = [
|
|
||||||
rateLimitDirectiveTypeDefs
|
|
||||||
]
|
|
||||||
const schemas = fs.readdirSync(path.join(WIKI.SERVERPATH, 'graph/schemas'))
|
|
||||||
schemas.forEach(schema => {
|
|
||||||
typeDefs.push(fs.readFileSync(path.join(WIKI.SERVERPATH, `graph/schemas/${schema}`), 'utf8'))
|
|
||||||
})
|
|
||||||
|
|
||||||
// Resolvers
|
|
||||||
|
|
||||||
WIKI.logger.info(`Loading GraphQL Resolvers...`)
|
|
||||||
let resolvers = {
|
|
||||||
Date: require('./scalars/date'),
|
|
||||||
JSON: require('./scalars/json'),
|
|
||||||
UUID: require('./scalars/uuid'),
|
|
||||||
Upload: GraphQLUpload
|
|
||||||
}
|
|
||||||
const resolversObj = _.values(autoload(path.join(WIKI.SERVERPATH, 'graph/resolvers')))
|
|
||||||
resolversObj.forEach(resolver => {
|
|
||||||
_.merge(resolvers, resolver)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Make executable schema
|
|
||||||
|
|
||||||
WIKI.logger.info(`Compiling GraphQL Schema...`)
|
|
||||||
let schema = makeExecutableSchema({
|
|
||||||
typeDefs,
|
|
||||||
resolvers
|
|
||||||
})
|
|
||||||
|
|
||||||
// Apply schema transforms
|
|
||||||
|
|
||||||
schema = rateLimitDirectiveTransformer(schema)
|
|
||||||
|
|
||||||
WIKI.logger.info(`GraphQL Schema: [ OK ]`)
|
|
||||||
|
|
||||||
module.exports = schema
|
|
@ -0,0 +1,61 @@
|
|||||||
|
import { merge } from 'lodash-es'
|
||||||
|
import fs from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { makeExecutableSchema } from '@graphql-tools/schema'
|
||||||
|
import { defaultKeyGenerator, rateLimitDirective } from 'graphql-rate-limit-directive'
|
||||||
|
import GraphQLUpload from 'graphql-upload/GraphQLUpload.mjs'
|
||||||
|
|
||||||
|
import DateScalar from './scalars/date.mjs'
|
||||||
|
import JSONScalar from './scalars/json.mjs'
|
||||||
|
import UUIDScalar from './scalars/uuid.mjs'
|
||||||
|
|
||||||
|
export async function initSchema () {
|
||||||
|
// Rate Limiter
|
||||||
|
|
||||||
|
const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective({
|
||||||
|
keyGenerator: (directiveArgs, source, args, context, info) => `${context.req.ip}:${defaultKeyGenerator(directiveArgs, source, args, context, info)}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Schemas
|
||||||
|
|
||||||
|
WIKI.logger.info(`Loading GraphQL Schema...`)
|
||||||
|
const typeDefs = [
|
||||||
|
rateLimitDirectiveTypeDefs
|
||||||
|
]
|
||||||
|
const schemaList = await fs.readdir(path.join(WIKI.SERVERPATH, 'graph/schemas'))
|
||||||
|
for (const schemaFile of schemaList) {
|
||||||
|
typeDefs.push(await fs.readFile(path.join(WIKI.SERVERPATH, `graph/schemas/${schemaFile}`), 'utf8'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolvers
|
||||||
|
|
||||||
|
WIKI.logger.info(`Loading GraphQL Resolvers...`)
|
||||||
|
let resolvers = {
|
||||||
|
Date: DateScalar,
|
||||||
|
JSON: JSONScalar,
|
||||||
|
UUID: UUIDScalar,
|
||||||
|
Upload: GraphQLUpload
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolverList = await fs.readdir(path.join(WIKI.SERVERPATH, 'graph/resolvers'))
|
||||||
|
for (const resolverFile of resolverList) {
|
||||||
|
const resolver = (await import(path.join(WIKI.SERVERPATH, 'graph/resolvers', resolverFile))).default
|
||||||
|
merge(resolvers, resolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make executable schema
|
||||||
|
|
||||||
|
WIKI.logger.info(`Compiling GraphQL Schema...`)
|
||||||
|
let schema = makeExecutableSchema({
|
||||||
|
typeDefs,
|
||||||
|
resolvers
|
||||||
|
})
|
||||||
|
|
||||||
|
// Apply schema transforms
|
||||||
|
|
||||||
|
schema = rateLimitDirectiveTransformer(schema)
|
||||||
|
|
||||||
|
WIKI.logger.info(`GraphQL Schema: [ OK ]`)
|
||||||
|
|
||||||
|
return schema
|
||||||
|
}
|
@ -1,16 +0,0 @@
|
|||||||
const graphHelper = require('../../helpers/graph')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
Mutation: {
|
|
||||||
async rebuildSearchIndex (obj, args, context) {
|
|
||||||
try {
|
|
||||||
await WIKI.data.searchEngine.rebuild()
|
|
||||||
return {
|
|
||||||
responseResult: graphHelper.generateSuccess('Index rebuilt successfully')
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return graphHelper.generateError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,16 @@
|
|||||||
|
import { generateError, generateSuccess } from '../../helpers/graph.mjs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Mutation: {
|
||||||
|
async rebuildSearchIndex (obj, args, context) {
|
||||||
|
try {
|
||||||
|
await WIKI.data.searchEngine.rebuild()
|
||||||
|
return {
|
||||||
|
responseResult: generateSuccess('Index rebuilt successfully')
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return generateError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
const gql = require('graphql')
|
import gql from 'graphql'
|
||||||
const { DateTime } = require('luxon')
|
import { DateTime } from 'luxon'
|
||||||
|
|
||||||
function parseDateTime (value) {
|
function parseDateTime (value) {
|
||||||
const nDate = DateTime.fromISO(value)
|
const nDate = DateTime.fromISO(value)
|
||||||
return nDate.isValid ? nDate : null
|
return nDate.isValid ? nDate : null
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new gql.GraphQLScalarType({
|
export default new gql.GraphQLScalarType({
|
||||||
name: 'Date',
|
name: 'Date',
|
||||||
description: 'ISO date-time string at UTC',
|
description: 'ISO date-time string at UTC',
|
||||||
parseValue(value) {
|
parseValue(value) {
|
@ -1,99 +0,0 @@
|
|||||||
const _ = require('lodash')
|
|
||||||
const crypto = require('node:crypto')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/* eslint-disable promise/param-names */
|
|
||||||
createDeferred () {
|
|
||||||
let result, resolve, reject
|
|
||||||
return {
|
|
||||||
resolve: function (value) {
|
|
||||||
if (resolve) {
|
|
||||||
resolve(value)
|
|
||||||
} else {
|
|
||||||
result = result || new Promise(function (r) { r(value) })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
reject: function (reason) {
|
|
||||||
if (reject) {
|
|
||||||
reject(reason)
|
|
||||||
} else {
|
|
||||||
result = result || new Promise(function (x, j) { j(reason) })
|
|
||||||
}
|
|
||||||
},
|
|
||||||
promise: new Promise(function (r, j) {
|
|
||||||
if (result) {
|
|
||||||
r(result)
|
|
||||||
} else {
|
|
||||||
resolve = r
|
|
||||||
reject = j
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Decode a tree path
|
|
||||||
*
|
|
||||||
* @param {string} str String to decode
|
|
||||||
* @returns Decoded tree path
|
|
||||||
*/
|
|
||||||
decodeTreePath (str) {
|
|
||||||
return str.replaceAll('_', '-').replaceAll('.', '/')
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Encode a tree path
|
|
||||||
*
|
|
||||||
* @param {string} str String to encode
|
|
||||||
* @returns Encoded tree path
|
|
||||||
*/
|
|
||||||
encodeTreePath (str) {
|
|
||||||
return str?.toLowerCase()?.replaceAll('-', '_')?.replaceAll('/', '.') || ''
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Generate SHA-1 Hash of a string
|
|
||||||
*
|
|
||||||
* @param {string} str String to hash
|
|
||||||
* @returns Hashed string
|
|
||||||
*/
|
|
||||||
generateHash (str) {
|
|
||||||
return crypto.createHash('sha1').update(str).digest('hex')
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get default value of type
|
|
||||||
*
|
|
||||||
* @param {any} type primitive type name
|
|
||||||
* @returns Default value
|
|
||||||
*/
|
|
||||||
getTypeDefaultValue (type) {
|
|
||||||
switch (type.toLowerCase()) {
|
|
||||||
case 'string':
|
|
||||||
return ''
|
|
||||||
case 'number':
|
|
||||||
return 0
|
|
||||||
case 'boolean':
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parseModuleProps (props) {
|
|
||||||
return _.transform(props, (result, value, key) => {
|
|
||||||
let defaultValue = ''
|
|
||||||
if (_.isPlainObject(value)) {
|
|
||||||
defaultValue = !_.isNil(value.default) ? value.default : this.getTypeDefaultValue(value.type)
|
|
||||||
} else {
|
|
||||||
defaultValue = this.getTypeDefaultValue(value)
|
|
||||||
}
|
|
||||||
_.set(result, key, {
|
|
||||||
default: defaultValue,
|
|
||||||
type: (value.type || value).toLowerCase(),
|
|
||||||
title: value.title || _.startCase(key),
|
|
||||||
hint: value.hint || '',
|
|
||||||
enum: value.enum || false,
|
|
||||||
enumDisplay: value.enumDisplay || 'select',
|
|
||||||
multiline: value.multiline || false,
|
|
||||||
sensitive: value.sensitive || false,
|
|
||||||
icon: value.icon || 'rename',
|
|
||||||
order: value.order || 100
|
|
||||||
})
|
|
||||||
return result
|
|
||||||
}, {})
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,102 @@
|
|||||||
|
import { isNil, isPlainObject, set, startCase, transform } from 'lodash-es'
|
||||||
|
import crypto from 'node:crypto'
|
||||||
|
|
||||||
|
/* eslint-disable promise/param-names */
|
||||||
|
export function createDeferred () {
|
||||||
|
let result, resolve, reject
|
||||||
|
return {
|
||||||
|
resolve: function (value) {
|
||||||
|
if (resolve) {
|
||||||
|
resolve(value)
|
||||||
|
} else {
|
||||||
|
result = result || new Promise(function (r) { r(value) })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reject: function (reason) {
|
||||||
|
if (reject) {
|
||||||
|
reject(reason)
|
||||||
|
} else {
|
||||||
|
result = result || new Promise(function (x, j) { j(reason) })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
promise: new Promise(function (r, j) {
|
||||||
|
if (result) {
|
||||||
|
r(result)
|
||||||
|
} else {
|
||||||
|
resolve = r
|
||||||
|
reject = j
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a tree path
|
||||||
|
*
|
||||||
|
* @param {string} str String to decode
|
||||||
|
* @returns Decoded tree path
|
||||||
|
*/
|
||||||
|
export function decodeTreePath (str) {
|
||||||
|
return str.replaceAll('_', '-').replaceAll('.', '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode a tree path
|
||||||
|
*
|
||||||
|
* @param {string} str String to encode
|
||||||
|
* @returns Encoded tree path
|
||||||
|
*/
|
||||||
|
export function encodeTreePath (str) {
|
||||||
|
return str?.toLowerCase()?.replaceAll('-', '_')?.replaceAll('/', '.') || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate SHA-1 Hash of a string
|
||||||
|
*
|
||||||
|
* @param {string} str String to hash
|
||||||
|
* @returns Hashed string
|
||||||
|
*/
|
||||||
|
export function generateHash (str) {
|
||||||
|
return crypto.createHash('sha1').update(str).digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default value of type
|
||||||
|
*
|
||||||
|
* @param {any} type primitive type name
|
||||||
|
* @returns Default value
|
||||||
|
*/
|
||||||
|
export function getTypeDefaultValue (type) {
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case 'string':
|
||||||
|
return ''
|
||||||
|
case 'number':
|
||||||
|
return 0
|
||||||
|
case 'boolean':
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseModuleProps (props) {
|
||||||
|
return transform(props, (result, value, key) => {
|
||||||
|
let defaultValue = ''
|
||||||
|
if (isPlainObject(value)) {
|
||||||
|
defaultValue = !isNil(value.default) ? value.default : this.getTypeDefaultValue(value.type)
|
||||||
|
} else {
|
||||||
|
defaultValue = this.getTypeDefaultValue(value)
|
||||||
|
}
|
||||||
|
set(result, key, {
|
||||||
|
default: defaultValue,
|
||||||
|
type: (value.type || value).toLowerCase(),
|
||||||
|
title: value.title || startCase(key),
|
||||||
|
hint: value.hint || '',
|
||||||
|
enum: value.enum || false,
|
||||||
|
enumDisplay: value.enumDisplay || 'select',
|
||||||
|
multiline: value.multiline || false,
|
||||||
|
sensitive: value.sensitive || false,
|
||||||
|
icon: value.icon || 'rename',
|
||||||
|
order: value.order || 100
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}, {})
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
const CustomError = require('custom-error-instance')
|
import CustomError from 'custom-error-instance'
|
||||||
|
|
||||||
module.exports = {
|
export default {
|
||||||
Custom (slug, message) {
|
Custom (slug, message) {
|
||||||
return CustomError(slug, { message })
|
return CustomError(slug, { message })
|
||||||
},
|
},
|
@ -1,21 +0,0 @@
|
|||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
generateSuccess (msg) {
|
|
||||||
return {
|
|
||||||
succeeded: true,
|
|
||||||
errorCode: 0,
|
|
||||||
slug: 'ok',
|
|
||||||
message: _.defaultTo(msg, 'Operation succeeded.')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
generateError (err, complete = true) {
|
|
||||||
const error = {
|
|
||||||
succeeded: false,
|
|
||||||
errorCode: _.isFinite(err.code) ? err.code : 1,
|
|
||||||
slug: err.name,
|
|
||||||
message: err.message || 'An unexpected error occured.'
|
|
||||||
}
|
|
||||||
return (complete) ? { operation: error } : error
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,20 @@
|
|||||||
|
import { defaultTo, isFinite } from 'lodash-es'
|
||||||
|
|
||||||
|
export function generateSuccess (msg) {
|
||||||
|
return {
|
||||||
|
succeeded: true,
|
||||||
|
errorCode: 0,
|
||||||
|
slug: 'ok',
|
||||||
|
message: defaultTo(msg, 'Operation succeeded.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateError (err, complete = true) {
|
||||||
|
const error = {
|
||||||
|
succeeded: false,
|
||||||
|
errorCode: isFinite(err.code) ? err.code : 1,
|
||||||
|
slug: err.name,
|
||||||
|
message: err.message || 'An unexpected error occured.'
|
||||||
|
}
|
||||||
|
return (complete) ? { operation: error } : error
|
||||||
|
}
|
@ -1,151 +0,0 @@
|
|||||||
const qs = require('querystring')
|
|
||||||
const _ = require('lodash')
|
|
||||||
const crypto = require('crypto')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
|
|
||||||
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
|
|
||||||
// eslint-disable-next-line no-control-regex
|
|
||||||
const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/
|
|
||||||
|
|
||||||
const contentToExt = {
|
|
||||||
markdown: 'md',
|
|
||||||
html: 'html'
|
|
||||||
}
|
|
||||||
const extToContent = _.invert(contentToExt)
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/**
|
|
||||||
* Parse raw url path and make it safe
|
|
||||||
*/
|
|
||||||
parsePath (rawPath, opts = {}) {
|
|
||||||
let pathObj = {
|
|
||||||
// TODO: use site base lang
|
|
||||||
locale: 'en', // WIKI.config.lang.code,
|
|
||||||
path: 'home',
|
|
||||||
private: false,
|
|
||||||
privateNS: '',
|
|
||||||
explicitLocale: false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean Path
|
|
||||||
rawPath = _.trim(qs.unescape(rawPath))
|
|
||||||
if (_.startsWith(rawPath, '/')) { rawPath = rawPath.substring(1) }
|
|
||||||
rawPath = rawPath.replace(unsafeCharsRegex, '')
|
|
||||||
if (rawPath === '') { rawPath = 'home' }
|
|
||||||
|
|
||||||
rawPath = rawPath.replace(/\\/g, '').replace(/\/\//g, '').replace(/\.\.+/ig, '')
|
|
||||||
|
|
||||||
// Extract Info
|
|
||||||
let pathParts = _.filter(_.split(rawPath, '/'), p => {
|
|
||||||
p = _.trim(p)
|
|
||||||
return !_.isEmpty(p) && p !== '..' && p !== '.'
|
|
||||||
})
|
|
||||||
if (pathParts[0].startsWith('_')) {
|
|
||||||
pathParts.shift()
|
|
||||||
}
|
|
||||||
if (localeSegmentRegex.test(pathParts[0])) {
|
|
||||||
pathObj.locale = pathParts[0]
|
|
||||||
pathObj.explicitLocale = true
|
|
||||||
pathParts.shift()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip extension
|
|
||||||
if (opts.stripExt && pathParts.length > 0) {
|
|
||||||
const lastPart = _.last(pathParts)
|
|
||||||
if (lastPart.indexOf('.') > 0) {
|
|
||||||
pathParts.pop()
|
|
||||||
const lastPartMeta = path.parse(lastPart)
|
|
||||||
pathParts.push(lastPartMeta.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pathObj.path = _.join(pathParts, '/')
|
|
||||||
return pathObj
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Generate unique hash from page
|
|
||||||
*/
|
|
||||||
generateHash(opts) {
|
|
||||||
return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Inject Page Metadata
|
|
||||||
*/
|
|
||||||
injectPageMetadata(page) {
|
|
||||||
let meta = [
|
|
||||||
['title', page.title],
|
|
||||||
['description', page.description],
|
|
||||||
['published', page.isPublished.toString()],
|
|
||||||
['date', page.updatedAt],
|
|
||||||
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
|
|
||||||
['editor', page.editorKey],
|
|
||||||
['dateCreated', page.createdAt]
|
|
||||||
]
|
|
||||||
switch (page.contentType) {
|
|
||||||
case 'markdown':
|
|
||||||
return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
|
|
||||||
case 'html':
|
|
||||||
return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
|
|
||||||
case 'json':
|
|
||||||
return {
|
|
||||||
...page.content,
|
|
||||||
_meta: _.fromPairs(meta)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return page.content
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Check if path is a reserved path
|
|
||||||
*/
|
|
||||||
isReservedPath(rawPath) {
|
|
||||||
const firstSection = _.head(rawPath.split('/'))
|
|
||||||
if (firstSection.length < 1) {
|
|
||||||
return true
|
|
||||||
} else if (localeSegmentRegex.test(firstSection)) {
|
|
||||||
return true
|
|
||||||
} else if (
|
|
||||||
_.some(WIKI.data.reservedPaths, p => {
|
|
||||||
return p === firstSection
|
|
||||||
})) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get file extension from content type
|
|
||||||
*/
|
|
||||||
getFileExtension(contentType) {
|
|
||||||
return _.get(contentToExt, contentType, 'txt')
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get content type from file extension
|
|
||||||
*/
|
|
||||||
getContentType (filePath) {
|
|
||||||
const ext = _.last(filePath.split('.'))
|
|
||||||
return _.get(extToContent, ext, false)
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get Page Meta object from disk path
|
|
||||||
*/
|
|
||||||
getPagePath (filePath) {
|
|
||||||
let fpath = filePath
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
fpath = filePath.replace(/\\/g, '/')
|
|
||||||
}
|
|
||||||
let meta = {
|
|
||||||
locale: WIKI.config.lang.code,
|
|
||||||
path: _.initial(fpath.split('.')).join('')
|
|
||||||
}
|
|
||||||
const result = localeFolderRegex.exec(meta.path)
|
|
||||||
if (result[1]) {
|
|
||||||
meta = {
|
|
||||||
locale: result[1].replace('/', ''),
|
|
||||||
path: result[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,155 @@
|
|||||||
|
import qs from 'querystring'
|
||||||
|
import { fromPairs, get, initial, invert, isEmpty, last } from 'lodash-es'
|
||||||
|
import crypto from 'node:crypto'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
|
||||||
|
const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/
|
||||||
|
|
||||||
|
const contentToExt = {
|
||||||
|
markdown: 'md',
|
||||||
|
html: 'html'
|
||||||
|
}
|
||||||
|
const extToContent = invert(contentToExt)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse raw url path and make it safe
|
||||||
|
*/
|
||||||
|
export function parsePath (rawPath, opts = {}) {
|
||||||
|
const pathObj = {
|
||||||
|
// TODO: use site base lang
|
||||||
|
locale: 'en', // WIKI.config.lang.code,
|
||||||
|
path: 'home',
|
||||||
|
private: false,
|
||||||
|
privateNS: '',
|
||||||
|
explicitLocale: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean Path
|
||||||
|
rawPath = qs.unescape(rawPath).trim()
|
||||||
|
if (rawPath.startsWith('/')) { rawPath = rawPath.substring(1) }
|
||||||
|
rawPath = rawPath.replace(unsafeCharsRegex, '')
|
||||||
|
if (rawPath === '') { rawPath = 'home' }
|
||||||
|
|
||||||
|
rawPath = rawPath.replace(/\\/g, '').replace(/\/\//g, '').replace(/\.\.+/ig, '')
|
||||||
|
|
||||||
|
// Extract Info
|
||||||
|
let pathParts = rawPath.split('/').filter(p => {
|
||||||
|
p = p.trim()
|
||||||
|
return !isEmpty(p) && p !== '..' && p !== '.'
|
||||||
|
})
|
||||||
|
if (pathParts[0].startsWith('_')) {
|
||||||
|
pathParts.shift()
|
||||||
|
}
|
||||||
|
if (localeSegmentRegex.test(pathParts[0])) {
|
||||||
|
pathObj.locale = pathParts[0]
|
||||||
|
pathObj.explicitLocale = true
|
||||||
|
pathParts.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip extension
|
||||||
|
if (opts.stripExt && pathParts.length > 0) {
|
||||||
|
const lastPart = last(pathParts)
|
||||||
|
if (lastPart.indexOf('.') > 0) {
|
||||||
|
pathParts.pop()
|
||||||
|
const lastPartMeta = path.parse(lastPart)
|
||||||
|
pathParts.push(lastPartMeta.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pathObj.path = _.join(pathParts, '/')
|
||||||
|
return pathObj
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate unique hash from page
|
||||||
|
*/
|
||||||
|
export function generateHash(opts) {
|
||||||
|
return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Page Metadata
|
||||||
|
*/
|
||||||
|
export function injectPageMetadata(page) {
|
||||||
|
const meta = [
|
||||||
|
['title', page.title],
|
||||||
|
['description', page.description],
|
||||||
|
['published', page.isPublished.toString()],
|
||||||
|
['date', page.updatedAt],
|
||||||
|
['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
|
||||||
|
['editor', page.editorKey],
|
||||||
|
['dateCreated', page.createdAt]
|
||||||
|
]
|
||||||
|
switch (page.contentType) {
|
||||||
|
case 'markdown':
|
||||||
|
return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
|
||||||
|
case 'html':
|
||||||
|
return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
|
||||||
|
case 'json':
|
||||||
|
return {
|
||||||
|
...page.content,
|
||||||
|
_meta: fromPairs(meta)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return page.content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if path is a reserved path
|
||||||
|
*/
|
||||||
|
export function isReservedPath(rawPath) {
|
||||||
|
const firstSection = _.head(rawPath.split('/'))
|
||||||
|
if (firstSection.length < 1) {
|
||||||
|
return true
|
||||||
|
} else if (localeSegmentRegex.test(firstSection)) {
|
||||||
|
return true
|
||||||
|
} else if (
|
||||||
|
WIKI.data.reservedPaths.some(p => {
|
||||||
|
return p === firstSection
|
||||||
|
})) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get file extension from content type
|
||||||
|
*/
|
||||||
|
export function getFileExtension(contentType) {
|
||||||
|
return get(contentToExt, contentType, 'txt')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get content type from file extension
|
||||||
|
*/
|
||||||
|
export function getContentType (filePath) {
|
||||||
|
const ext = last(filePath.split('.'))
|
||||||
|
return get(extToContent, ext, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Page Meta object from disk path
|
||||||
|
*/
|
||||||
|
export function getPagePath (filePath) {
|
||||||
|
let fpath = filePath
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
fpath = filePath.replace(/\\/g, '/')
|
||||||
|
}
|
||||||
|
let meta = {
|
||||||
|
locale: WIKI.config.lang.code,
|
||||||
|
path: initial(fpath.split('.')).join('')
|
||||||
|
}
|
||||||
|
const result = localeFolderRegex.exec(meta.path)
|
||||||
|
if (result[1]) {
|
||||||
|
meta = {
|
||||||
|
locale: result[1].replace('/', ''),
|
||||||
|
path: result[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
const util = require('node:util')
|
|
||||||
const crypto = require('node:crypto')
|
|
||||||
const randomBytes = util.promisify(crypto.randomBytes)
|
|
||||||
const passportJWT = require('passport-jwt')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
sanitizeCommitUser (user) {
|
|
||||||
// let wlist = new RegExp('[^a-zA-Z0-9-_.\',& ' + appdata.regex.cjk + appdata.regex.arabic + ']', 'g')
|
|
||||||
// return {
|
|
||||||
// name: _.chain(user.name).replace(wlist, '').trim().value(),
|
|
||||||
// email: appconfig.git.showUserEmail ? user.email : appconfig.git.serverEmail
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Generate a random token
|
|
||||||
*
|
|
||||||
* @param {any} length
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async generateToken (length) {
|
|
||||||
return (await randomBytes(length)).toString('hex')
|
|
||||||
},
|
|
||||||
|
|
||||||
extractJWT: passportJWT.ExtractJwt.fromExtractors([
|
|
||||||
passportJWT.ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
||||||
(req) => {
|
|
||||||
let token = null
|
|
||||||
if (req && req.cookies) {
|
|
||||||
token = req.cookies['jwt']
|
|
||||||
}
|
|
||||||
// Force uploads to use Auth headers
|
|
||||||
if (req.path.toLowerCase() === '/u') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return token
|
|
||||||
}
|
|
||||||
])
|
|
||||||
}
|
|
@ -0,0 +1,38 @@
|
|||||||
|
import util from 'node:util'
|
||||||
|
import crypto from 'node:crypto'
|
||||||
|
import passportJWT from 'passport-jwt'
|
||||||
|
|
||||||
|
const randomBytes = util.promisify(crypto.randomBytes)
|
||||||
|
|
||||||
|
export function sanitizeCommitUser (user) {
|
||||||
|
// let wlist = new RegExp('[^a-zA-Z0-9-_.\',& ' + appdata.regex.cjk + appdata.regex.arabic + ']', 'g')
|
||||||
|
// return {
|
||||||
|
// name: _.chain(user.name).replace(wlist, '').trim().value(),
|
||||||
|
// email: appconfig.git.showUserEmail ? user.email : appconfig.git.serverEmail
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random token
|
||||||
|
*
|
||||||
|
* @param {any} length
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function generateToken (length) {
|
||||||
|
return (await randomBytes(length)).toString('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const extractJWT = passportJWT.ExtractJwt.fromExtractors([
|
||||||
|
passportJWT.ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
(req) => {
|
||||||
|
let token = null
|
||||||
|
if (req && req.cookies) {
|
||||||
|
token = req.cookies['jwt']
|
||||||
|
}
|
||||||
|
// Force uploads to use Auth headers
|
||||||
|
if (req.path.toLowerCase() === '/u') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
])
|
@ -1,20 +0,0 @@
|
|||||||
## IMPORTANT
|
|
||||||
|
|
||||||
Localization files are not stored into files!
|
|
||||||
|
|
||||||
Contact us on Gitter to request access to the translation web service: https://gitter.im/Requarks/wiki
|
|
||||||
|
|
||||||
## Development Mode
|
|
||||||
|
|
||||||
If you need to add new keys and test them live, simply create a {LANG}.yml file in this folder containing the values you want to test. e.g.:
|
|
||||||
|
|
||||||
### en.yml
|
|
||||||
```yml
|
|
||||||
admin:
|
|
||||||
api.title: 'API Access'
|
|
||||||
auth.title: 'Authentication'
|
|
||||||
```
|
|
||||||
|
|
||||||
The official localization keys will still be loaded first, but your local files will overwrite any existing keys (and add new ones).
|
|
||||||
|
|
||||||
Note that you must restart Wiki.js to load any changes made to the files, which happens automatically on save when in dev mode.
|
|
@ -1,46 +0,0 @@
|
|||||||
/* global WIKI */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Security Middleware
|
|
||||||
*
|
|
||||||
* @param {Express Request} req Express request object
|
|
||||||
* @param {Express Response} res Express response object
|
|
||||||
* @param {Function} next next callback function
|
|
||||||
* @return {any} void
|
|
||||||
*/
|
|
||||||
module.exports = function (req, res, next) {
|
|
||||||
// -> Disable X-Powered-By
|
|
||||||
req.app.disable('x-powered-by')
|
|
||||||
|
|
||||||
// -> Disable Frame Embedding
|
|
||||||
if (WIKI.config.security.securityIframe) {
|
|
||||||
res.set('X-Frame-Options', 'deny')
|
|
||||||
}
|
|
||||||
|
|
||||||
// -> Re-enable XSS Fitler if disabled
|
|
||||||
res.set('X-XSS-Protection', '1; mode=block')
|
|
||||||
|
|
||||||
// -> Disable MIME-sniffing
|
|
||||||
res.set('X-Content-Type-Options', 'nosniff')
|
|
||||||
|
|
||||||
// -> Disable IE Compatibility Mode
|
|
||||||
res.set('X-UA-Compatible', 'IE=edge')
|
|
||||||
|
|
||||||
// -> Disables referrer header when navigating to a different origin
|
|
||||||
if (WIKI.config.security.securityReferrerPolicy) {
|
|
||||||
res.set('Referrer-Policy', 'same-origin')
|
|
||||||
}
|
|
||||||
|
|
||||||
// -> Enforce HSTS
|
|
||||||
if (WIKI.config.security.securityHSTS) {
|
|
||||||
res.set('Strict-Transport-Security', `max-age=${WIKI.config.security.securityHSTSDuration}; includeSubDomains`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// -> Prevent Open Redirect from user provided URL
|
|
||||||
if (WIKI.config.security.securityOpenRedirect) {
|
|
||||||
// Strips out all repeating / character in the provided URL
|
|
||||||
req.url = req.url.replace(/(\/)(?=\/*\1)/g, '')
|
|
||||||
}
|
|
||||||
|
|
||||||
return next()
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SEO Middleware
|
|
||||||
*
|
|
||||||
* @param {Express Request} req Express request object
|
|
||||||
* @param {Express Response} res Express response object
|
|
||||||
* @param {Function} next next callback function
|
|
||||||
* @return {any} void
|
|
||||||
*/
|
|
||||||
module.exports = function (req, res, next) {
|
|
||||||
if (req.path.length > 1 && _.endsWith(req.path, '/')) {
|
|
||||||
let query = req.url.slice(req.path.length) || ''
|
|
||||||
res.redirect(301, req.path.slice(0, -1) + query)
|
|
||||||
} else {
|
|
||||||
_.set(res.locals, 'pageMeta.url', `${WIKI.config.host}${req.path}`)
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,14 @@
|
|||||||
/* global WIKI */
|
/* global WIKI */
|
||||||
|
|
||||||
const Model = require('objection').Model
|
import { Model } from 'objection'
|
||||||
const { DateTime } = require('luxon')
|
import { DateTime } from 'luxon'
|
||||||
const ms = require('ms')
|
import ms from 'ms'
|
||||||
const jwt = require('jsonwebtoken')
|
import jwt from 'jsonwebtoken'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Users model
|
* Users model
|
||||||
*/
|
*/
|
||||||
module.exports = class ApiKey extends Model {
|
export class ApiKey extends Model {
|
||||||
static get tableName() { return 'apiKeys' }
|
static get tableName() { return 'apiKeys' }
|
||||||
|
|
||||||
static get jsonSchema () {
|
static get jsonSchema () {
|
@ -1,9 +1,9 @@
|
|||||||
const Model = require('objection').Model
|
import { Model } from 'objection'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook model
|
* Hook model
|
||||||
*/
|
*/
|
||||||
module.exports = class Hook extends Model {
|
export class Hook extends Model {
|
||||||
static get tableName () { return 'hooks' }
|
static get tableName () { return 'hooks' }
|
||||||
|
|
||||||
static get jsonAttributes () {
|
static get jsonAttributes () {
|
@ -0,0 +1,45 @@
|
|||||||
|
import { Analytics } from './analytics.mjs'
|
||||||
|
import { ApiKey } from './apiKeys.mjs'
|
||||||
|
import { Asset } from './assets.mjs'
|
||||||
|
import { Authentication } from './authentication.mjs'
|
||||||
|
import { CommentProvider } from './commentProviders.mjs'
|
||||||
|
import { Comment } from './comments.mjs'
|
||||||
|
import { Group } from './groups.mjs'
|
||||||
|
import { Hook } from './hooks.mjs'
|
||||||
|
import { Locale } from './locales.mjs'
|
||||||
|
import { Navigation } from './navigation.mjs'
|
||||||
|
import { PageHistory } from './pageHistory.mjs'
|
||||||
|
import { PageLink } from './pageLinks.mjs'
|
||||||
|
import { Page } from './pages.mjs'
|
||||||
|
import { Renderer } from './renderers.mjs'
|
||||||
|
import { Setting } from './settings.mjs'
|
||||||
|
import { Site } from './sites.mjs'
|
||||||
|
import { Storage } from './storage.mjs'
|
||||||
|
import { Tag } from './tags.mjs'
|
||||||
|
import { Tree } from './tree.mjs'
|
||||||
|
import { UserKey } from './userKeys.mjs'
|
||||||
|
import { User } from './users.mjs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
analytics: Analytics,
|
||||||
|
apiKeys: ApiKey,
|
||||||
|
assets: Asset,
|
||||||
|
authentication: Authentication,
|
||||||
|
commentProviders: CommentProvider,
|
||||||
|
comments: Comment,
|
||||||
|
groups: Group,
|
||||||
|
hooks: Hook,
|
||||||
|
locales: Locale,
|
||||||
|
navigation: Navigation,
|
||||||
|
pageHistory: PageHistory,
|
||||||
|
pageLinks: PageLink,
|
||||||
|
pages: Page,
|
||||||
|
renderers: Renderer,
|
||||||
|
settings: Setting,
|
||||||
|
sites: Site,
|
||||||
|
storage: Storage,
|
||||||
|
tags: Tag,
|
||||||
|
tree: Tree,
|
||||||
|
userKeys: UserKey,
|
||||||
|
users: User
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
const Model = require('objection').Model
|
import { Model } from 'objection'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locales model
|
* Locales model
|
||||||
*/
|
*/
|
||||||
module.exports = class Locale extends Model {
|
export class Locale extends Model {
|
||||||
static get tableName() { return 'locales' }
|
static get tableName() { return 'locales' }
|
||||||
static get idColumn() { return 'code' }
|
static get idColumn() { return 'code' }
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
module.exports = async (payload) => {
|
export async function task (payload) {
|
||||||
WIKI.logger.info('Checking for latest version...')
|
WIKI.logger.info('Checking for latest version...')
|
||||||
|
|
||||||
try {
|
try {
|
@ -1,6 +1,6 @@
|
|||||||
const { DateTime } = require('luxon')
|
import { DateTime } from 'luxon'
|
||||||
|
|
||||||
module.exports = async (payload) => {
|
export async function task (payload) {
|
||||||
WIKI.logger.info('Cleaning scheduler job history...')
|
WIKI.logger.info('Cleaning scheduler job history...')
|
||||||
|
|
||||||
try {
|
try {
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = async (payload) => {
|
export async function task (payload) {
|
||||||
WIKI.logger.info('Fetching latest localization data...')
|
WIKI.logger.info('Fetching latest localization data...')
|
||||||
|
|
||||||
try {
|
try {
|
@ -1,20 +1,20 @@
|
|||||||
const path = require('node:path')
|
import path from 'node:path'
|
||||||
const fs = require('fs-extra')
|
import fse from 'fs-extra'
|
||||||
const { DateTime } = require('luxon')
|
import { DateTime } from 'luxon'
|
||||||
|
|
||||||
module.exports = async ({ payload }) => {
|
export async function task ({ payload }) {
|
||||||
WIKI.logger.info('Purging orphaned upload files...')
|
WIKI.logger.info('Purging orphaned upload files...')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const uplTempPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads')
|
const uplTempPath = path.resolve(WIKI.ROOTPATH, WIKI.config.dataPath, 'uploads')
|
||||||
await fs.ensureDir(uplTempPath)
|
await fse.ensureDir(uplTempPath)
|
||||||
const ls = await fs.readdir(uplTempPath)
|
const ls = await fse.readdir(uplTempPath)
|
||||||
const fifteenAgo = DateTime.now().minus({ minutes: 15 })
|
const fifteenAgo = DateTime.now().minus({ minutes: 15 })
|
||||||
|
|
||||||
for (const f of ls) {
|
for (const f of ls) {
|
||||||
const stat = fs.stat(path.join(uplTempPath, f))
|
const stat = fse.stat(path.join(uplTempPath, f))
|
||||||
if ((await stat).isFile && stat.ctime < fifteenAgo) {
|
if ((await stat).isFile && stat.ctime < fifteenAgo) {
|
||||||
await fs.unlink(path.join(uplTempPath, f))
|
await fse.unlink(path.join(uplTempPath, f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in new issue